diff --git a/load/src/document.rs b/load/src/document.rs index 33bee9d6..dfb84978 100644 --- a/load/src/document.rs +++ b/load/src/document.rs @@ -32,7 +32,7 @@ pub enum DeclaredDocument { #[cfg(feature = "turtle")] Turtle(Dataset), - Json(Box), + Json(Box), } impl Document { @@ -143,10 +143,7 @@ impl Document { .map_err(LangError::NQuads)?; Ok(DeclaredDocument::NQuads(dataset)) } - Self::Json(d) => { - d.declare(context, vocabulary, generator)?; - Ok(DeclaredDocument::Json(d)) - } + Self::Json(d) => Ok(d.declare(context, vocabulary, generator)?), } } } diff --git a/load/src/document/json.rs b/load/src/document/json.rs index 3db108ab..19c301d0 100644 --- a/load/src/document/json.rs +++ b/load/src/document/json.rs @@ -1,9 +1,9 @@ use json_syntax::Parse; -use locspan::Location; -use rdf_types::{Generator, VocabularyMut}; +use locspan::{Location, Meta}; +use rdf_types::{Generator, Id, VocabularyMut}; use treeldr::{BlankIdIndex, IriIndex}; -use crate::{source, BuildContext, LangError, LoadError}; +use crate::{source, BuildContext, Dataset, LangError, LoadError}; #[cfg(feature = "json-schema")] pub mod schema; @@ -53,27 +53,77 @@ pub enum Document { impl Document { pub fn declare>( - &self, + self, context: &mut BuildContext, vocabulary: &mut V, generator: &mut impl Generator, - ) -> Result<(), LangError> { + ) -> Result { match self { #[cfg(feature = "json-schema")] Self::Schema(s) => { - treeldr_json_schema::import_schema(s, None, context, vocabulary, generator)?; - Ok(()) + treeldr_json_schema::import_schema(&s, None, context, vocabulary, generator)?; + Ok(crate::document::DeclaredDocument::Json(Box::new( + DeclaredDocument::Schema(s), + ))) } #[cfg(feature = "lexicon")] - Self::Lexicon(_d) => { - // treeldr_lexicon::import(&s, None, context, vocabulary, generator)?; - // ... - unimplemented!() + Self::Lexicon(d) => { + let dataset: Dataset = d + .into_triples(vocabulary, &mut *generator) + .map(|triple| { + Meta( + triple + .map_subject(|s| Meta(s, source::Metadata::default())) + .map_predicate(|p| Meta(Id::Iri(p), source::Metadata::default())) + .map_object(|o| Meta(label_object(o), source::Metadata::default())) + .into_quad(None), + source::Metadata::default(), + ) + }) + .collect(); + + use treeldr_build::Document; + dataset + .declare(&mut (), context, vocabulary, generator) + .map_err(LangError::NQuads)?; + Ok(crate::document::DeclaredDocument::NQuads(dataset)) } } } } +fn label_object( + object: rdf_types::Object>, +) -> rdf_types::meta::Object { + match object { + rdf_types::Object::Id(id) => rdf_types::meta::Object::Id(id), + rdf_types::Object::Literal(l) => rdf_types::meta::Object::Literal(label_literal(l)), + } +} + +fn label_literal( + literal: rdf_types::Literal, +) -> rdf_types::meta::Literal { + match literal { + rdf_types::Literal::String(s) => { + rdf_types::meta::Literal::String(Meta(s, source::Metadata::default())) + } + rdf_types::Literal::TypedString(s, t) => rdf_types::meta::Literal::TypedString( + Meta(s, source::Metadata::default()), + Meta(t, source::Metadata::default()), + ), + rdf_types::Literal::LangString(s, t) => rdf_types::meta::Literal::LangString( + Meta(s, source::Metadata::default()), + Meta(t, source::Metadata::default()), + ), + } +} + +pub enum DeclaredDocument { + #[cfg(feature = "json-schema")] + Schema(treeldr_json_schema::Schema), +} + pub fn import

( files: &source::Files

, source_id: source::FileId, diff --git a/modules/lexicon/.rustfmt.toml b/modules/lexicon/.rustfmt.toml new file mode 100644 index 00000000..18d655e2 --- /dev/null +++ b/modules/lexicon/.rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true \ No newline at end of file diff --git a/modules/lexicon/Cargo.toml b/modules/lexicon/Cargo.toml index 419f87b6..cec126b5 100644 --- a/modules/lexicon/Cargo.toml +++ b/modules/lexicon/Cargo.toml @@ -9,9 +9,12 @@ edition.workspace = true treeldr.workspace = true log.workspace = true iref.workspace = true +static-iref.workspace = true +contextual.workspace = true clap = { workspace = true, features = ["derive"] } derivative.workspace = true thiserror.workspace = true json-syntax = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"] } -serde_repr = "0.1" \ No newline at end of file +serde_repr = "0.1" +rdf-types.workspace = true \ No newline at end of file diff --git a/modules/lexicon/src/import.rs b/modules/lexicon/src/import.rs index 821752b9..64d4cf2b 100644 --- a/modules/lexicon/src/import.rs +++ b/modules/lexicon/src/import.rs @@ -1,13 +1,703 @@ +use contextual::{DisplayWithContext, WithContext}; +use iref::{AsIri, Iri, IriBuf}; +use rdf_types::{ + BlankIdVocabulary, Generator, Id, IriVocabulary, Literal, Object, Triple, Vocabulary, + VocabularyMut, +}; +use treeldr::vocab; + +use crate::{ + LexBoolean, LexInteger, LexObject, LexPrimitive, LexPrimitiveArray, LexRef, LexRefUnion, + LexRefVariant, LexString, LexUnknown, LexUserType, LexXrpcBody, LexXrpcBodySchema, + LexXrpcParametersNonPrimitiveProperty, LexXrpcParametersProperty, LexXrpcQuery, LexiconDoc, + ObjectNonPrimitiveProperty, ObjectProperty, +}; + /// Checks if the given JSON document is a supported Lexicon document. pub fn is_lexicon_document(json: &json_syntax::Value) -> bool { - match json.as_object() { - Some(object) => match object.get("lexicon").next() { - Some(value) => match value.as_number() { - Some(number) => number.as_str() == "1", - None => false, - }, - None => false, - }, - None => false, - } + match json.as_object() { + Some(object) => match object.get("lexicon").next() { + Some(value) => match value.as_number() { + Some(number) => number.as_str() == "1", + None => false, + }, + None => false, + }, + None => false, + } +} + +pub type OutputSubject = Id<::Iri, ::BlankId>; +pub type OutputPredicate = ::Iri; +pub type OutputObject = Object, Literal::Iri>>; +pub type OutputTriple = Triple, OutputPredicate, OutputObject>; + +trait RdfId { + fn rdf_id(&self, vocabulary: &mut V, namespace: Iri) -> OutputSubject; +} + +pub struct IntoTriples<'v, V: Vocabulary, G> { + vocabulary: &'v mut V, + generator: G, + stack: Vec>, + pending: Vec>, +} + +impl<'v, V: Vocabulary, G> IntoTriples<'v, V, G> { + pub fn new(doc: LexiconDoc, vocabulary: &'v mut V, generator: G) -> Self { + Self { + vocabulary, + generator, + stack: vec![Item::Doc(doc)], + pending: Vec::new(), + } + } +} + +impl<'v, V: VocabularyMut, G: Generator> Iterator for IntoTriples<'v, V, G> +where + V::Iri: Clone, + V::BlankId: Clone, + OutputTriple: DisplayWithContext, +{ + type Item = Triple< + Id, + V::Iri, + Object, Literal>, + >; + + fn next(&mut self) -> Option { + if let Some(triple) = self.pending.pop() { + // eprintln!("{} .", triple.with(&*self.vocabulary)); + return Some(triple); + } + + while let Some(item) = self.stack.pop() { + item.process( + self.vocabulary, + &mut self.generator, + &mut self.stack, + &mut self.pending, + ); + + if let Some(triple) = self.pending.pop() { + // eprintln!("{} .", triple.with(&*self.vocabulary)); + return Some(triple); + } + } + + None + } +} + +pub enum Item { + Doc(LexiconDoc), + UserType(OutputSubject, LexUserType), + XrpcQuery(OutputSubject, LexXrpcQuery), + XrpcParametersProperty(OutputSubject, LexXrpcParametersProperty), + XrpcBody(OutputSubject, LexXrpcBody), + Primitive(OutputSubject, LexPrimitive), + PrimitiveArray(OutputSubject, LexPrimitiveArray), + RefVariant(OutputSubject, LexRefVariant), + Ref(OutputSubject, LexRef), + RefUnion(OutputSubject, LexRefUnion), + Boolean(OutputSubject, LexBoolean), + Integer(OutputSubject, LexInteger), + String(OutputSubject, LexString), + Object(OutputSubject, LexObject), + ObjectProperty(OutputSubject, ObjectProperty), + Unknown(OutputSubject, LexUnknown), +} + +impl Item { + pub fn process( + self, + vocabulary: &mut V, + generator: &mut impl Generator, + stack: &mut Vec>, + triples: &mut Vec>, + ) where + V::Iri: Clone, + V::BlankId: Clone, + { + match self { + Self::Doc(doc) => { + if let Some(main) = doc.definitions.main { + let iri = IriBuf::from_string(format!("lexicon:{}", doc.id)).unwrap(); + let id = Id::Iri(vocabulary.insert(iri.as_iri())); + stack.push(Item::UserType(id, main.into())) + } + + for (suffix, ty) in doc.definitions.other { + let iri = + IriBuf::from_string(format!("lexicon:{}.{}", doc.id, suffix)).unwrap(); + let id = Id::Iri(vocabulary.insert(iri.as_iri())); + stack.push(Item::UserType(id, ty)) + } + } + Self::UserType(id, ty) => match ty { + LexUserType::Record(_) => { + log::warn!("records are not yet supported") + } + LexUserType::Query(q) => stack.push(Item::XrpcQuery(id, q)), + LexUserType::Procedure(_) => { + log::warn!("procedures are not yet supported") + } + LexUserType::Subscription(_) => { + log::warn!("subscriptions are not yet supported") + } + LexUserType::Array(_) => { + log::warn!("arrays are not yet supported") + } + LexUserType::Token(_) => { + log::warn!("tokens are not yet supported") + } + LexUserType::Object(o) => stack.push(Item::Object(id, o)), + LexUserType::Boolean(b) => stack.push(Item::Boolean(id, b)), + LexUserType::Integer(i) => stack.push(Item::Integer(id, i)), + LexUserType::String(s) => stack.push(Item::String(id, s)), + LexUserType::Bytes(_) => { + log::warn!("bytes are not yet supported") + } + LexUserType::CidLink(_) => { + log::warn!("CID links are not yet supported") + } + LexUserType::Unknown(u) => stack.push(Item::Unknown(id, u)), + }, + Self::XrpcQuery(id, q) => { + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(vocab::TreeLdr::Layout.as_iri()))), + )); + + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::TreeLdr::Name.as_iri()), + Object::Literal(Literal::String( + nsid_name(vocabulary.iri(id.as_iri().unwrap()).unwrap().as_str()) + .to_string(), + )), + )); + + let fields_id = match q.parameters { + Some(params) => build_rdf_list( + vocabulary, + generator, + triples, + params.properties, + |vocabulary, generator, triples, (name, p)| { + let f_id = generator.next(vocabulary); + + triples.push(Triple( + f_id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri( + vocabulary.insert(vocab::TreeLdr::Field.as_iri()), + )), + )); + + triples.push(Triple( + f_id.clone(), + vocabulary.insert(vocab::TreeLdr::Name.as_iri()), + Object::Literal(Literal::String(name.clone())), + )); + + let item_iri = IriBuf::new(&format!( + "{}.{}", + vocabulary.iri(id.as_iri().unwrap()).unwrap(), + name + )) + .unwrap(); + let item_id = Id::Iri(vocabulary.insert(item_iri.as_iri())); + stack.push(Item::XrpcParametersProperty(item_id.clone(), p)); + + let t_id = generator.next(vocabulary); + triples.push(Triple( + t_id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri( + vocabulary.insert(vocab::TreeLdr::Layout.as_iri()), + )), + )); + + if params.required.contains(&name) { + triples.push(Triple( + t_id.clone(), + vocabulary.insert(vocab::TreeLdr::Required.as_iri()), + Object::Id(item_id), + )); + } else { + triples.push(Triple( + t_id.clone(), + vocabulary.insert(vocab::TreeLdr::Option.as_iri()), + Object::Id(item_id), + )); + }; + + triples.push(Triple( + f_id.clone(), + vocabulary.insert(vocab::TreeLdr::Format.as_iri()), + Object::Id(t_id), + )); + + Object::Id(f_id) + }, + ), + None => Id::Iri(vocabulary.insert(vocab::Rdf::Nil.as_iri())), + }; + + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::TreeLdr::Fields.as_iri()), + Object::Id(fields_id), + )); + + if let Some(output) = q.output { + let o_iri = IriBuf::new(&format!( + "{}.output", + vocabulary.iri(id.as_iri().unwrap()).unwrap() + )) + .unwrap(); + let o_id = Id::Iri(vocabulary.insert(o_iri.as_iri())); + stack.push(Item::XrpcBody(o_id, output)) + } + } + Self::XrpcParametersProperty(id, p) => match p { + LexXrpcParametersProperty::Primitive(p) => stack.push(Item::Primitive(id, p)), + LexXrpcParametersProperty::NonPrimitive(n) => match n { + LexXrpcParametersNonPrimitiveProperty::Array(a) => { + stack.push(Item::PrimitiveArray(id, a)) + } + }, + }, + Self::XrpcBody(id, b) => match b.schema { + LexXrpcBodySchema::Object(o) => stack.push(Item::Object(id, o)), + LexXrpcBodySchema::Ref(r) => stack.push(Item::RefVariant(id, r)), + }, + Self::Primitive(id, p) => match p { + LexPrimitive::Boolean(b) => stack.push(Item::Boolean(id, b)), + LexPrimitive::Integer(i) => stack.push(Item::Integer(id, i)), + LexPrimitive::String(s) => stack.push(Item::String(id, s)), + LexPrimitive::Unknown(u) => stack.push(Item::Unknown(id, u)), + }, + Self::PrimitiveArray(id, a) => { + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(vocab::TreeLdr::Layout.as_iri()))), + )); + + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::TreeLdr::Name.as_iri()), + Object::Literal(Literal::String( + nsid_name(vocabulary.iri(id.as_iri().unwrap()).unwrap().as_str()) + .to_string(), + )), + )); + + let item_iri = IriBuf::from_string(format!( + "{}.items", + vocabulary.iri(id.as_iri().unwrap()).unwrap() + )) + .unwrap(); + let item_id = Id::Iri(vocabulary.insert(item_iri.as_iri())); + stack.push(Item::Primitive(item_id.clone(), a.items)); + + triples.push(Triple( + id, + vocabulary.insert(vocab::TreeLdr::Array.as_iri()), + Object::Id(item_id), + )); + } + Self::RefVariant(id, r) => match r { + LexRefVariant::Ref(r) => stack.push(Item::Ref(id, r)), + LexRefVariant::Union(u) => stack.push(Item::RefUnion(id, u)), + }, + Self::Ref(id, r) => { + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(vocab::TreeLdr::Layout.as_iri()))), + )); + + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::TreeLdr::Name.as_iri()), + Object::Literal(Literal::String( + nsid_name(vocabulary.iri(id.as_iri().unwrap()).unwrap().as_str()) + .to_string(), + )), + )); + + let iri = IriBuf::from_string(format!("lexicon:{}", r.ref_)).unwrap(); + + triples.push(Triple( + id, + vocabulary.insert(vocab::TreeLdr::Alias.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(iri.as_iri()))), + )); + } + Self::RefUnion(id, r) => { + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(vocab::TreeLdr::Layout.as_iri()))), + )); + + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::TreeLdr::Name.as_iri()), + Object::Literal(Literal::String( + nsid_name(vocabulary.iri(id.as_iri().unwrap()).unwrap().as_str()) + .to_string(), + )), + )); + + if r.closed.is_some() { + log::warn!("ref union `closed` constraint not yet supported") + } + + let variants_id = build_rdf_list( + vocabulary, + generator, + triples, + r.refs, + |vocabulary, generator, triples, r| { + let v_id = generator.next(vocabulary); + + triples.push(Triple( + v_id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri( + vocabulary.insert(vocab::TreeLdr::Variant.as_iri()), + )), + )); + + triples.push(Triple( + v_id.clone(), + vocabulary.insert(vocab::TreeLdr::Name.as_iri()), + Object::Literal(Literal::String(nsid_name(&r).to_string())), + )); + + let format_iri = IriBuf::from_string(format!("lexicon:{}", r)).unwrap(); + let format_id = Id::Iri(vocabulary.insert(format_iri.as_iri())); + + triples.push(Triple( + v_id.clone(), + vocabulary.insert(vocab::TreeLdr::Format.as_iri()), + Object::Id(format_id), + )); + + Object::Id(v_id) + }, + ); + + triples.push(Triple( + id, + vocabulary.insert(vocab::TreeLdr::Enumeration.as_iri()), + Object::Id(variants_id), + )); + } + Self::Object(id, o) => { + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(vocab::TreeLdr::Layout.as_iri()))), + )); + + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::TreeLdr::Name.as_iri()), + Object::Literal(Literal::String( + nsid_name(vocabulary.iri(id.as_iri().unwrap()).unwrap().as_str()) + .to_string(), + )), + )); + + if !o.nullable.is_empty() { + log::warn!("object `nullable` constraint not yet supported") + } + + let fields_id = build_rdf_list( + vocabulary, + generator, + triples, + o.properties, + |vocabulary, generator, triples, (name, prop)| { + let f_id = generator.next(vocabulary); + + triples.push(Triple( + f_id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(vocab::TreeLdr::Field.as_iri()))), + )); + + triples.push(Triple( + f_id.clone(), + vocabulary.insert(vocab::TreeLdr::Name.as_iri()), + Object::Literal(Literal::String(name.clone())), + )); + + let item_iri = IriBuf::from_string(format!( + "{}.{}", + vocabulary.iri(id.as_iri().unwrap()).unwrap(), + name + )) + .unwrap(); + let item_id = Id::Iri(vocabulary.insert(item_iri.as_iri())); + stack.push(Item::ObjectProperty(item_id.clone(), prop)); + + let t_id = generator.next(vocabulary); + triples.push(Triple( + t_id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(vocab::TreeLdr::Layout.as_iri()))), + )); + + if o.required.contains(&name) { + triples.push(Triple( + t_id.clone(), + vocabulary.insert(vocab::TreeLdr::Required.as_iri()), + Object::Id(item_id), + )); + } else { + triples.push(Triple( + t_id.clone(), + vocabulary.insert(vocab::TreeLdr::Option.as_iri()), + Object::Id(item_id), + )); + }; + + triples.push(Triple( + f_id.clone(), + vocabulary.insert(vocab::TreeLdr::Format.as_iri()), + Object::Id(t_id), + )); + + Object::Id(f_id) + }, + ); + + triples.push(Triple( + id, + vocabulary.insert(vocab::TreeLdr::Fields.as_iri()), + Object::Id(fields_id), + )); + } + Self::ObjectProperty(id, p) => match p { + ObjectProperty::Ref(r) => stack.push(Item::RefVariant(id, r)), + ObjectProperty::Primitive(p) => stack.push(Item::Primitive(id, p)), + ObjectProperty::NonPrimitive(ObjectNonPrimitiveProperty::Array(_)) => { + log::warn!("arrays are not yet supported") + } + ObjectProperty::NonPrimitive(ObjectNonPrimitiveProperty::Blob(_)) => { + log::warn!("blobs are not yet supported") + } + ObjectProperty::Ipld(_) => { + log::warn!("IPLD types are not yet supported") + } + }, + Self::Boolean(id, b) => { + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(vocab::TreeLdr::Layout.as_iri()))), + )); + + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::TreeLdr::Name.as_iri()), + Object::Literal(Literal::String( + nsid_name(vocabulary.iri(id.as_iri().unwrap()).unwrap().as_str()) + .to_string(), + )), + )); + + if b.const_.is_some() { + log::warn!("boolean `const` constraint not yet supported") + } + + if b.default.is_some() { + log::warn!("boolean `default` constraint not yet supported") + } + + triples.push(Triple( + id, + vocabulary.insert(vocab::TreeLdr::Alias.as_iri()), + Object::Id(Id::Iri( + vocabulary.insert(vocab::Primitive::Boolean.as_iri()), + )), + )); + } + Self::Integer(id, i) => { + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(vocab::TreeLdr::Layout.as_iri()))), + )); + + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::TreeLdr::Name.as_iri()), + Object::Literal(Literal::String( + nsid_name(vocabulary.iri(id.as_iri().unwrap()).unwrap().as_str()) + .to_string(), + )), + )); + + if i.const_.is_some() { + log::warn!("integer `const` constraint not yet supported") + } + + if i.default.is_some() { + log::warn!("integer `default` constraint not yet supported") + } + + if i.enum_.is_some() { + log::warn!("integer `enum` constraint not yet supported") + } + + if i.minimum.is_some() { + log::warn!("integer `minimum` constraint not yet supported") + } + + if i.maximum.is_some() { + log::warn!("integer `maximum` constraint not yet supported") + } + + triples.push(Triple( + id, + vocabulary.insert(vocab::TreeLdr::Alias.as_iri()), + Object::Id(Id::Iri( + vocabulary.insert(vocab::Primitive::Boolean.as_iri()), + )), + )); + } + Self::String(id, s) => { + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(vocab::TreeLdr::Layout.as_iri()))), + )); + + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::TreeLdr::Name.as_iri()), + Object::Literal(Literal::String( + nsid_name(vocabulary.iri(id.as_iri().unwrap()).unwrap().as_str()) + .to_string(), + )), + )); + + if s.const_.is_some() { + log::warn!("string `const` constraint not yet supported") + } + + if s.default.is_some() { + log::warn!("string `default` constraint not yet supported") + } + + if s.enum_.is_some() { + log::warn!("string `enum` constraint not yet supported") + } + + if s.min_length.is_some() { + log::warn!("string `min_length` constraint not yet supported") + } + + if s.max_length.is_some() { + log::warn!("string `max_length` constraint not yet supported") + } + + if s.min_grapheme.is_some() { + log::warn!("string `min_grapheme` constraint not yet supported") + } + + if s.max_grapheme.is_some() { + log::warn!("string `max_grapheme` constraint not yet supported") + } + + if s.format.is_some() { + log::warn!("string `format` constraint not yet supported") + } + + triples.push(Triple( + id, + vocabulary.insert(vocab::TreeLdr::Alias.as_iri()), + Object::Id(Id::Iri( + vocabulary.insert(vocab::Primitive::Boolean.as_iri()), + )), + )); + } + Self::Unknown(id, _) => { + log::warn!("unknown user type {}", id.with(&*vocabulary)); + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(vocab::TreeLdr::Layout.as_iri()))), + )); + + triples.push(Triple( + id.clone(), + vocabulary.insert(vocab::TreeLdr::Name.as_iri()), + Object::Literal(Literal::String( + nsid_name(vocabulary.iri(id.as_iri().unwrap()).unwrap().as_str()) + .to_string(), + )), + )); + } + } + } +} + +fn nsid_name(nsid: &str) -> &str { + match nsid.rsplit_once('.') { + Some((_, r)) => r, + None => nsid, + } +} + +fn build_rdf_list, I: IntoIterator>( + vocabulary: &mut V, + generator: &mut G, + triples: &mut Vec>, + items: I, + mut f: impl FnMut(&mut V, &mut G, &mut Vec>, I::Item) -> OutputObject, +) -> OutputSubject +where + I::IntoIter: DoubleEndedIterator, + V::Iri: Clone, + V::BlankId: Clone, +{ + let mut head = Id::Iri(vocabulary.insert(vocab::Rdf::Nil.as_iri())); + + for item in items.into_iter().rev() { + let node = generator.next(vocabulary); + + triples.push(Triple( + node.clone(), + vocabulary.insert(vocab::Rdf::Type.as_iri()), + Object::Id(Id::Iri(vocabulary.insert(vocab::Rdf::List.as_iri()))), + )); + + let first = f(vocabulary, generator, triples, item); + + triples.push(Triple( + node.clone(), + vocabulary.insert(vocab::Rdf::First.as_iri()), + first, + )); + + triples.push(Triple( + node.clone(), + vocabulary.insert(vocab::Rdf::Rest.as_iri()), + Object::Id(head), + )); + + head = node + } + + head } diff --git a/modules/lexicon/src/lib.rs b/modules/lexicon/src/lib.rs index 2bd79ac8..0034c09d 100644 --- a/modules/lexicon/src/lib.rs +++ b/modules/lexicon/src/lib.rs @@ -3,237 +3,431 @@ pub mod import; use std::collections::BTreeMap; +use import::IntoTriples; +use rdf_types::Vocabulary; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -#[derive(Serialize_repr, Deserialize_repr)] +#[derive(Debug, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum Version { - One = 1, + One = 1, } /// A lexicon document. -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct LexiconDoc { - lexicon: Version, - id: String, - revision: Option, - description: Option, + pub lexicon: Version, + pub id: String, - #[serde(rename = "defs")] - definitions: BTreeMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub revision: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + #[serde(rename = "defs")] + pub definitions: Definitions, } -#[derive(Serialize, Deserialize)] -pub struct LexRef(String); +#[derive(Debug, Serialize, Deserialize)] +pub struct Definitions { + #[serde(skip_serializing_if = "Option::is_none")] + main: Option, -#[derive(Serialize, Deserialize)] -#[serde(untagged)] -pub enum Definition { - UserType(LexUserType), - Array(LexArray), - Primitive(LexPrimitive), - Ref(LexRef), + #[serde(flatten)] + other: BTreeMap, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(tag = "type")] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "kebab-case")] +pub enum LexMainUserType { + Record(LexRecord), + Query(LexXrpcQuery), + Procedure(LexXrpcProcedure), + Subscription(LexXrpcSubscription), +} + +impl From for LexUserType { + fn from(value: LexMainUserType) -> Self { + match value { + LexMainUserType::Record(r) => Self::Record(r), + LexMainUserType::Query(q) => Self::Query(q), + LexMainUserType::Procedure(p) => Self::Procedure(p), + LexMainUserType::Subscription(s) => Self::Subscription(s), + } + } +} + +impl LexiconDoc { + pub fn into_triples( + self, + vocabulary: &mut V, + generator: G, + ) -> IntoTriples { + IntoTriples::new(self, vocabulary, generator) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LexRef { + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + #[serde(rename = "ref")] + pub ref_: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LexRefUnion { + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + pub refs: Vec, + + #[serde(skip_serializing_if = "Option::is_none")] + pub closed: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type")] +#[serde(rename_all = "kebab-case")] +pub enum LexRefVariant { + Ref(LexRef), + Union(LexRefUnion), +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type")] +#[serde(rename_all = "kebab-case")] pub enum LexUserType { - Query(LexXrpcQuery), - Procedure(LexXrpcProcedure), - Record(LexRecord), - Token(LexToken), - Object(LexObject), - Blob(LexBlob), - Image(LexImage), - Video(LexVideo), - Audio(LexAudio), -} - -#[derive(Serialize, Deserialize)] + Record(LexRecord), + Query(LexXrpcQuery), + Procedure(LexXrpcProcedure), + Subscription(LexXrpcSubscription), + Array(LexArray), + Token(LexToken), + Object(LexObject), + Boolean(LexBoolean), + Integer(LexInteger), + String(LexString), + Bytes(LexBytes), + CidLink(LexCidLink), + Unknown(LexUnknown), +} + +#[derive(Debug, Serialize, Deserialize)] pub struct LexXrpcQuery { - description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, - #[serde(default)] - parameters: BTreeMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub parameters: Option, - output: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub output: Option, - #[serde(default)] - errors: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub errors: Vec, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum LexXrpcParametersType { + Params, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LexXrpcParameters { + #[serde(rename = "type")] + pub type_: LexXrpcParametersType, + + pub description: Option, + + #[serde(default)] + pub required: Vec, + + pub properties: BTreeMap, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum LexXrpcParametersProperty { + Primitive(LexPrimitive), + NonPrimitive(LexXrpcParametersNonPrimitiveProperty), +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum LexXrpcParametersNonPrimitiveProperty { + Array(LexPrimitiveArray), +} + +#[derive(Debug, Serialize, Deserialize)] pub struct LexXrpcBody { - description: Option, + pub description: Option, - encoding: Encoding, + pub encoding: LexXrpcBodyEncoding, - schema: LexObject, + pub schema: LexXrpcBodySchema, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] -pub enum Encoding { - One(String), - Many(Vec), +pub enum LexXrpcBodyEncoding { + One(String), + Many(Vec), } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum LexXrpcBodySchema { + Object(LexObject), + Ref(LexRefVariant), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LexXrpcSubscriptionMessage { + pub description: Option, + pub schema: LexXrpcBodySchema, +} + +#[derive(Debug, Serialize, Deserialize)] pub struct LexXrpcError { - name: String, - description: Option, + pub name: String, + pub description: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct LexXrpcProcedure { - description: Option, + pub description: Option, - #[serde(default)] - parameters: BTreeMap, + #[serde(default)] + pub parameters: BTreeMap, - input: Option, + pub input: Option, - output: Option, + pub output: Option, - #[serde(default)] - errors: Vec, + #[serde(default)] + pub errors: Vec, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct LexRecord { - description: Option, - key: Option, - record: LexObject, + pub description: Option, + pub key: Option, + pub record: LexObject, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] +pub struct LexXrpcSubscription { + pub description: Option, + pub parameters: Option, + pub message: Option, + + #[serde(default)] + pub infos: Vec, + + #[serde(default)] + pub errors: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] pub struct LexToken { - description: Option, + pub description: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct LexObject { - description: Option, - required: Vec, + pub description: Option, + + #[serde(default)] + pub required: Vec, - #[serde(default)] - properties: BTreeMap, + #[serde(default)] + pub nullable: Vec, + + #[serde(default)] + pub properties: BTreeMap, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] -pub enum Property { - Ref(LexRef), - Array(LexArray), - Primitive(LexPrimitive), - Refs(Vec), +pub enum ObjectProperty { + Ref(LexRefVariant), + Ipld(LexIpldType), + Primitive(LexPrimitive), + NonPrimitive(ObjectNonPrimitiveProperty), +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum ObjectNonPrimitiveProperty { + Array(LexArray), + Blob(LexBlob), +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum LexIpldType { + Bytes(LexBytes), + CidLink(LexCidLink), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LexCidLink { + pub description: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LexBytes { + pub description: Option, + + pub min_size: Option, + + pub max_size: Option, +} + +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LexBlob { - description: Option, + pub description: Option, - accept: Option>, + pub accept: Option>, - max_size: Option, + pub max_size: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LexImage { - description: Option, - accept: Option>, - max_size: Option, - max_width: Option, - max_height: Option, + pub description: Option, + pub accept: Option>, + pub max_size: Option, + pub max_width: Option, + pub max_height: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LexVideo { - description: Option, - accept: Option>, - max_size: Option, - max_width: Option, - max_height: Option, - max_length: Option, + pub description: Option, + pub accept: Option>, + pub max_size: Option, + pub max_width: Option, + pub max_height: Option, + pub max_length: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LexAudio { - description: Option, - accept: Option>, - max_size: Option, - max_length: Option, + pub description: Option, + pub accept: Option>, + pub max_size: Option, + pub max_length: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LexArray { - description: Option, - items: ArrayItem, - min_length: Option, - max_lenght: Option, +pub struct LexArray { + pub description: Option, + pub items: T, + pub min_length: Option, + pub max_length: Option, } -#[derive(Serialize, Deserialize)] +pub type LexPrimitiveArray = LexArray; + +#[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum ArrayItem { - Ref(LexRef), - Primitive(LexPrimitive), - Refs(Vec), + Primitive(LexPrimitive), + Ipld(LexIpldType), + Blob(LexBlob), + Ref(LexRefVariant), } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(tag = "type")] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "kebab-case")] pub enum LexPrimitive { - Boolean(LexBoolean), - Number(LexNumber), - Integer(LexInteger), - String(LexString), + Boolean(LexBoolean), + Integer(LexInteger), + String(LexString), + Unknown(LexUnknown), } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LexBoolean { - default: Option, - const_: Option, + pub description: Option, + pub default: Option, + pub const_: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LexNumber { - default: Option, - minimum: Option, - maximum: Option, - enum_: Option>, - const_: Option, + pub default: Option, + pub minimum: Option, + pub maximum: Option, + pub enum_: Option>, + pub const_: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LexInteger { - default: Option, - minimum: Option, - maximum: Option, - enum_: Option>, - const_: Option, + pub description: Option, + pub default: Option, + pub minimum: Option, + pub maximum: Option, + pub enum_: Option>, + pub const_: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LexString { - default: Option, - min_length: Option, - max_length: Option, - enum_: Option>, - const_: Option, - - #[serde(default)] - known_values: Vec, + pub description: Option, + pub default: Option, + pub format: Option, + pub min_length: Option, + pub max_length: Option, + pub min_grapheme: Option, + pub max_grapheme: Option, + pub enum_: Option>, + pub const_: Option, + + #[serde(default)] + pub known_values: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum LexStringFormat { + Datetime, + Uri, + AtUri, + Did, + Handle, + AtIdentifier, + Nsid, + Cid, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LexUnknown { + pub description: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LexParams { + #[serde(default)] + pub properties: BTreeMap, }