Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,135 @@
|
||||
use dever_runtime::wire::document;
|
||||
|
||||
fn document_fault(_: impl std::fmt::Display) -> Fault {
|
||||
&FaultInfo { message: "JSON failure", source: "document-reference", line: 1, column: 1 }
|
||||
}
|
||||
|
||||
fn generated_document(values: &[String]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |source| GENERATED_DOCUMENT(source).map_err(document_fault))
|
||||
}
|
||||
|
||||
fn reference_document(values: &[String]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |source| {
|
||||
let value = document::parse_with_depth(source, document::DOCUMENT_MAX_DEPTH).map_err(document_fault)?;
|
||||
document::stringify_with_depth(&value, document::DOCUMENT_MAX_DEPTH).map_err(document_fault)
|
||||
})
|
||||
}
|
||||
|
||||
// 独立手写 Map 适配,解析/写出引擎仍共用;只衡量表示转换与生成层。
|
||||
type ReferenceNode = reference_writer::Node<String, Vec<i64>, Map<String, i64>>;
|
||||
|
||||
fn reference_map_document(values: &[String]) -> Result<String, Fault> {
|
||||
let Some(source) = values.first() else { return Ok(String::new()); };
|
||||
let parsed = document::parse_with_depth(source, document::DOCUMENT_MAX_DEPTH).map_err(document_fault)?;
|
||||
let mut nodes = indexmap::IndexMap::with_capacity(parsed.nodes.len());
|
||||
for (index, value) in parsed.nodes.into_iter().enumerate() {
|
||||
let value = match value {
|
||||
document::Value::Null => ReferenceNode::Null,
|
||||
document::Value::Boolean(value) => ReferenceNode::Boolean(value),
|
||||
document::Value::Number(value) => ReferenceNode::Number(value),
|
||||
document::Value::String(value) => ReferenceNode::String(value),
|
||||
document::Value::Array(values) => ReferenceNode::Array(values),
|
||||
document::Value::Object(values) => ReferenceNode::Object(values.map_or_else(|| Map::with_capacity(0), |values| *values)),
|
||||
};
|
||||
nodes.insert(index as i64, value);
|
||||
}
|
||||
reference_map_write(parsed.root as i64, &nodes)
|
||||
}
|
||||
|
||||
fn reference_map_write(root: i64, nodes: &indexmap::IndexMap<i64, ReferenceNode>) -> Result<String, Fault> {
|
||||
let root = usize::try_from(root).map_err(document_fault)?;
|
||||
if nodes.len() > document::MAX_VALUES { return Err(document_fault("too many nodes")); }
|
||||
let mut ordered = vec![None; nodes.len()];
|
||||
for (id, value) in nodes {
|
||||
if let Ok(index) = usize::try_from(*id) && let Some(slot) = ordered.get_mut(index) {
|
||||
*slot = Some(value);
|
||||
}
|
||||
}
|
||||
let ordered = ordered.into_iter().map(|value| value.ok_or_else(|| document_fault("missing node"))).collect::<Result<Vec<_>, _>>()?;
|
||||
reference_writer::stringify_nodes(root, ordered.len(), |index| {
|
||||
match ordered[index] {
|
||||
ReferenceNode::Null => reference_writer::Node::Null,
|
||||
ReferenceNode::Boolean(value) => reference_writer::Node::Boolean(*value),
|
||||
ReferenceNode::Number(value) => reference_writer::Node::Number(value.as_str()),
|
||||
ReferenceNode::String(value) => reference_writer::Node::String(value.as_str()),
|
||||
ReferenceNode::Array(values) => reference_writer::Node::Array(values.iter().copied()),
|
||||
ReferenceNode::Object(values) => reference_writer::Node::Object(values.iter().map(|(name, id)| (name.as_str(), *id))),
|
||||
}
|
||||
}, document::DOCUMENT_MAX_DEPTH).map_err(document_fault)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ReferenceFailure {
|
||||
Fault(Fault),
|
||||
Parse(i64, String, Fault),
|
||||
Write(i64, String, Fault),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ReferenceFailure {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Fault(fault) | Self::Parse(_, _, fault) | Self::Write(_, _, fault) => fault.fmt(formatter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reference_map_faults(values: &[String]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |source| reference_map_contract(source).map_err(document_fault))
|
||||
}
|
||||
|
||||
// 原参照继续保留;这里独立保留解析/输出错误的载荷与静态源位置。
|
||||
fn reference_map_contract(source: &str) -> Result<String, ReferenceFailure> {
|
||||
let parse_fault = &FaultInfo { message: "ParseResult.Failed", source: "document-reference", line: 1, column: 1 };
|
||||
let write_fault = &FaultInfo { message: "StringifyResult.Failed", source: "document-reference", line: 2, column: 1 };
|
||||
let fail = |node, message: &str| ReferenceFailure::Write(node, message.into(), write_fault);
|
||||
let parsed = document::parse_with_depth(source, document::DOCUMENT_MAX_DEPTH)
|
||||
.map_err(|error| ReferenceFailure::Parse(error.position as i64, error.message, parse_fault))?;
|
||||
let mut nodes = indexmap::IndexMap::with_capacity(parsed.nodes.len());
|
||||
for (index, value) in parsed.nodes.into_iter().enumerate() {
|
||||
let value = match value {
|
||||
document::Value::Null => ReferenceNode::Null,
|
||||
document::Value::Boolean(value) => ReferenceNode::Boolean(value),
|
||||
document::Value::Number(value) => ReferenceNode::Number(value),
|
||||
document::Value::String(value) => ReferenceNode::String(value),
|
||||
document::Value::Array(values) => ReferenceNode::Array(values),
|
||||
document::Value::Object(values) => ReferenceNode::Object(values.map_or_else(|| Map::with_capacity(0), |values| *values)),
|
||||
};
|
||||
nodes.insert(index as i64, value);
|
||||
}
|
||||
let root = parsed.root as i64;
|
||||
let root_index = usize::try_from(root).map_err(|_| fail(root, "negative JSON root"))?;
|
||||
if nodes.len() > document::MAX_VALUES { return Err(fail(root, "JSON node count exceeds 65536 values")); }
|
||||
let mut ordered = vec![None; nodes.len()];
|
||||
for (id, value) in &nodes {
|
||||
if let Ok(index) = usize::try_from(*id) && let Some(slot) = ordered.get_mut(index) {
|
||||
*slot = Some(value);
|
||||
}
|
||||
}
|
||||
let ordered = ordered.into_iter().enumerate().map(|(index, value)| value.ok_or_else(|| fail(index as i64, "JSON node IDs must be consecutive"))).collect::<Result<Vec<_>, _>>()?;
|
||||
reference_writer::stringify_nodes(root_index, ordered.len(), |index| {
|
||||
match ordered[index] {
|
||||
ReferenceNode::Null => reference_writer::Node::Null,
|
||||
ReferenceNode::Boolean(value) => reference_writer::Node::Boolean(*value),
|
||||
ReferenceNode::Number(value) => reference_writer::Node::Number(value.as_str()),
|
||||
ReferenceNode::String(value) => reference_writer::Node::String(value.as_str()),
|
||||
ReferenceNode::Array(values) => reference_writer::Node::Array(values.iter().copied()),
|
||||
ReferenceNode::Object(values) => reference_writer::Node::Object(values.iter().map(|(name, id)| (name.as_str(), *id))),
|
||||
}
|
||||
}, document::DOCUMENT_MAX_DEPTH).map_err(|error| ReferenceFailure::Write(error.node as i64, error.message, write_fault))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("case,sample,implementation,elapsed_ns,checksum");
|
||||
let text = "雪\\\"\n".repeat(256);
|
||||
let strings = dever_runtime::wire::json::Json::encode(&vec![text; 32]).unwrap().as_str().to_owned();
|
||||
let numbers = format!("[{}]", vec!["1234567890123456789012345678901234e-12"; 256].join(","));
|
||||
let mut nested = "1e9999".to_owned();
|
||||
for _ in 0..48 { nested = format!("{{\"value\":{nested}}}"); }
|
||||
let empty_objects = format!("[{}]", vec!["{}"; 128].join(","));
|
||||
let wide = format!("{{{}}}", (0..256).map(|index| format!("\"key{index}\":1e9999")).collect::<Vec<_>>().join(","));
|
||||
for (name, source) in [("document_text", strings), ("document_numbers", numbers), ("document_nested", nested), ("document_empty", "{}".into()), ("document_empty_objects", empty_objects), ("document_wide", wide)] {
|
||||
compare(name, std::slice::from_ref(&source), 100, generated_document, reference_document, |value| value.bytes().map(u64::from).sum());
|
||||
compare(&format!("{name}_map"), std::slice::from_ref(&source), 100, generated_document, reference_map_document, |value| value.bytes().map(u64::from).sum());
|
||||
compare(&format!("{name}_map_faults"), &[source], 100, generated_document, reference_map_faults, |value| value.bytes().map(u64::from).sum());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// 阶段诊断复用正式入口;shared 标签只测运行库,不是独立 Rust 引擎参照。
|
||||
// map 场景名保留历史编号,Rust 参照仍构造 Map;当前 Dever 直接拥有节点 List。
|
||||
struct PreparedDocument {
|
||||
generated: DOCUMENT_TYPE,
|
||||
reference: (i64, indexmap::IndexMap<i64, ReferenceNode>),
|
||||
raw: document::Document,
|
||||
}
|
||||
|
||||
fn reference_nodes(parsed: document::Document) -> (i64, indexmap::IndexMap<i64, ReferenceNode>) {
|
||||
let mut nodes = indexmap::IndexMap::with_capacity(parsed.nodes.len());
|
||||
for (index, value) in parsed.nodes.into_iter().enumerate() {
|
||||
let value = match value {
|
||||
document::Value::Null => ReferenceNode::Null,
|
||||
document::Value::Boolean(value) => ReferenceNode::Boolean(value),
|
||||
document::Value::Number(value) => ReferenceNode::Number(value),
|
||||
document::Value::String(value) => ReferenceNode::String(value),
|
||||
document::Value::Array(values) => ReferenceNode::Array(values),
|
||||
document::Value::Object(values) => ReferenceNode::Object(values.map_or_else(|| Map::with_capacity(0), |values| *values)),
|
||||
};
|
||||
nodes.insert(index as i64, value);
|
||||
}
|
||||
(parsed.root as i64, nodes)
|
||||
}
|
||||
|
||||
fn phase_raw_parse(values: &[String]) -> Result<usize, Fault> {
|
||||
values.first().map_or(Ok(0), |source| {
|
||||
document::parse_with_depth(source, document::DOCUMENT_MAX_DEPTH)
|
||||
.map(|parsed| black_box(parsed).nodes.len()).map_err(document_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn phase_generated_parse(values: &[String]) -> Result<usize, Fault> {
|
||||
values.first().map_or(Ok(0), |source| {
|
||||
GENERATED_PARSE(source).map(|parsed| black_box(parsed).field1.len()).map_err(document_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn phase_reference_parse(values: &[String]) -> Result<usize, Fault> {
|
||||
values.first().map_or(Ok(0), |source| {
|
||||
document::parse_with_depth(source, document::DOCUMENT_MAX_DEPTH)
|
||||
.map(|parsed| black_box(reference_nodes(parsed)).1.len()).map_err(document_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn phase_generated_write(values: &[PreparedDocument]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| GENERATED_WRITE(&value.generated).map_err(document_fault))
|
||||
}
|
||||
|
||||
fn phase_reference_write(values: &[PreparedDocument]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| reference_map_write(value.reference.0, &value.reference.1))
|
||||
}
|
||||
|
||||
fn phase_raw_write(values: &[PreparedDocument]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
document::stringify_with_depth(&value.raw, document::DOCUMENT_MAX_DEPTH).map_err(document_fault)
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum PhaseWriteFailure {
|
||||
Fault(Fault),
|
||||
Write(i64, String, Fault),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PhaseWriteFailure {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Fault(fault) | Self::Write(_, _, fault) => fault.fmt(formatter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn phase_contract_write(values: &[PreparedDocument]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| reference_contract_write(&value.generated).map_err(document_fault))
|
||||
}
|
||||
|
||||
fn phase_adapter_write(values: &[PreparedDocument]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| runtime_contract_write(&value.generated).map_err(document_fault))
|
||||
}
|
||||
|
||||
// 同一份节点直接调用当前运行库,只衡量生成适配,不作为独立 JSON 引擎参照。
|
||||
fn runtime_contract_write(value: &DOCUMENT_TYPE) -> Result<String, PhaseWriteFailure> {
|
||||
let origin = &FaultInfo { message: "StringifyResult.Failed", source: "document-reference", line: 1, column: 1 };
|
||||
let root = usize::try_from(value.field0).map_err(|_| {
|
||||
PhaseWriteFailure::Write(value.field0, "negative JSON root".into(), origin)
|
||||
})?;
|
||||
document::stringify_slice(root, &value.field1, document::DOCUMENT_MAX_DEPTH)
|
||||
.map_err(|error| PhaseWriteFailure::Write(error.node as i64, error.message, origin))
|
||||
}
|
||||
|
||||
// 同一实参及错误布局,只隔离生成层;解析和输出引擎仍共享。
|
||||
fn reference_contract_write(value: &DOCUMENT_TYPE) -> Result<String, PhaseWriteFailure> {
|
||||
let origin = &FaultInfo { message: "StringifyResult.Failed", source: "document-reference", line: 1, column: 1 };
|
||||
let fail = |node, message: &str| PhaseWriteFailure::Write(node, message.into(), origin);
|
||||
let root = usize::try_from(value.field0).map_err(|_| fail(value.field0, "negative JSON root"))?;
|
||||
reference_writer::stringify_nodes(root, value.field1.len(), |index| {
|
||||
match &value.field1[index] {
|
||||
document::Value::Null => reference_writer::Node::Null,
|
||||
document::Value::Boolean(value) => reference_writer::Node::Boolean(*value),
|
||||
document::Value::Number(value) => reference_writer::Node::Number(value.as_str()),
|
||||
document::Value::String(value) => reference_writer::Node::String(value.as_str()),
|
||||
document::Value::Array(values) => reference_writer::Node::Array(values.iter().copied()),
|
||||
document::Value::Object(values) => reference_writer::Node::Object(values.iter().flat_map(|values| values.iter()).map(|(name, id)| (name.as_str(), *id))),
|
||||
}
|
||||
}, document::DOCUMENT_MAX_DEPTH).map_err(|error| PhaseWriteFailure::Write(error.node as i64, error.message, origin))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("case,sample,implementation,elapsed_ns,checksum");
|
||||
let numbers = format!("[{}]", vec!["1234567890123456789012345678901234e-12"; 256].join(","));
|
||||
let mut nested = "1e9999".to_owned();
|
||||
for _ in 0..48 { nested = format!("{{\"value\":{nested}}}"); }
|
||||
let text = dever_runtime::wire::json::Json::encode(&vec!["雪\\\"\n".repeat(256); 32]).unwrap().as_str().to_owned();
|
||||
let ascii = dever_runtime::wire::serde_json::to_string(&vec!["abcdef".repeat(256); 32]).unwrap();
|
||||
let unicode = dever_runtime::wire::serde_json::to_string(&vec!["雪🙂".repeat(256); 32]).unwrap();
|
||||
for (name, source) in [("numbers", numbers), ("nested", nested), ("text", text), ("ascii", ascii), ("unicode", unicode)] {
|
||||
let prepared = PreparedDocument {
|
||||
generated: GENERATED_PARSE(&source).unwrap(),
|
||||
reference: reference_nodes(document::parse_with_depth(&source, document::DOCUMENT_MAX_DEPTH).unwrap()),
|
||||
raw: document::parse_with_depth(&source, document::DOCUMENT_MAX_DEPTH).unwrap(),
|
||||
};
|
||||
let inputs = std::slice::from_ref(&source);
|
||||
compare_named(&format!("{name}_parse_shared"), inputs, 100, phase_raw_parse, phase_raw_parse, |count| *count as u64, "shared");
|
||||
compare(&format!("{name}_parse_map"), inputs, 100, phase_generated_parse, phase_reference_parse, |count| *count as u64);
|
||||
let values = &[prepared];
|
||||
compare_named(&format!("{name}_write_shared"), values, 100, phase_raw_write, phase_raw_write, |value| value.bytes().map(u64::from).sum(), "shared");
|
||||
compare(&format!("{name}_write_map"), values, 100, phase_generated_write, phase_reference_write, |value| value.bytes().map(u64::from).sum());
|
||||
compare(&format!("{name}_write_map_contract"), values, 100, phase_generated_write, phase_contract_write, |value| value.bytes().map(u64::from).sum());
|
||||
compare_named(&format!("{name}_write_adapter"), values, 100, phase_generated_write, phase_adapter_write, |value| value.bytes().map(u64::from).sum(), "shared_engine");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// 两侧都接收已验证的 JSON,返回独立拥有的原始子树;不在计时内重复验证输入。
|
||||
// 独立参照只使用 Serde,不调用 Dever 的键比较或字段读取实现。
|
||||
use dever_runtime::wire::serde::{
|
||||
Deserializer,
|
||||
de::{DeserializeSeed, IgnoredAny, MapAccess, Visitor},
|
||||
};
|
||||
use dever_runtime::wire::serde_json::{self, value::RawValue};
|
||||
|
||||
struct PropertyCase {
|
||||
source: Json,
|
||||
name: String,
|
||||
}
|
||||
|
||||
struct MatchingKey<'name>(&'name str);
|
||||
|
||||
impl<'input> DeserializeSeed<'input> for MatchingKey<'_> {
|
||||
type Value = bool;
|
||||
|
||||
fn deserialize<Decoder: Deserializer<'input>>(
|
||||
self,
|
||||
decoder: Decoder,
|
||||
) -> Result<bool, Decoder::Error> {
|
||||
decoder.deserialize_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'input> Visitor<'input> for MatchingKey<'_> {
|
||||
type Value = bool;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter.write_str("an object key")
|
||||
}
|
||||
|
||||
fn visit_str<Failure: dever_runtime::wire::serde::de::Error>(
|
||||
self,
|
||||
value: &str,
|
||||
) -> Result<bool, Failure> {
|
||||
Ok(value == self.0)
|
||||
}
|
||||
}
|
||||
|
||||
struct MatchingProperty<'name>(&'name str);
|
||||
|
||||
impl<'input> Visitor<'input> for MatchingProperty<'_> {
|
||||
type Value = Option<&'input RawValue>;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter.write_str("an object")
|
||||
}
|
||||
|
||||
fn visit_map<Access: MapAccess<'input>>(
|
||||
self,
|
||||
mut fields: Access,
|
||||
) -> Result<Self::Value, Access::Error> {
|
||||
let mut result = None;
|
||||
while let Some(matches) = fields.next_key_seed(MatchingKey(self.0))? {
|
||||
if matches {
|
||||
result = Some(fields.next_value()?);
|
||||
} else {
|
||||
fields.next_value::<IgnoredAny>()?;
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime_property(values: &[PropertyCase]) -> Result<Option<String>, Fault> {
|
||||
let Some(value) = values.first() else {
|
||||
return Ok(None);
|
||||
};
|
||||
value
|
||||
.source
|
||||
.property(&value.name)
|
||||
.map(|found| found.map(Json::into_text))
|
||||
.map_err(json_fault)
|
||||
}
|
||||
|
||||
fn native_property(values: &[PropertyCase]) -> Result<Option<String>, Fault> {
|
||||
let Some(value) = values.first() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let mut decoder =
|
||||
dever_runtime::wire::serde_json::Deserializer::from_str(value.source.as_str());
|
||||
decoder
|
||||
.deserialize_map(MatchingProperty(&value.name))
|
||||
.map(|found| found.map(|raw| raw.get().to_owned()))
|
||||
.map_err(json_fault)
|
||||
}
|
||||
|
||||
fn property_costs() {
|
||||
for (kind, suffix) in [("plain", ""), ("escaped", "雪\\\"\n")] {
|
||||
let names = (0..256)
|
||||
.map(|index| format!("key_{index:03}_{suffix}"))
|
||||
.collect::<Vec<_>>();
|
||||
let fields = names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
format!(
|
||||
"{}: [ 1e9999, -0, \"\\u96ea\" ]",
|
||||
serde_json::to_string(name).unwrap()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let source = format!("{{{}}}", fields.join(","));
|
||||
for (position, name) in [
|
||||
("first", names[0].as_str()),
|
||||
("middle", names[128].as_str()),
|
||||
("last", names[255].as_str()),
|
||||
("missing", "absent"),
|
||||
] {
|
||||
let values = [PropertyCase {
|
||||
source: Json::parse(&source).unwrap(),
|
||||
name: name.into(),
|
||||
}];
|
||||
let expected =
|
||||
(position != "missing").then(|| r#"[ 1e9999, -0, "\u96ea" ]"#.to_owned());
|
||||
assert_eq!(runtime_property(&values).unwrap(), expected);
|
||||
assert_eq!(native_property(&values).unwrap(), expected);
|
||||
compare_named(
|
||||
&format!("json_property_{kind}_{position}"),
|
||||
&values,
|
||||
1000,
|
||||
runtime_property,
|
||||
native_property,
|
||||
|value| value.as_ref().map_or(0, |text| text.len() as u64),
|
||||
"rust_serde",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
// 两侧从已验证的原始值开始,独立参照直接序列化 Serde RawValue。
|
||||
// 此组只比较原文输出;构造与更新成本由现有 json 组同时检查。
|
||||
struct RawOutputCase {
|
||||
dever: Json,
|
||||
rust: Box<RawValue>,
|
||||
values: usize,
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
fn runtime_raw_output(values: &[RawOutputCase]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
Ok(value.dever.as_str().trim_ascii().to_owned())
|
||||
})
|
||||
}
|
||||
|
||||
fn native_raw_output(values: &[RawOutputCase]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
serde_json::to_string(&value.rust).map_err(json_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn runtime_nested_output(values: &[RawOutputCase]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
Json::array(std::slice::from_ref(&value.dever)).map(Json::into_text).map_err(json_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn native_nested_output(values: &[RawOutputCase]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
if value.values + 1 > 65_536 || value.depth + 1 > 64 || value.rust.get().len() + 2 > 16 * 1024 * 1024 {
|
||||
return Err(json_fault("JSON budget"));
|
||||
}
|
||||
serde_json::to_string(&[&value.rust]).map_err(json_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn generated_nested_array_output(values: &[RawOutputCase]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
GENERATED_ARRAY(std::slice::from_ref(&value.dever)).map_err(json_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn runtime_from_text(values: &[String]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
Json::parse(value).map(Json::into_text).map_err(json_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn native_from_text(values: &[String]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
serde_json::from_str::<Box<RawValue>>(value)
|
||||
.map(|raw| Box::<str>::from(raw).into()).map_err(json_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn runtime_from_text_nested(values: &[String]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
let raw = Json::parse(value).map_err(json_fault)?;
|
||||
Json::array(&[raw]).map(Json::into_text).map_err(json_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn native_from_text_nested(values: &[String]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
let raw: Box<RawValue> = serde_json::from_str(value).map_err(json_fault)?;
|
||||
serde_json::to_string(&[raw]).map_err(json_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn from_text_costs(name: &str, source: &str, values: &[RawOutputCase]) {
|
||||
let count = if name == "short" { 100_000 } else { 1000 };
|
||||
compare_named(
|
||||
&format!("json_nested_output_{name}"), values, count,
|
||||
runtime_nested_output, native_nested_output,
|
||||
|value| value.len() as u64, "rust_serde",
|
||||
);
|
||||
compare_named(
|
||||
&format!("json_dever_array_output_{name}"), values, count,
|
||||
generated_nested_array_output, native_nested_output,
|
||||
|value| value.len() as u64, "rust_serde",
|
||||
);
|
||||
// 原生参照仅检查 JSON 语法,不包含 Dever 的去重和预算,不能据此宣称合同追平。
|
||||
let sources = [source.to_owned()];
|
||||
compare_named(
|
||||
&format!("json_from_text_{name}"), &sources, count,
|
||||
runtime_from_text, native_from_text,
|
||||
|value| value.len() as u64, "rust_syntax_only",
|
||||
);
|
||||
compare_named(
|
||||
&format!("json_from_text_nested_{name}"), &sources, count,
|
||||
runtime_from_text_nested, native_from_text_nested,
|
||||
|value| value.len() as u64, "rust_syntax_only",
|
||||
);
|
||||
}
|
||||
|
||||
fn raw_output_costs() {
|
||||
nested_output_costs();
|
||||
array_output_costs();
|
||||
for (name, text) in [
|
||||
("short", "a".repeat(64)),
|
||||
("plain_4096", "a".repeat(4096)),
|
||||
("plain_65536", "a".repeat(65536)),
|
||||
("escaped", "雪\"\\\n".repeat(1024)),
|
||||
] {
|
||||
let source = format!(
|
||||
"{{\"text\":{},\"number\":1e9999}}",
|
||||
serde_json::to_string(&text).unwrap()
|
||||
);
|
||||
let values = [RawOutputCase {
|
||||
dever: Json::parse(&source).unwrap(),
|
||||
rust: serde_json::from_str(&source).unwrap(),
|
||||
// 上面构造的对象恰好包含 text、number 两个标量字段。
|
||||
values: 3,
|
||||
depth: 1,
|
||||
}];
|
||||
assert_eq!(runtime_raw_output(&values).unwrap(), source);
|
||||
assert_eq!(native_raw_output(&values).unwrap(), source);
|
||||
from_text_costs(name, &source, &values);
|
||||
compare_named(
|
||||
&format!("json_raw_output_{name}"),
|
||||
&values,
|
||||
1000,
|
||||
runtime_raw_output,
|
||||
native_raw_output,
|
||||
|value| value.len() as u64,
|
||||
"rust_serde",
|
||||
);
|
||||
// 输入解码已经取得 RawValue;输出不应丢掉它后再从 String 解析。
|
||||
let decoded = [RawOutputCase {
|
||||
dever: serde_json::from_str(&source).unwrap(),
|
||||
rust: serde_json::from_str(&source).unwrap(),
|
||||
values: 3,
|
||||
depth: 1,
|
||||
}];
|
||||
compare_named(
|
||||
&format!("json_decoded_output_{name}"),
|
||||
&decoded,
|
||||
1000,
|
||||
runtime_raw_output,
|
||||
native_raw_output,
|
||||
|value| value.len() as u64,
|
||||
"rust_serde",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
struct ArrayOutputCase {
|
||||
dever: Vec<Json>,
|
||||
rust: Vec<NativeArrayValue>,
|
||||
}
|
||||
|
||||
struct NativeArrayValue {
|
||||
raw: Box<RawValue>,
|
||||
values: usize,
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
struct NativeArray<'value>(&'value [NativeArrayValue]);
|
||||
|
||||
impl Serialize for NativeArray<'_> {
|
||||
fn serialize<Encoder: Serializer>(&self, encoder: Encoder) -> Result<Encoder::Ok, Encoder::Error> {
|
||||
use dever_runtime::wire::serde::ser::{Error, SerializeSeq};
|
||||
let mut sequence = encoder.serialize_seq(Some(self.0.len()))?;
|
||||
let mut count = 1;
|
||||
let mut bytes = 2;
|
||||
for (index, value) in self.0.iter().enumerate() {
|
||||
count += value.values;
|
||||
bytes += value.raw.get().len() + usize::from(index != 0);
|
||||
if count > 65_536 || value.depth + 1 > 64 || bytes > 16 * 1024 * 1024 {
|
||||
return Err(Encoder::Error::custom("JSON budget"));
|
||||
}
|
||||
sequence.serialize_element(&value.raw)?;
|
||||
}
|
||||
sequence.end()
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime_array_output(values: &[ArrayOutputCase]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
Json::array(&value.dever).map(Json::into_text).map_err(json_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn native_array_output(values: &[ArrayOutputCase]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
serde_json::to_string(&NativeArray(&value.rust)).map_err(json_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn generated_array_output(values: &[ArrayOutputCase]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| {
|
||||
GENERATED_ARRAY(&value.dever).map_err(json_fault)
|
||||
})
|
||||
}
|
||||
|
||||
fn array_output_costs() {
|
||||
for count in [0, 1, 4, 16] {
|
||||
let sources: Vec<_> = [" null ", " [1,-0] ", r#" {"雪":"\u96ea"} "#, " [] "]
|
||||
.into_iter().cycle().take(count).collect();
|
||||
// 两侧每个子值都已验证且保留结构统计;计时包含合并预算和完整写出。
|
||||
let values = [ArrayOutputCase {
|
||||
dever: sources.iter().map(|source| Json::parse(source).unwrap()).collect(),
|
||||
rust: sources.iter().map(|source| {
|
||||
let (values, depth) = reference_shape(&serde_json::from_str(source).unwrap());
|
||||
NativeArrayValue { raw: serde_json::from_str(source).unwrap(), values, depth }
|
||||
}).collect(),
|
||||
}];
|
||||
compare_named(&format!("json_array_mixed_{count}"), &values, 100_000,
|
||||
runtime_array_output, native_array_output, |value| value.len() as u64, "rust_bounded");
|
||||
compare_named(&format!("json_dever_array_mixed_{count}"), &values, 100_000,
|
||||
generated_array_output, native_array_output, |value| value.len() as u64, "rust_bounded");
|
||||
}
|
||||
}
|
||||
|
||||
struct NestedCase {
|
||||
dever: Json,
|
||||
rust: Box<RawValue>,
|
||||
values: usize,
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
fn runtime_nested_record(values: &[NestedCase]) -> Result<String, Fault> {
|
||||
values.first().map_or(Ok(String::new()), |value| GENERATED_NESTED(&value.dever).map_err(json_fault))
|
||||
}
|
||||
|
||||
fn native_nested_record(values: &[NestedCase]) -> Result<String, Fault> {
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "dever_runtime::wire::serde")]
|
||||
struct Nested<'value> { value: &'value RawValue }
|
||||
let Some(value) = values.first() else { return Ok(String::new()); };
|
||||
if value.values + 1 > 65_536 || value.depth + 1 > 64 || value.rust.get().len() + 10 > 16 * 1024 * 1024 {
|
||||
return Err(json_fault("JSON budget"));
|
||||
}
|
||||
serde_json::to_string(&Nested { value: &value.rust }).map_err(json_fault)
|
||||
}
|
||||
|
||||
fn reference_shape(value: &serde_json::Value) -> (usize, usize) {
|
||||
let children: Vec<_> = match value {
|
||||
serde_json::Value::Array(values) => values.iter().map(reference_shape).collect(),
|
||||
serde_json::Value::Object(values) => values.values().map(reference_shape).collect(),
|
||||
_ => return (1, 0),
|
||||
};
|
||||
(1 + children.iter().map(|child| child.0).sum::<usize>(), 1 + children.iter().map(|child| child.1).max().unwrap_or(0))
|
||||
}
|
||||
|
||||
fn nested_output_costs() {
|
||||
for (name, text) in [
|
||||
("short", "a".repeat(64)),
|
||||
("plain_4096", "a".repeat(4096)),
|
||||
("plain_65536", "a".repeat(65536)),
|
||||
("escaped", "雪\"\\\n".repeat(1024)),
|
||||
] {
|
||||
let source = format!("{{\"text\":{},\"number\":-0}}", serde_json::to_string(&text).unwrap());
|
||||
// 两侧都从已验证原文及结构信息开始;独立 Rust 不调用 Dever 的验证或输出。
|
||||
let (count, depth) = reference_shape(&serde_json::from_str(&source).unwrap());
|
||||
let values = [NestedCase {
|
||||
dever: Json::parse(&source).unwrap(),
|
||||
rust: serde_json::from_str(&source).unwrap(), values: count, depth,
|
||||
}];
|
||||
compare_named(&format!("json_dever_nested_{name}"), &values,
|
||||
if name == "short" { 100_000 } else { 1000 },
|
||||
runtime_nested_record, native_nested_record, |value| value.len() as u64, "rust_bounded");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
//! 冻结原写出算法供前后对照;生产运行库不依赖此通用节点适配。
|
||||
use dever_runtime::wire::document::{MAX_BYTES, MAX_VALUES, WriteError};
|
||||
use dever_runtime::wire::serde_json;
|
||||
|
||||
pub enum Node<Text, Children, Members> {
|
||||
Null,
|
||||
Boolean(bool),
|
||||
Number(Text),
|
||||
String(Text),
|
||||
Array(Children),
|
||||
Object(Members),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Size {
|
||||
bytes: usize,
|
||||
values: usize,
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
/// 节点直接按下标借用,编号和文档结构在 measure 中检查;不再给取节点包装错误通道。
|
||||
pub fn stringify_nodes<'node, Children, Members>(
|
||||
root: usize,
|
||||
count: usize,
|
||||
node: impl Fn(usize) -> Node<&'node str, Children, Members>,
|
||||
max_depth: usize,
|
||||
) -> Result<String, WriteError>
|
||||
where
|
||||
Children: Iterator<Item = i64>,
|
||||
Members: Iterator<Item = (&'node str, i64)>,
|
||||
{
|
||||
let bytes = measure(root, count, &node, max_depth)?;
|
||||
let mut output = String::with_capacity(bytes);
|
||||
write_node(&node, root, &mut output);
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn measure<'node, Children, Members>(
|
||||
root: usize,
|
||||
count: usize,
|
||||
node: &impl Fn(usize) -> Node<&'node str, Children, Members>,
|
||||
max_depth: usize,
|
||||
) -> Result<usize, WriteError>
|
||||
where
|
||||
Children: Iterator<Item = i64>,
|
||||
Members: Iterator<Item = (&'node str, i64)>,
|
||||
{
|
||||
let fail = |node, message: &str| WriteError {
|
||||
node,
|
||||
message: message.into(),
|
||||
};
|
||||
if count > MAX_VALUES {
|
||||
return Err(fail(root, "JSON node count exceeds 65536 values"));
|
||||
}
|
||||
if root >= count {
|
||||
return Err(fail(root, "JSON root does not refer to a document node"));
|
||||
}
|
||||
let mut sizes = Vec::<Size>::with_capacity(count);
|
||||
for index in 0..count {
|
||||
let mut size = Size {
|
||||
bytes: 0,
|
||||
values: 1,
|
||||
depth: 0,
|
||||
};
|
||||
match node(index) {
|
||||
Node::Null => size.bytes = 4,
|
||||
Node::Boolean(value) => size.bytes = if value { 4 } else { 5 },
|
||||
Node::Number(raw) => {
|
||||
if !matches!(raw.as_bytes().first(), Some(b'-' | b'0'..=b'9'))
|
||||
|| !serde_json::from_str::<&serde_json::value::RawValue>(raw)
|
||||
.is_ok_and(|value| value.get() == raw)
|
||||
{
|
||||
return Err(fail(index, "invalid JSON number"));
|
||||
}
|
||||
size.bytes = raw.len();
|
||||
}
|
||||
Node::String(text) => size.bytes = string_size(text),
|
||||
Node::Array(children) => {
|
||||
size.bytes = 2;
|
||||
size.depth = 1;
|
||||
for (position, child) in children.enumerate() {
|
||||
size.bytes += usize::from(position != 0);
|
||||
add_child(&mut size, child, &sizes, index, max_depth)?;
|
||||
}
|
||||
}
|
||||
Node::Object(fields) => {
|
||||
size.bytes = 2;
|
||||
size.depth = 1;
|
||||
for (position, (name, child)) in fields.enumerate() {
|
||||
size.bytes += string_size(name) + 1 + usize::from(position != 0);
|
||||
add_child(&mut size, child, &sizes, index, max_depth)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
check(size, index, max_depth)?;
|
||||
sizes.push(size);
|
||||
}
|
||||
Ok(sizes[root].bytes)
|
||||
}
|
||||
|
||||
fn add_child(
|
||||
size: &mut Size,
|
||||
child: i64,
|
||||
sizes: &[Size],
|
||||
node: usize,
|
||||
max_depth: usize,
|
||||
) -> Result<(), WriteError> {
|
||||
let child = usize::try_from(child).map_err(|_| WriteError {
|
||||
node,
|
||||
message: "negative JSON node ID".into(),
|
||||
})?;
|
||||
let child = sizes.get(child).ok_or_else(|| WriteError {
|
||||
node,
|
||||
message: "JSON child must precede parent".into(),
|
||||
})?;
|
||||
size.bytes += child.bytes;
|
||||
size.values += child.values;
|
||||
size.depth = size.depth.max(child.depth + 1);
|
||||
check(*size, node, max_depth)
|
||||
}
|
||||
|
||||
fn check(size: Size, node: usize, max_depth: usize) -> Result<(), WriteError> {
|
||||
let message = if size.bytes > MAX_BYTES {
|
||||
"JSON exceeds 16 MiB"
|
||||
} else if size.values > MAX_VALUES {
|
||||
"JSON contains more than 65536 values"
|
||||
} else if size.depth > max_depth {
|
||||
"JSON nesting exceeds configured limit"
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
Err(WriteError {
|
||||
node,
|
||||
message: message.into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn string_size(text: &str) -> usize {
|
||||
text.len()
|
||||
+ 2
|
||||
+ text
|
||||
.bytes()
|
||||
.map(|byte| match byte {
|
||||
b'"' | b'\\' | 8 | 12 | b'\n' | b'\r' | b'\t' => 1,
|
||||
0..=31 => 5,
|
||||
_ => 0,
|
||||
})
|
||||
.sum::<usize>()
|
||||
}
|
||||
|
||||
fn write_node<'node, Children, Members>(
|
||||
node: &impl Fn(usize) -> Node<&'node str, Children, Members>,
|
||||
index: usize,
|
||||
output: &mut String,
|
||||
) where
|
||||
Children: Iterator<Item = i64>,
|
||||
Members: Iterator<Item = (&'node str, i64)>,
|
||||
{
|
||||
match node(index) {
|
||||
Node::Null => output.push_str("null"),
|
||||
Node::Boolean(value) => output.push_str(if value { "true" } else { "false" }),
|
||||
Node::Number(raw) => output.push_str(raw),
|
||||
Node::String(text) => write_string(text, output),
|
||||
Node::Array(children) => {
|
||||
output.push('[');
|
||||
for (position, child) in children.enumerate() {
|
||||
if position != 0 {
|
||||
output.push(',');
|
||||
}
|
||||
write_node(node, child as usize, output);
|
||||
}
|
||||
output.push(']');
|
||||
}
|
||||
Node::Object(fields) => {
|
||||
output.push('{');
|
||||
for (position, (name, child)) in fields.enumerate() {
|
||||
if position != 0 {
|
||||
output.push(',');
|
||||
}
|
||||
write_string(name, output);
|
||||
output.push(':');
|
||||
write_node(node, child as usize, output);
|
||||
}
|
||||
output.push('}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_string(text: &str, output: &mut String) {
|
||||
output.push('"');
|
||||
for character in text.chars() {
|
||||
match character {
|
||||
'"' => output.push_str("\\\""),
|
||||
'\\' => output.push_str("\\\\"),
|
||||
'\u{08}' => output.push_str("\\b"),
|
||||
'\u{0c}' => output.push_str("\\f"),
|
||||
'\n' => output.push_str("\\n"),
|
||||
'\r' => output.push_str("\\r"),
|
||||
'\t' => output.push_str("\\t"),
|
||||
character if character < '\u{20}' => {
|
||||
use std::fmt::Write;
|
||||
write!(output, "\\u{:04x}", character as u32).unwrap();
|
||||
}
|
||||
character => output.push(character),
|
||||
}
|
||||
}
|
||||
output.push('"');
|
||||
}
|
||||
Reference in new issue
Block a user