pub mod bin;
mod bencode;
mod bson;
mod cbor;
mod csv;
mod envy;
mod hjson;
mod json;
mod json5;
mod msgpack;
mod pickle;
mod postcard;
mod ron;
mod sexpr;
mod toml;
mod tree;
pub use self::tree::TreeDecoder;
mod urlform;
mod yaml;
use alloc::vec::Vec;
pub use self::bencode::Bencode;
pub use self::bencode::{BencodeDecoder, BencodeEncoder};
pub use self::bson::Bson;
pub use self::bson::{BsonDecoder, BsonEncoder};
pub use self::cbor::Cbor;
pub use self::cbor::{CborDecoder, CborEncoder};
pub use self::csv::Csv;
pub use self::csv::{CsvDecoder, CsvEncoder};
pub use self::envy::Envy;
pub use self::envy::EnvyDecoder;
pub use self::hjson::Hjson;
pub use self::hjson::{HjsonDecoder, HjsonEncoder};
pub use self::json::Json;
pub use self::json::{JsonDecoder, JsonEncoder};
pub use self::json5::Json5;
pub use self::json5::{Json5Decoder, Json5Encoder};
pub use self::msgpack::MsgPack;
pub use self::msgpack::{MsgPackDecoder, MsgPackEncoder};
pub use self::pickle::Pickle;
pub use self::pickle::{PickleDecoder, PickleEncoder};
pub use self::postcard::Postcard;
pub use self::postcard::{PostcardDecoder, PostcardEncoder};
pub use self::ron::Ron;
pub use self::ron::{RonDecoder, RonEncoder};
pub use self::sexpr::Sexpr;
pub use self::sexpr::{SexprDecoder, SexprEncoder};
pub use self::toml::Toml;
pub use self::toml::{TomlDecoder, TomlEncoder};
pub use self::urlform::UrlForm;
pub use self::urlform::{UrlFormDecoder, UrlFormEncoder};
pub use self::yaml::Yaml;
pub use self::yaml::{YamlDecoder, YamlEncoder};
use crate::error::Result;
use crate::value::Value;
use crate::{NsonDeserialize, NsonSerialize};
pub trait Format: Copy + 'static {
const NAME: &'static str;
const MIME: &'static str;
const EXTENSIONS: &'static [&'static str];
const BINARY: bool;
fn encode<T: NsonSerialize + ?Sized>(self, value: &T) -> Result<Vec<u8>>;
fn decode<'de, T: NsonDeserialize<'de>>(self, input: &'de [u8]) -> Result<T>;
}
pub fn encode_with<T: NsonSerialize + ?Sized, F: Format>(value: &T, format: F) -> Result<Vec<u8>> {
format.encode(value)
}
pub fn decode_with<'de, T: NsonDeserialize<'de>, F: Format>(
input: &'de [u8],
format: F,
) -> Result<T> {
format.decode(input)
}
pub fn to_value<F: Format>(input: &[u8], format: F) -> Result<Value> {
format.decode(input)
}
pub fn transcode<F: Format, G: Format>(input: &[u8], from: F, to: G) -> Result<Vec<u8>> {
let value: Value = from.decode(input)?;
to.encode(&value)
}
#[derive(Clone, Copy, Debug)]
pub struct FormatInfo {
pub kind: FormatKind,
pub name: &'static str,
pub mime: &'static str,
pub extensions: &'static [&'static str],
pub binary: bool,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
#[non_exhaustive]
pub enum FormatKind {
Json,
Json5,
Hjson,
Yaml,
Toml,
Ron,
Sexpr,
Csv,
UrlForm,
Cbor,
MsgPack,
Bson,
Bencode,
Postcard,
Pickle,
Envy,
}
pub fn all() -> &'static [FormatInfo] {
&[
FormatInfo {
kind: FormatKind::Json,
name: "json",
mime: "application/json",
extensions: &["json"],
binary: false,
},
FormatInfo {
kind: FormatKind::Json5,
name: "json5",
mime: "application/json5",
extensions: &["json5"],
binary: false,
},
FormatInfo {
kind: FormatKind::Hjson,
name: "hjson",
mime: "application/hjson",
extensions: &["hjson"],
binary: false,
},
FormatInfo {
kind: FormatKind::Yaml,
name: "yaml",
mime: "application/yaml",
extensions: &["yaml", "yml"],
binary: false,
},
FormatInfo {
kind: FormatKind::Toml,
name: "toml",
mime: "application/toml",
extensions: &["toml"],
binary: false,
},
FormatInfo {
kind: FormatKind::Ron,
name: "ron",
mime: "text/ron",
extensions: &["ron"],
binary: false,
},
FormatInfo {
kind: FormatKind::Sexpr,
name: "sexpr",
mime: "text/x-sexpr",
extensions: &["sexp", "sx", "scm"],
binary: false,
},
FormatInfo {
kind: FormatKind::Csv,
name: "csv",
mime: "text/csv",
extensions: &["csv"],
binary: false,
},
FormatInfo {
kind: FormatKind::UrlForm,
name: "urlform",
mime: "application/x-www-form-urlencoded",
extensions: &["form"],
binary: false,
},
FormatInfo {
kind: FormatKind::Cbor,
name: "cbor",
mime: "application/cbor",
extensions: &["cbor"],
binary: true,
},
FormatInfo {
kind: FormatKind::MsgPack,
name: "msgpack",
mime: "application/msgpack",
extensions: &["msgpack", "mp"],
binary: true,
},
FormatInfo {
kind: FormatKind::Bson,
name: "bson",
mime: "application/bson",
extensions: &["bson"],
binary: true,
},
FormatInfo {
kind: FormatKind::Bencode,
name: "bencode",
mime: "application/x-bittorrent",
extensions: &["torrent", "bencode"],
binary: true,
},
FormatInfo {
kind: FormatKind::Postcard,
name: "postcard",
mime: "application/postcard",
extensions: &["postcard"],
binary: true,
},
FormatInfo {
kind: FormatKind::Pickle,
name: "pickle",
mime: "application/python-pickle",
extensions: &["pkl", "pickle"],
binary: true,
},
FormatInfo {
kind: FormatKind::Envy,
name: "envy",
mime: "text/plain",
extensions: &[],
binary: false,
},
]
}
pub fn by_name(name: &str) -> Option<FormatKind> {
all()
.iter()
.find(|info| info.name.eq_ignore_ascii_case(name))
.map(|info| info.kind)
}
pub fn by_extension(ext: &str) -> Option<FormatKind> {
let ext = ext.strip_prefix('.').unwrap_or(ext);
all()
.iter()
.find(|info| info.extensions.iter().any(|e| e.eq_ignore_ascii_case(ext)))
.map(|info| info.kind)
}
pub fn detect(input: &[u8]) -> Option<FormatKind> {
let first = *input.first()?;
if (0x80..=0x85).contains(&first) && input.get(1).is_some_and(|b| *b <= 5) {
return Some(FormatKind::Pickle);
}
if (matches!(first, b'd' | b'l' | b'i') || first.is_ascii_digit()) && bencode_like(input) {
return Some(FormatKind::Bencode);
}
if input.len() >= 5 && plausible_bson_length(input) {
return Some(FormatKind::Bson);
}
match first {
b'-' if input.starts_with(b"---") => return Some(FormatKind::Yaml),
b'{' | b'[' | b'"' | b'-' | b'+' | b'.' => return Some(FormatKind::Json),
b't' | b'f' | b'n' => return Some(FormatKind::Json),
b'(' => return Some(FormatKind::Sexpr),
b'#' | b'=' => return Some(FormatKind::Toml),
b'%' => return Some(FormatKind::UrlForm),
_ => {}
}
if is_msgpack_signature(first) {
return Some(FormatKind::MsgPack);
}
if is_cbor_signature(first) {
return Some(FormatKind::Cbor);
}
None
}
fn plausible_bson_length(input: &[u8]) -> bool {
if input.len() < 5 {
return false;
}
let Ok(len) = usize::try_from(u32::from_le_bytes([input[0], input[1], input[2], input[3]]))
else {
return false;
};
len == input.len() && matches!(input[4], 0x01..=0x13 | 0xFF)
}
fn bencode_like(input: &[u8]) -> bool {
let mut i = 0;
if matches!(input[0], b'd' | b'l' | b'i') {
return true;
}
while i < input.len() && input[i].is_ascii_digit() {
i += 1;
}
i < input.len() && input[i] == b':'
}
fn is_cbor_signature(b: u8) -> bool {
matches!(
b,
0x18..=0x1B
| 0x38..=0x3B
| 0x58..=0x5B
| 0x78..=0x7B
| 0x98..=0x9B
| 0xB8..=0xBB
| 0xC0..=0xDB
| 0xF4..=0xF7
| 0xF9..=0xFB
)
}
fn is_msgpack_signature(b: u8) -> bool {
matches!(
b,
0x80..=0x8F
| 0x90..=0x9F
| 0xA0..=0xBF
| 0xC0..=0xC1
| 0xC2..=0xC3
| 0xC4..=0xC7
| 0xCA..=0xCB
| 0xCC..=0xD4
| 0xD9..=0xDB
| 0xDC..=0xDD
| 0xDE..=0xDF
| 0xE0..=0xFF
)
}