use oneyaml_core::{Limits, Settings, Span};
use super::{Mapping, Number, Sequence, Value};
use crate::construct::Tag as EventTag;
use crate::construct::keys::key_text;
use crate::construct::scalar::resolve;
use crate::error::Error;
use crate::resolve::{Resolved, Resolver};
type Json = serde_json::Value;
#[cfg_attr(docsrs, doc(cfg(feature = "serde_json")))]
impl TryFrom<Json> for Value {
type Error = Error;
fn try_from(json: Json) -> Result<Self, Error> {
from_json(json)
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "serde_json")))]
impl TryFrom<&Value> for Json {
type Error = Error;
fn try_from(value: &Value) -> Result<Self, Error> {
to_json(value, &Limits::default())
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "serde_json")))]
impl TryFrom<Value> for Json {
type Error = Error;
fn try_from(value: Value) -> Result<Self, Error> {
to_json(&value, &Limits::default())
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "serde_json")))]
impl Value {
pub fn to_json_with(&self, settings: &Settings) -> Result<Json, Error> {
to_json(self, settings.get_limits())
}
}
pub(crate) const JSON_MAX_DEPTH: usize = 128;
fn too_deep_for_json() -> Error {
Error::new(
oneyaml_core::code::JSON_DEPTH_EXCEEDED,
format!("JSON output nests deeper than {JSON_MAX_DEPTH} levels"),
None,
)
.with_help(format!(
"the cap of {JSON_MAX_DEPTH} levels is fixed (serde_json's recursion limit; its Value \
drops recursively) and no Limits setting raises it; for deeper data, serialize the \
Value itself (`Value::serialize`, e.g. `oneyaml::to_string`) instead of building a \
serde_json::Value"
))
}
enum Frame<'a> {
Seq {
items: std::slice::Iter<'a, Value>,
out: Vec<Json>,
index: usize,
},
Map {
entries: super::MappingIter<'a>,
out: serde_json::Map<String, Json>,
key: String,
},
}
impl Frame<'_> {
fn finish(self) -> Json {
match self {
Frame::Seq { out, .. } => Json::Array(out),
Frame::Map { out, .. } => Json::Object(out),
}
}
fn deliver(&mut self, j: Json) {
match self {
Frame::Seq { out, .. } => out.push(j),
Frame::Map { out, key, .. } => {
out.insert(std::mem::take(key), j);
}
}
}
}
fn located<'f, 'a: 'f>(e: Error, frames: impl DoubleEndedIterator<Item = &'f Frame<'a>>) -> Error {
let mut e = e;
for frame in frames.rev() {
e = match frame {
Frame::Seq { index, .. } => e.in_index(index.saturating_sub(1)),
Frame::Map { key, .. } => e.in_key(key),
};
}
e.finish(None, Vec::new())
}
fn number_to_json(n: &Number) -> Result<Json, Error> {
if let Some(f) = n.as_float() {
return serde_json::Number::from_f64(f)
.map(Json::Number)
.ok_or_else(|| {
Error::new(
oneyaml_core::code::INVALID_VALUE,
format!("float {n} has no JSON form (JSON numbers are finite)"),
None,
)
});
}
if let Some(u) = n.as_u64() {
return Ok(Json::Number(u.into()));
}
if let Some(i) = n.as_i64() {
return Ok(Json::Number(i.into()));
}
Err(Error::new(
oneyaml_core::code::INTEGER_OUT_OF_RANGE,
format!("integer {n} is out of range for a JSON number (i64 or u64)"),
None,
))
}
enum Step<'a> {
Leaf(Json),
Open(Frame<'a>),
}
fn step(v: &Value) -> Result<Step<'_>, Error> {
Ok(Step::Leaf(match v {
Value::Null => Json::Null,
Value::Bool(b) => Json::Bool(*b),
Value::Number(n) => number_to_json(n)?,
Value::String(s) => Json::String(s.clone()),
Value::Sequence(q) => {
return Ok(Step::Open(Frame::Seq {
items: q.iter(),
out: Vec::with_capacity(q.len()),
index: 0,
}));
}
Value::Mapping(m) => {
return Ok(Step::Open(Frame::Map {
entries: m.iter(),
out: serde_json::Map::new(),
key: String::new(),
}));
}
Value::Tagged(t) => {
return Err(Error::new(
oneyaml_core::code::TYPE_MISMATCH,
format!("tagged value `{}` has no JSON form", t.tag),
None,
));
}
}))
}
fn next_child<'a>(top: &mut Frame<'a>) -> Result<Option<&'a Value>, Error> {
match top {
Frame::Seq { items, index, .. } => Ok(items.next().inspect(|_| {
*index = index.saturating_add(1);
})),
Frame::Map { entries, key, .. } => match entries.next() {
None => Ok(None),
Some((Value::String(s), v)) => {
key.clone_from(s);
Ok(Some(v))
}
Some((other, _)) => Err(Error::new(
oneyaml_core::code::TYPE_MISMATCH,
format!(
"mapping key `{}` has no JSON form (JSON object keys are strings)",
key_text(other)
),
None,
)),
},
}
}
fn to_json(root: &Value, limits: &Limits) -> Result<Json, Error> {
if let Err(e) = root.check_depth(limits) {
return Err(e.into_error().finish(None, Vec::new()));
}
if super::walk::depth(root, JSON_MAX_DEPTH) > JSON_MAX_DEPTH {
return Err(too_deep_for_json().finish(None, Vec::new()));
}
let mut top = match step(root) {
Ok(Step::Leaf(j)) => return Ok(j),
Ok(Step::Open(f)) => f,
Err(e) => return Err(e.finish(None, Vec::new())),
};
let mut outer: Vec<Frame<'_>> = Vec::new();
loop {
let child = match next_child(&mut top) {
Ok(c) => c,
Err(e) => return Err(located(e, outer.iter())),
};
match child {
Some(v) => match step(v) {
Ok(Step::Leaf(j)) => top.deliver(j),
Ok(Step::Open(f)) => outer.push(std::mem::replace(&mut top, f)),
Err(e) => return Err(located(e, outer.iter().chain(std::iter::once(&top)))),
},
None => {
let done = match outer.pop() {
Some(parent) => std::mem::replace(&mut top, parent).finish(),
None => return Ok(top.finish()),
};
top.deliver(done);
}
}
}
}
fn number_from_json(n: &serde_json::Number, core: &Resolver) -> Result<Value, Error> {
if let Some(u) = n.as_u64() {
return Ok(Value::from(u));
}
if let Some(i) = n.as_i64() {
return Ok(Value::from(i));
}
number_from_text(&n.to_string(), core)
}
fn number_from_text(text: &str, core: &Resolver) -> Result<Value, Error> {
match resolve(core, &EventTag::Absent, true, text, Span::empty(0)) {
Ok(Resolved::Number(num)) if num.is_infinite() => Err(Error::new(
oneyaml_core::code::INVALID_VALUE,
format!("JSON number {text} overflows a float (YAML would read it as infinity)"),
None,
)),
Ok(Resolved::Number(num)) => Ok(Value::Number(num)),
_ => {
debug_assert!(false, "JSON number {text} is not a Core number");
Err(Error::internal("a JSON number is not a Core number", None))
}
}
}
enum JsonFrame {
Array {
items: std::vec::IntoIter<Json>,
out: Sequence,
index: usize,
},
Object {
entries: serde_json::map::IntoIter,
out: Mapping,
key: String,
},
}
impl JsonFrame {
fn finish(self) -> Value {
match self {
JsonFrame::Array { out, .. } => Value::Sequence(out),
JsonFrame::Object { out, .. } => Value::Mapping(out),
}
}
fn deliver(&mut self, v: Value) {
match self {
JsonFrame::Array { out, .. } => out.push(v),
JsonFrame::Object { out, key, .. } => {
out.insert(Value::String(std::mem::take(key)), v);
}
}
}
fn next_child(&mut self) -> Option<Json> {
match self {
JsonFrame::Array { items, index, .. } => items.next().inspect(|_| {
*index = index.saturating_add(1);
}),
JsonFrame::Object { entries, key, .. } => entries.next().map(|(k, v)| {
*key = k;
v
}),
}
}
}
enum Opened {
Leaf(Value),
Frame(JsonFrame),
}
fn open(j: Json, core: &Resolver) -> Result<Opened, Error> {
Ok(match j {
Json::Null => Opened::Leaf(Value::Null),
Json::Bool(b) => Opened::Leaf(Value::Bool(b)),
Json::Number(n) => Opened::Leaf(number_from_json(&n, core)?),
Json::String(s) => Opened::Leaf(Value::String(s)),
Json::Array(a) => Opened::Frame(JsonFrame::Array {
out: Sequence::from(Vec::with_capacity(a.len())),
items: a.into_iter(),
index: 0,
}),
Json::Object(o) => Opened::Frame(JsonFrame::Object {
out: Mapping::with_capacity(o.len()),
entries: o.into_iter(),
key: String::new(),
}),
})
}
fn located_json<'f>(e: Error, frames: impl DoubleEndedIterator<Item = &'f JsonFrame>) -> Error {
let mut e = e;
for frame in frames.rev() {
e = match frame {
JsonFrame::Array { index, .. } => e.in_index(index.saturating_sub(1)),
JsonFrame::Object { key, .. } => e.in_key(key),
};
}
e.finish(None, Vec::new())
}
fn from_json(root: Json) -> Result<Value, Error> {
let core = Resolver::new(&Settings::new());
let mut top = match open(root, &core) {
Ok(Opened::Leaf(v)) => return Ok(v),
Ok(Opened::Frame(f)) => f,
Err(e) => return Err(e.finish(None, Vec::new())),
};
let mut outer: Vec<JsonFrame> = Vec::new();
loop {
match top.next_child() {
Some(child) => match open(child, &core) {
Ok(Opened::Leaf(v)) => top.deliver(v),
Ok(Opened::Frame(f)) => outer.push(std::mem::replace(&mut top, f)),
Err(e) => {
let e = located_json(e, outer.iter().chain(std::iter::once(&top)));
drain(top, outer);
return Err(e);
}
},
None => {
let done = match outer.pop() {
Some(parent) => std::mem::replace(&mut top, parent).finish(),
None => return Ok(top.finish()),
};
top.deliver(done);
}
}
}
}
fn drain(top: JsonFrame, outer: Vec<JsonFrame>) {
let mut left: Vec<Json> = Vec::new();
let mut frames = outer;
frames.push(top);
for mut f in frames {
while let Some(j) = f.next_child() {
left.push(j);
}
}
while let Some(j) = left.pop() {
match j {
Json::Array(a) => left.extend(a),
Json::Object(o) => left.extend(o.into_iter().map(|(_, v)| v)),
_ => {}
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
use super::*;
fn core() -> Resolver {
Resolver::new(&Settings::new())
}
#[test]
fn json_numbers_are_exact() {
let r = core();
let num = |n: serde_json::Number| number_from_json(&n, &r).unwrap();
assert_eq!(num(u64::MAX.into()).as_u64(), Some(u64::MAX));
assert_eq!(num(i64::MIN.into()).as_i64(), Some(i64::MIN));
let n = num(serde_json::Number::from_f64(-0.0).unwrap());
assert!(n.as_f64().unwrap().is_sign_negative());
assert_eq!(
num(serde_json::Number::from_f64(1e300).unwrap()).as_f64(),
Some(1e300)
);
}
#[test]
fn arbitrary_precision_texts() {
let r = core();
let e = number_from_text("1e400", &r).unwrap_err();
assert_eq!(e.code(), oneyaml_core::code::INVALID_VALUE);
assert!(number_from_text("-1E+400", &r).is_err());
assert_eq!(number_from_text("-0", &r).unwrap(), Value::from(0));
let wide = number_from_text("123456789012345678901234567890123456789012", &r).unwrap();
assert!(wide.as_number().unwrap().is_big_integer());
assert_eq!(number_from_text("1e-400", &r).unwrap().as_f64(), Some(0.0));
}
#[test]
fn json_number_texts_are_core_numbers() {
let r = core();
let ints = ["0", "7", "-0", "-12", "123456789012345678901234567890"];
let fracs = ["", ".0", ".5", ".000123"];
let exps = ["", "e5", "E5", "e+5", "e-5", "E-400", "e400"];
for i in ints {
for f in fracs {
for e in exps {
let text = format!("{i}{f}{e}");
let got = resolve(&r, &EventTag::Absent, true, &text, Span::empty(0));
assert!(matches!(got, Ok(Resolved::Number(_))), "{text}");
}
}
}
for f in [1.5, 1e300, -0.0, 5e-324, f64::MAX, f64::MIN_POSITIVE, 0.1] {
let text = serde_json::Number::from_f64(f).unwrap().to_string();
let got = resolve(&r, &EventTag::Absent, true, &text, Span::empty(0));
assert!(matches!(got, Ok(Resolved::Number(_))), "{text}");
}
}
}