Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import JSON Schemas #14

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ treeldr = { path = "../core" }
treeldr-syntax = { path = "../syntax" }
iref = "2.1.2"
grdf = { version = "0.7.3", features = ["loc"] }
serde_json = "1.0.79"
log = "0.4"
locspan = "*"
locspan = "0.3"
codespan-reporting = "0.11"
stderrlog = "0.5"
clap = { version = "3.0", features = ["derive"] }
Expand Down
73 changes: 69 additions & 4 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,29 @@ fn main() {
let mut vocab = treeldr::Vocabulary::new();
let mut quads = Vec::new();
for filename in args.filenames {
match files.load(&filename, None) {
Ok(file_id) => {
import_treeldr(&mut vocab, &mut quads, &files, file_id);
}
match files.load(&filename, None, None) {
Ok(file_id) => match files.get(file_id).unwrap().mime_type() {
Some(source::MimeType::TreeLdr) => {
import_treeldr(&mut vocab, &mut quads, &files, file_id);
}
#[cfg(feature = "json-schema")]
Some(source::MimeType::JsonSchema) => {
import_json_schema(&mut vocab, &mut quads, &files, file_id);
}
#[allow(unreachable_patterns)]
Some(mime_type) => {
log::error!(
"unsupported mime type `{}` for file `{}`",
mime_type,
filename.display()
);
std::process::exit(1);
}
None => {
log::error!("unknown format for file `{}`", filename.display());
std::process::exit(1);
}
},
Err(e) => {
log::error!("unable to read file `{}`: {}", filename.display(), e);
std::process::exit(1);
Expand Down Expand Up @@ -133,3 +152,49 @@ fn import_treeldr(
}
}
}

#[cfg(feature = "json-schema")]
/// Import a JSON Schema file.
fn import_json_schema(
vocab: &mut treeldr::Vocabulary,
quads: &mut Vec<syntax::vocab::LocQuad<source::FileId>>,
files: &source::Files,
source_id: source::FileId,
) {
let file = files.get(source_id).unwrap();

match serde_json::from_str::<serde_json::Value>(file.buffer()) {
Ok(json) => match treeldr_json_schema::Schema::try_from(json) {
Ok(schema) => {
if let Err(e) =
treeldr_json_schema::import_schema(&schema, &source_id, None, vocab, quads)
{
let diagnostic = codespan_reporting::diagnostic::Diagnostic::error()
.with_message(format!("JSON Schema import failed: {}", e));
let writer = StandardStream::stderr(ColorChoice::Always);
let config = codespan_reporting::term::Config::default();
term::emit(&mut writer.lock(), &config, files, &diagnostic)
.expect("diagnostic failed");
std::process::exit(1);
}
}
Err(e) => {
let diagnostic = codespan_reporting::diagnostic::Diagnostic::error()
.with_message(format!("JSON Schema error: {}", e));
let writer = StandardStream::stderr(ColorChoice::Always);
let config = codespan_reporting::term::Config::default();
term::emit(&mut writer.lock(), &config, files, &diagnostic)
.expect("diagnostic failed");
std::process::exit(1);
}
},
Err(e) => {
let diagnostic = codespan_reporting::diagnostic::Diagnostic::error()
.with_message(format!("JSON parse error: {}", e));
let writer = StandardStream::stderr(ColorChoice::Always);
let config = codespan_reporting::term::Config::default();
term::emit(&mut writer.lock(), &config, files, &diagnostic).expect("diagnostic failed");
std::process::exit(1);
}
}
}
44 changes: 44 additions & 0 deletions cli/src/source.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use iref::{Iri, IriBuf};
use std::collections::HashMap;
use std::fmt;
use std::ops::{Deref, Range};
use std::path::{Path, PathBuf};

Expand All @@ -10,6 +11,7 @@ pub struct File {
source: PathBuf,
base_iri: Option<IriBuf>,
buffer: Buffer,
mime_type: Option<MimeType>,
}

impl File {
Expand All @@ -24,6 +26,45 @@ impl File {
pub fn buffer(&self) -> &Buffer {
&self.buffer
}

pub fn mime_type(&self) -> Option<MimeType> {
self.mime_type
}
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum MimeType {
/// application/treeldr
TreeLdr,

/// application/schema+json
JsonSchema,
}

impl MimeType {
fn name(&self) -> &'static str {
match self {
Self::TreeLdr => "application/treeldr",
Self::JsonSchema => "application/schema+json",
}
}

fn infer(source: &Path, _content: &str) -> Option<MimeType> {
source
.extension()
.and_then(std::ffi::OsStr::to_str)
.and_then(|ext| match ext {
"tldr" => Some(MimeType::TreeLdr),
"json" => Some(MimeType::JsonSchema),
_ => None,
})
}
}

impl fmt::Display for MimeType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.name().fmt(f)
}
}

#[derive(Default)]
Expand Down Expand Up @@ -53,17 +94,20 @@ impl Files {
&mut self,
source: &impl AsRef<Path>,
base_iri: Option<IriBuf>,
mime_type: Option<MimeType>,
) -> std::io::Result<FileId> {
let source = source.as_ref();
match self.sources.get(source).cloned() {
Some(id) => Ok(id),
None => {
let content = std::fs::read_to_string(source)?;
let id = FileId(self.files.len());
let mime_type = mime_type.or_else(|| MimeType::infer(source, &content));
self.files.push(File {
source: source.into(),
base_iri,
buffer: Buffer::new(content),
mime_type,
});
self.sources.insert(source.into(), id);
Ok(id)
Expand Down
49 changes: 40 additions & 9 deletions core/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,17 +178,10 @@ impl<F: Clone + Ord> Context<F> {
}
}
Term::Schema(vocab::Schema::MultipleValues) => {
let (prop, field) =
self.require_property_or_layout_field_mut(id, Some(id_loc))?;
let prop = self.require_property_mut(id, Some(id_loc))?;
let Loc(multiple, _) = expect_boolean(object)?;

if let Some(prop) = prop {
prop.set_functional(!multiple, Some(loc.clone()))?
}

if let Some(field) = field {
field.set_functional(!multiple, Some(loc))?
}
prop.set_functional(!multiple, Some(loc.clone()))?
}
Term::Owl(vocab::Owl::UnionOf) => {
let ty = self.require_type_mut(id, Some(id_loc))?;
Expand Down Expand Up @@ -258,6 +251,44 @@ impl<F: Clone + Ord> Context<F> {
let layout = self.require_layout_mut(id, Some(id_loc))?;
layout.set_enum(fields_id, Some(loc))?
}
Term::TreeLdr(vocab::TreeLdr::Set) => {
let Loc(item_layout_id, _) = expect_id(object)?;
let layout = self.require_layout_mut(id, Some(id_loc))?;
layout.set_set(item_layout_id, Some(loc))?
}
Term::TreeLdr(vocab::TreeLdr::List) => {
let Loc(item_layout_id, _) = expect_id(object)?;
let layout = self.require_layout_mut(id, Some(id_loc))?;
layout.set_list(item_layout_id, Some(loc))?
}
Term::TreeLdr(vocab::TreeLdr::Native) => {
let Loc(native_layout_id, layout_id_loc) = expect_id(object)?;

let native_layout = match native_layout_id {
Id::Iri(Term::Xsd(vocab::Xsd::AnyUri)) => layout::Native::Uri,
Id::Iri(Term::Xsd(vocab::Xsd::Boolean)) => layout::Native::Boolean,
Id::Iri(Term::Xsd(vocab::Xsd::Date)) => layout::Native::Date,
Id::Iri(Term::Xsd(vocab::Xsd::DateTime)) => layout::Native::DateTime,
Id::Iri(Term::Xsd(vocab::Xsd::Double)) => layout::Native::Double,
Id::Iri(Term::Xsd(vocab::Xsd::Float)) => layout::Native::Float,
Id::Iri(Term::Xsd(vocab::Xsd::Int)) => layout::Native::Integer,
Id::Iri(Term::Xsd(vocab::Xsd::Integer)) => layout::Native::Integer,
Id::Iri(Term::Xsd(vocab::Xsd::PositiveInteger)) => {
layout::Native::PositiveInteger
}
Id::Iri(Term::Xsd(vocab::Xsd::String)) => layout::Native::String,
Id::Iri(Term::Xsd(vocab::Xsd::Time)) => layout::Native::Time,
_ => {
return Err(Error::new(
error::LayoutNativeInvalid(native_layout_id).into(),
Some(layout_id_loc),
))
}
};

let layout = self.require_layout_mut(id, Some(id_loc))?;
layout.set_native(native_layout, Some(loc))?
}
_ => (),
}
}
Expand Down
17 changes: 10 additions & 7 deletions core/src/build/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ impl<F> Context<F> {

/// Resolve all the reference layouts.
///
/// Checks that the type of a reference layout (`&T`) is equal to the type of the target layout (`T`).
/// Checks that the type of a reference layout (`&T`) is equal to the type of the target layout (`T`), if any.
/// If no type is defined for the reference layout, it is set to the correct type.
pub fn resolve_references(&mut self) -> Result<(), Error<F>>
where
Expand Down Expand Up @@ -165,12 +165,15 @@ impl<F> Context<F> {
for (id, target_layout_id) in by_depth {
let (target_layout_id, cause) = target_layout_id.into_parts();
let target_layout = self.require_layout(target_layout_id, cause.clone())?;
let (target_ty_id, ty_cause) = target_layout.require_ty(cause)?.clone().into_parts();
self.get_mut(id)
.unwrap()
.as_layout_mut()
.unwrap()
.set_type(target_ty_id, ty_cause.into_preferred())?

if let Some(target_ty) = target_layout.ty().cloned() {
let (target_ty_id, ty_cause) = target_ty.into_parts();
self.get_mut(id)
.unwrap()
.as_layout_mut()
.unwrap()
.set_type(target_ty_id, ty_cause.into_preferred())?
}
}

Ok(())
Expand Down
47 changes: 34 additions & 13 deletions core/src/build/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ pub enum Description {
Reference(Id),
Literal(RegExp),
Enum(Id),
Set(Id),
List(Id),
}

impl Description {
Expand All @@ -71,6 +73,8 @@ impl Description {
Self::Native(n) => Type::Native(*n),
Self::Literal(_) => Type::Literal,
Self::Enum(_) => Type::Enum,
Self::Set(_) => Type::Set,
Self::List(_) => Type::List,
}
}
}
Expand All @@ -91,11 +95,6 @@ impl<F> Definition<F> {
self.ty.with_causes()
}

pub fn require_ty(&self, cause: Option<Location<F>>) -> Result<&WithCauses<Id, F>, Error<F>> {
self.ty
.value_or_else(|| Caused::new(error::LayoutMissingType(self.id).into(), cause))
}

pub fn add_use(&mut self, user_layout: Id, field: Id) {
self.uses.insert(UsedBy { user_layout, field });
}
Expand Down Expand Up @@ -249,6 +248,20 @@ impl<F> Definition<F> {
{
self.set_description(Description::Enum(items), cause)
}

pub fn set_set(&mut self, item_layout: Id, cause: Option<Location<F>>) -> Result<(), Error<F>>
where
F: Clone + Ord,
{
self.set_description(Description::Set(item_layout), cause)
}

pub fn set_list(&mut self, item_layout: Id, cause: Option<Location<F>>) -> Result<(), Error<F>>
where
F: Clone + Ord,
{
self.set_description(Description::List(item_layout), cause)
}
}

/// Field/layout usage.
Expand Down Expand Up @@ -306,15 +319,11 @@ impl<F: Ord + Clone> WithCauses<Definition<F>, F> {
) -> Result<crate::layout::Definition<F>, Error<F>> {
let (def, causes) = self.into_parts();

let ty_id = def.ty.ok_or_else(|| {
Caused::new(
error::LayoutMissingType(id).into(),
causes.preferred().cloned(),
)
let ty = def.ty.try_map_with_causes(|ty_id| {
Ok(*nodes
.require_type(*ty_id, ty_id.causes().preferred().cloned())?
.inner())
})?;
let ty = nodes
.require_type(*ty_id, ty_id.causes().preferred().cloned())?
.clone_with_causes(ty_id.into_causes());

let def_desc = def.desc.ok_or_else(|| {
Caused::new(
Expand Down Expand Up @@ -415,6 +424,18 @@ impl<F: Ord + Clone> WithCauses<Definition<F>, F> {
let lit = crate::layout::Literal::new(regexp, name, def.id.is_blank());
Ok(crate::layout::Description::Literal(lit))
}
Description::Set(layout_id) => {
let layout_ref = *nodes
.require_layout(layout_id, desc_causes.preferred().cloned())?
.inner();
Ok(crate::layout::Description::Set(layout_ref, def.name))
}
Description::List(layout_id) => {
let layout_ref = *nodes
.require_layout(layout_id, desc_causes.preferred().cloned())?
.inner();
Ok(crate::layout::Description::List(layout_ref, def.name))
}
})
.map_err(Caused::flatten)?;

Expand Down
Loading