#![allow(mismatched_lifetime_syntaxes)]
mod builder;
mod node;
mod strategy;
use mimalloc::MiMalloc;
use rayon::prelude::*;
use serde_json::json;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
pub use builder::SchemaBuilder;
pub fn get_builder(schema_uri: Option<&str>) -> SchemaBuilder {
SchemaBuilder::new(schema_uri)
}
type Schema = serde_json::Value;
pub struct BuildConfig {
pub delimiter: Option<u8>,
pub ignore_outer_array: bool,
}
pub fn build_single_json_object_schema(builder: &mut SchemaBuilder, object_slice: &mut [u8]) {
let object = simd_json::to_borrowed_value(object_slice).unwrap();
builder.add_object(&object);
}
pub fn build_json_schema(
builder: &mut SchemaBuilder,
json_slice: &mut [u8],
config: &BuildConfig,
) -> Schema {
let json_slice = trim_to_object(json_slice);
if is_json_object_array(json_slice) {
let array_elements = get_json_array_elements(json_slice);
build_multi_json_objects_schema(builder, array_elements, None);
if config.ignore_outer_array {
builder.to_schema()
} else {
let mut inner_schema = builder.to_schema();
let schema_uri = inner_schema
.as_object_mut()
.unwrap()
.shift_remove("$schema");
if let Some(schema_uri) = schema_uri {
json!({"$schema": schema_uri, "type": "array", "items": inner_schema})
} else {
json!({"type": "array", "items": inner_schema})
}
}
} else {
build_multi_json_objects_schema(builder, json_slice, config.delimiter);
builder.to_schema()
}
}
fn build_multi_json_objects_schema(
builder: &mut SchemaBuilder,
json_slice: &mut [u8],
delimiter: Option<u8>,
) {
if let Some(delimiter) = delimiter {
let object_iter = json_slice.par_split_mut(|byte| *byte == delimiter);
add_schema_from_object_par_iter(object_iter, builder);
} else {
let mut structure_count = 0;
let object_separator = move |byte: &u8| -> bool {
if *byte == b'{' {
structure_count += 1;
} else if *byte == b'}' {
structure_count -= 1;
}
structure_count == 0 && *byte == b'}'
};
let mut object_slices: Vec<&mut [u8]> = vec![];
json_slice
.split_inclusive_mut(object_separator)
.for_each(|slice| object_slices.push(slice));
let object_iter = object_slices.into_par_iter();
add_schema_from_object_par_iter(object_iter, builder);
}
}
fn add_schema_from_object_par_iter<'a>(
object_iter: impl ParallelIterator<Item = &'a mut [u8]>,
builder: &mut SchemaBuilder,
) {
let combined_builder = object_iter
.fold(
|| SchemaBuilder::new(None),
|mut chunk_builder, object| {
let trimmed_object = trim_to_object(object);
if trimmed_object.is_empty() {
return chunk_builder;
}
build_single_json_object_schema(&mut chunk_builder, trimmed_object);
chunk_builder
},
)
.reduce_with(|mut builder1, builder2| {
builder1.add_schema(builder2.to_schema());
builder1
})
.unwrap_or(SchemaBuilder::new(None));
builder.add_schema(combined_builder.to_schema());
}
fn trim_to_object(data: &mut [u8]) -> &mut [u8] {
let start = data
.iter()
.position(|&c| !c.is_ascii_whitespace() && (c == b'{' || c == b'['))
.unwrap_or(data.len());
if start == data.len() {
return &mut data[start..];
}
let end = data
.iter()
.rev()
.position(|&c| !c.is_ascii_whitespace() && (c == b'}' || c == b']'))
.map_or(0, |p| data.len() - p);
&mut data[start..end]
}
fn is_json_object_array(data: &[u8]) -> bool {
data[0] == b'[' && data[data.len() - 1] == b']'
}
fn get_json_array_elements(data: &mut [u8]) -> &mut [u8] {
let start = data.iter().position(|&c| c == b'[').unwrap_or(data.len());
let end = data.iter().rposition(|&c| c == b']').map_or(0, |p| p);
&mut data[start + 1..end]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trim_whitespace_non_empty() {
let mut data = br#" {"name": "John", "age": 30} "#.to_vec();
let trimmed_data = trim_to_object(&mut data);
let expected_data = br#"{"name": "John", "age": 30}"#.to_vec();
assert_eq!(trimmed_data, expected_data);
}
#[test]
fn test_trim_whitespace_empty() {
let mut data = br#" "#.to_vec();
let trimmed_data = trim_to_object(&mut data);
let expected_data = br#""#.to_vec();
assert_eq!(trimmed_data, expected_data);
}
#[test]
fn test_trim_non_json_array_characters() {
let mut data = br#"\n , [1, 2, 3] \b\t"#.to_vec();
let trimmed_data = trim_to_object(&mut data);
let expected_data = br#"[1, 2, 3]"#.to_vec();
assert_eq!(trimmed_data, expected_data);
}
#[test]
fn test_trim_non_json_object_characters() {
let mut data = br#"\n , {"name": "John", "age": 30} \b\t"#.to_vec();
let trimmed_data = trim_to_object(&mut data);
let expected_data = br#"{"name": "John", "age": 30}"#.to_vec();
assert_eq!(trimmed_data, expected_data);
}
#[test]
fn test_is_json_object_array() {
let data = br#"[{"name": "John", "age": 30}]"#.to_vec();
assert!(is_json_object_array(&data));
}
#[test]
fn test_get_json_array_elements_single_elements() {
let mut data = br#"[{"name": "John", "age": 30}]"#.to_vec();
let elements = get_json_array_elements(&mut data);
let expected_elements = br#"{"name": "John", "age": 30}"#.to_vec();
assert_eq!(elements, expected_elements);
}
#[test]
fn test_get_json_array_elements_multiple_elements() {
let mut data = br#"[{"name": "John", "age": 30}, {"name": "Joe", "age": 20}]"#.to_vec();
let elements = get_json_array_elements(&mut data);
let expected_elements =
br#"{"name": "John", "age": 30}, {"name": "Joe", "age": 20}"#.to_vec();
assert_eq!(elements, expected_elements);
}
}