use crate::{Error, FromJson, Item, ItemCollection, Result, Value};
use bytes::Bytes;
use serde::Serialize;
use std::io::{BufWriter, Write};
pub trait FromNdjson: FromJson {
fn from_ndjson_bytes(bytes: impl Into<Bytes>) -> Result<Self> {
let bytes = bytes.into();
Self::from_json_slice(&bytes)
}
}
pub trait ToNdjson: Serialize {
fn to_ndjson_writer(&self, writer: impl Write) -> Result<()> {
serde_json::to_writer(writer, self).map_err(Error::from)
}
fn to_ndjson_vec(&self) -> Result<Vec<u8>> {
serde_json::to_vec(self).map_err(Error::from)
}
}
impl FromNdjson for Item {}
impl FromNdjson for crate::Catalog {}
impl FromNdjson for crate::Collection {}
impl FromNdjson for ItemCollection {
fn from_ndjson_bytes(bytes: impl Into<Bytes>) -> Result<Self> {
bytes
.into()
.split(|b| *b == b'\n')
.filter_map(|line| {
if line.is_empty() {
None
} else {
Some(serde_json::from_slice::<Item>(line).map_err(Error::from))
}
})
.collect::<Result<Vec<_>>>()
.map(ItemCollection::from)
}
}
impl FromNdjson for Value {
fn from_ndjson_bytes(bytes: impl Into<Bytes>) -> Result<Self> {
let values = bytes
.into()
.split(|b| *b == b'\n')
.filter_map(|line| {
if line.is_empty() {
None
} else {
Some(serde_json::from_slice::<Value>(line).map_err(Error::from))
}
})
.collect::<Result<Vec<_>>>()?;
vec_into_value(values)
}
}
fn vec_into_value(mut values: Vec<Value>) -> Result<Value> {
if values.len() == 1 {
Ok(values.pop().unwrap())
} else {
Ok(ItemCollection::from(
values
.into_iter()
.map(Item::try_from)
.collect::<Result<Vec<_>>>()?,
)
.into())
}
}
impl ToNdjson for Item {}
impl ToNdjson for crate::Catalog {}
impl ToNdjson for crate::Collection {}
impl ToNdjson for ItemCollection {
fn to_ndjson_writer(&self, writer: impl Write) -> Result<()> {
let mut writer = BufWriter::new(writer);
for item in &self.items {
serde_json::to_writer(&mut writer, item)?;
writeln!(&mut writer)?;
}
Ok(())
}
fn to_ndjson_vec(&self) -> Result<Vec<u8>> {
let mut vec = Vec::new();
self.to_ndjson_writer(&mut vec)?;
Ok(vec)
}
}
impl ToNdjson for Value {
fn to_ndjson_vec(&self) -> Result<Vec<u8>> {
match self {
Value::Item(item) => item.to_ndjson_vec(),
Value::Catalog(catalog) => catalog.to_ndjson_vec(),
Value::Collection(collection) => collection.to_ndjson_vec(),
Value::ItemCollection(item_collection) => item_collection.to_ndjson_vec(),
}
}
}
impl ToNdjson for serde_json::Value {
fn to_ndjson_vec(&self) -> Result<Vec<u8>> {
let mut buf = Vec::new();
self.to_ndjson_writer(&mut buf)?;
Ok(buf)
}
fn to_ndjson_writer(&self, writer: impl Write) -> Result<()> {
if self
.get("type")
.and_then(|type_| type_.as_str())
.map(|type_| type_ == "FeatureCollection")
.unwrap_or_default()
{
if let Some(features) = self
.get("features")
.and_then(|features| features.as_array())
{
let mut writer = BufWriter::new(writer);
for feature in features {
serde_json::to_writer(&mut writer, feature)?;
writeln!(&mut writer)?;
}
}
} else {
serde_json::to_writer(writer, self)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{FromNdjson, ToNdjson};
use crate::{FromJson, Item, ItemCollection, Value};
use std::io::Cursor;
use std::{fs::File, io::Read};
#[test]
fn item_collection_from_bytes() {
let mut buf = Vec::new();
let _ = File::open("data/items.ndjson")
.unwrap()
.read_to_end(&mut buf)
.unwrap();
let item_collection = ItemCollection::from_ndjson_bytes(buf).unwrap();
assert_eq!(item_collection.items.len(), 2);
}
#[test]
fn value_from_bytes() {
let mut buf = Vec::new();
let _ = File::open("data/items.ndjson")
.unwrap()
.read_to_end(&mut buf)
.unwrap();
let _ = Value::from_ndjson_bytes(buf).unwrap();
}
#[test]
fn item_collection_write() {
let item_collection = ItemCollection::from(vec![Item::new("an-item")]);
let mut cursor = Cursor::new(Vec::new());
item_collection.to_ndjson_writer(&mut cursor).unwrap();
let _ = Item::from_json_slice(&cursor.into_inner()).unwrap();
}
#[test]
fn json_item_collection_write() {
let item_collection =
serde_json::to_value(ItemCollection::from(vec![Item::new("an-item")])).unwrap();
let mut cursor = Cursor::new(Vec::new());
item_collection.to_ndjson_writer(&mut cursor).unwrap();
let _ = Item::from_json_slice(&cursor.into_inner()).unwrap();
}
}