Skip to content

Commit

Permalink
Add CBOR support for the command-line interface.
Browse files Browse the repository at this point in the history
  • Loading branch information
timothee-haudebourg committed Apr 5, 2024
1 parent 095d25e commit 0fcc594
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 18 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ thiserror = "1.0.50"
serde = "1.0.192"
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
json-syntax = "0.12.3"
serde_cbor = "0.11.2"
codespan-reporting = "0.11.1"

locspan = "0.8.2"
Expand All @@ -54,11 +55,12 @@ proc-macro2 = "1.0.66"
quote = "1.0.33"

[dependencies]
treeldr-layouts.workspace = true
treeldr-layouts = { workspace = true, features = ["serde_cbor"] }
clap = { workspace = true, features = ["derive"] }
stderrlog.workspace = true
nquads-syntax.workspace = true
json-syntax.workspace = true
serde_cbor.workspace = true
codespan-reporting.workspace = true
thiserror.workspace = true
iref.workspace = true
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ subcommand, or the `-o` option of the `hydrate` subcommand.
| Tree format | Option value(s) |
| ----------- | ------------------------------------------------ |
| JSON | `application/json`, `json` |
| CBOR | `application/cbor`, `cbor` |

The following table lists all the RDF formats supported by TreeLDR.
The "Option value" can be given to the `-i` option of the `hydrate` subcommand,
Expand Down
4 changes: 2 additions & 2 deletions generators/rust/treeldr-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rdf_types::BlankId;
///
/// ```
/// use treeldr::tldr;
/// #[tldr("layouts/examples/simple_record.json")]
/// #[tldr("layouts/examples/record.json")]
/// mod module {
/// // a `SimpleLayout` type will be generated here.
/// }
Expand All @@ -22,7 +22,7 @@ pub use treeldr_macros::tldr;
///
/// ```
/// # use treeldr_macros::tldr_include;
/// tldr_include!("layouts/examples/simple_record.json");
/// tldr_include!("layouts/examples/record.json");
/// ```
pub use treeldr_macros::tldr_include;

Expand Down
2 changes: 1 addition & 1 deletion layouts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ json-syntax.workspace = true
lazy_static = "1.4.0"
static_assertions = "1.1.0"

serde_cbor = { version = "0.11.2", optional = true }
serde_cbor = { workspace = true, optional = true }

[dev-dependencies]
nquads-syntax.workspace = true
Expand Down
4 changes: 2 additions & 2 deletions layouts/src/value/cbor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rdf_types::{
};
use static_iref::iri;

use crate::{layout::LayoutType, Layouts, Ref};
use crate::{layout::LayoutType, LayoutRegistry, Ref};

#[cfg(feature = "serde_cbor")]
mod serde_cbor;
Expand Down Expand Up @@ -33,7 +33,7 @@ pub enum InvalidTag {
pub fn get_layout_tag<V, I>(
vocabulary: &V,
interpretation: &I,
layouts: &Layouts<I::Resource>,
layouts: &impl LayoutRegistry<I::Resource>,
layout_ref: &Ref<LayoutType, I::Resource>,
) -> Result<Option<u64>, InvalidTag>
where
Expand Down
32 changes: 26 additions & 6 deletions layouts/src/value/cbor/serde_cbor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use rdf_types::{
Interpretation, Vocabulary,
};

use crate::{value::Number, Layouts, Literal, TypedLiteral, TypedValue, Value};
use crate::{value::Number, LayoutRegistry, Literal, TypedLiteral, TypedValue, Value};

use super::{get_layout_tag, InvalidTag};

Expand All @@ -14,7 +14,7 @@ impl TypedValue {
/// <https://schema.treeldr.org/cbor#tag>.
pub fn try_into_tagged_serde_cbor(
self,
layouts: &Layouts,
layouts: impl LayoutRegistry,
) -> Result<serde_cbor::Value, InvalidTag> {
self.try_into_tagged_serde_cbor_with(&(), &(), layouts)
}
Expand All @@ -28,7 +28,23 @@ impl<R> TypedValue<R> {
self,
vocabulary: &V,
interpretation: &I,
layouts: &Layouts<R>,
layouts: impl LayoutRegistry<R>,
) -> Result<serde_cbor::Value, InvalidTag>
where
V: Vocabulary,
I: Interpretation<Resource = R>
+ IriInterpretation<V::Iri>
+ ReverseLiteralInterpretation<Literal = V::Literal>,
R: Ord,
{
self.try_into_tagged_serde_cbor_with_ref(vocabulary, interpretation, &layouts)
}

fn try_into_tagged_serde_cbor_with_ref<V, I>(
self,
vocabulary: &V,
interpretation: &I,
layouts: &impl LayoutRegistry<R>,
) -> Result<serde_cbor::Value, InvalidTag>
where
V: Vocabulary,
Expand All @@ -52,7 +68,7 @@ impl<R> TypedValue<R> {
}
Self::Literal(TypedLiteral::Id(s, ty)) => (serde_cbor::Value::Text(s), Some(ty.cast())),
Self::Variant(inner, ty, _) => (
inner.try_into_tagged_serde_cbor_with(vocabulary, interpretation, layouts)?,
inner.try_into_tagged_serde_cbor_with_ref(vocabulary, interpretation, layouts)?,
Some(ty.cast()),
),
Self::Record(map, ty) => (
Expand All @@ -61,7 +77,7 @@ impl<R> TypedValue<R> {
.map(|(key, value)| {
Ok((
serde_cbor::Value::Text(key),
value.try_into_tagged_serde_cbor_with(
value.try_into_tagged_serde_cbor_with_ref(
vocabulary,
interpretation,
layouts,
Expand All @@ -77,7 +93,11 @@ impl<R> TypedValue<R> {
items
.into_iter()
.map(|t| {
t.try_into_tagged_serde_cbor_with(vocabulary, interpretation, layouts)
t.try_into_tagged_serde_cbor_with_ref(
vocabulary,
interpretation,
layouts,
)
})
.collect::<Result<_, _>>()?,
),
Expand Down
7 changes: 6 additions & 1 deletion src/format/rdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use clap::builder::TypedValueParser;
use locspan::Span;
use nquads_syntax::Parse;
use rdf_types::dataset::BTreeDataset;
use rdf_types::Quad;

#[derive(Debug, thiserror::Error)]
pub enum LoadError {
Expand Down Expand Up @@ -57,7 +58,11 @@ impl RDFFormat {
}
}

pub fn write(&self, dataset: BTreeDataset, mut output: impl Write) -> Result<(), io::Error> {
pub fn write(
&self,
dataset: impl IntoIterator<Item = Quad>,
mut output: impl Write,
) -> Result<(), io::Error> {
match self {
Self::NQuads => {
for quad in dataset {
Expand Down
49 changes: 45 additions & 4 deletions src/format/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ use std::io::{self, BufRead, Write};

use clap::builder::TypedValueParser;
use json_syntax::Print;
use treeldr_layouts::value::NonJsonValue;
use treeldr_layouts::{value::NonJsonValue, LayoutRegistry};

#[derive(Debug, thiserror::Error)]
pub enum LoadError {
#[error("JSON parse error: {0}")]
Json(json_syntax::parse::Error<io::Error>),

#[error("CBOR parse error: {0}")]
Cbor(serde_cbor::Error),
}

#[derive(Debug, thiserror::Error)]
Expand All @@ -18,15 +21,23 @@ pub enum WriteError {

#[error(transparent)]
IO(#[from] io::Error),

#[error("invalid CBOR tag: {0}")]
CborTag(treeldr_layouts::value::cbor::InvalidTag),

#[error(transparent)]
Cbor(serde_cbor::Error),
}

#[derive(Debug, Clone)]
pub enum TreeFormat {
Json,
Cbor,
}

impl TreeFormat {
pub const POSSIBLE_VALUES: &'static [&'static str] = &["application/json", "json"];
pub const POSSIBLE_VALUES: &'static [&'static str] =
&["application/json", "json", "application/cbor", "cbor"];

pub fn parser(
) -> clap::builder::MapValueParser<clap::builder::PossibleValuesParser, fn(String) -> Self> {
Expand All @@ -36,14 +47,16 @@ impl TreeFormat {

pub fn new(name: &str) -> Option<Self> {
match name {
"json" | "application/json" => Some(Self::Json),
"application/json" | "json" => Some(Self::Json),
"application/cbor" | "cbor" => Some(Self::Cbor),
_ => None,
}
}

pub fn as_str(&self) -> &'static str {
match self {
Self::Json => "application/json",
Self::Cbor => "application/cbor",
}
}

Expand All @@ -56,11 +69,13 @@ impl TreeFormat {
json_syntax::Value::parse_utf8(utf8_input).map_err(LoadError::Json)?;
Ok(json.into())
}
Self::Cbor => serde_cbor::from_reader(input).map_err(LoadError::Cbor),
}
}

pub fn write(
pub fn write_typed(
&self,
layouts: &impl LayoutRegistry,
value: treeldr_layouts::TypedValue,
pretty: bool,
mut output: impl Write,
Expand All @@ -77,6 +92,32 @@ impl TreeFormat {
write!(output, "{}", json.compact_print()).map_err(WriteError::IO)
}
}
Self::Cbor => {
let cbor = value
.try_into_tagged_serde_cbor(layouts)
.map_err(WriteError::CborTag)?;
serde_cbor::to_writer(output, &cbor).map_err(WriteError::Cbor)
}
}
}

pub fn write_untyped(
&self,
value: treeldr_layouts::Value,
pretty: bool,
mut output: impl Write,
) -> Result<(), WriteError> {
match self {
Self::Json => {
let json: json_syntax::Value =
value.try_into().map_err(WriteError::NonJsonValue)?;
if pretty {
write!(output, "{}", json.pretty_print()).map_err(WriteError::IO)
} else {
write!(output, "{}", json.compact_print()).map_err(WriteError::IO)
}
}
Self::Cbor => serde_cbor::to_writer(output, &value).map_err(WriteError::Cbor),
}
}
}
Expand Down
61 changes: 60 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,41 @@ enum Command {
#[arg(short, long, value_parser = rdf::parse_term)]
layout: Option<Term>,
},

/// Convert a tree value or RDF dataset without changing its shape.
Convert {
#[command(subcommand)]
command: Convert,
},
}

#[derive(clap::Subcommand)]
pub enum Convert {
/// Convert a tree value into another format.
Tree {
/// Format of the input tree value.
#[arg(short, long, value_parser = TreeFormat::parser(), default_value = "json")]
input: TreeFormat,

/// Format of the output tree value.
#[arg(short, long, value_parser = TreeFormat::parser(), default_value = "json")]
output: TreeFormat,

/// Pretty print the output.
#[arg(short, long)]
pretty: bool,
},

/// Convert an RDF dataset into another format.
Rdf {
/// Format of the input RDF dataset.
#[arg(short, long, value_parser = RDFFormat::parser(), default_value = "n-quads")]
input: RDFFormat,

/// Format of the output RDF dataset.
#[arg(short, long, value_parser = RDFFormat::parser(), default_value = "n-quads")]
output: RDFFormat,
},
}

fn main() -> ExitCode {
Expand Down Expand Up @@ -199,7 +234,7 @@ impl Command {
treeldr_layouts::distill::hydrate(&layouts, &input, &layout_ref, &subjects)
.map_err(Error::Hydrate)?;
output
.write(output_data, pretty, io::stdout())
.write_typed(&layouts, output_data, pretty, io::stdout())
.map_err(Error::CreateTree)
}
Self::Dehydrate {
Expand All @@ -219,6 +254,30 @@ impl Command {
.map_err(Error::Dehydrate)?;
output.write(output_data, io::stdout()).map_err(Error::IO)
}
Self::Convert { command } => command.run(),
}
}
}

impl Convert {
fn run(self) -> Result<(), Error> {
match self {
Self::Tree {
input,
output,
pretty,
} => {
let stdin = BufReader::new(io::stdin());
let value = input.load(stdin).map_err(Error::LoadTree)?;
output
.write_untyped(value, pretty, io::stdout())
.map_err(Error::CreateTree)
}
Self::Rdf { input, output } => {
let stdin = BufReader::new(io::stdin());
let dataset = input.load(stdin).map_err(Error::LoadRdf)?.into_indexed();
output.write(dataset, io::stdout()).map_err(Error::IO)
}
}
}
}
Expand Down

0 comments on commit 0fcc594

Please sign in to comment.