#![cfg(feature = "std")]
#![cfg(feature = "csv-validate")]
#![cfg(not(feature = "lsp"))]
use std::fmt::{self, Write};
use super::json;
#[cfg(not(target_arch = "wasm32"))]
use crate::cddl_from_str;
#[cfg(target_arch = "wasm32")]
use crate::{error::ErrorMsg, lexer::Position, parser, pest_bridge};
#[cfg(target_arch = "wasm32")]
use serde::Serialize;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
#[derive(Serialize)]
struct ParserError {
#[cfg(feature = "ast-span")]
position: Position,
msg: ErrorMsg,
}
pub type Result = std::result::Result<(), Error>;
#[derive(Debug)]
pub enum Error {
Validation(Vec<json::ValidationError>),
CSVParsing(csv::Error),
JSONSerialization(serde_json::Error),
CDDLParsing(String),
JSONValidation(json::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Validation(errors) => {
let mut error_str = String::new();
for e in errors.iter() {
let _ = writeln!(error_str, "{}", e);
}
write!(f, "{}", error_str)
}
Error::CSVParsing(error) => write!(f, "error parsing CSV: {}", error),
Error::JSONSerialization(error) => {
write!(f, "error converting CSV to JSON representation: {}", error)
}
Error::CDDLParsing(error) => write!(f, "error parsing CDDL: {}", error),
Error::JSONValidation(error) => write!(f, "{}", error),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::CSVParsing(error) => Some(error),
Error::JSONSerialization(error) => Some(error),
_ => None,
}
}
}
pub fn parse_csv_to_json(
csv_data: &str,
has_header: Option<bool>,
) -> std::result::Result<serde_json::Value, csv::Error> {
let has_header = has_header.unwrap_or(false);
let mut reader = csv::ReaderBuilder::new()
.has_headers(false)
.flexible(true)
.from_reader(csv_data.as_bytes());
let mut rows: Vec<serde_json::Value> = Vec::new();
for (row_idx, result) in reader.records().enumerate() {
let record = result?;
let fields: Vec<serde_json::Value> = record
.iter()
.map(|field| {
if has_header && row_idx == 0 {
return serde_json::Value::String(field.to_string());
}
coerce_field(field)
})
.collect();
rows.push(serde_json::Value::Array(fields));
}
Ok(serde_json::Value::Array(rows))
}
fn coerce_field(field: &str) -> serde_json::Value {
if field.is_empty() {
return serde_json::Value::String(String::new());
}
if let Ok(n) = field.parse::<u64>() {
return serde_json::json!(n);
}
if let Ok(n) = field.parse::<i64>() {
return serde_json::json!(n);
}
if let Ok(n) = field.parse::<f64>() {
if n.is_finite() {
return serde_json::json!(n);
}
}
serde_json::Value::String(field.to_string())
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "additional-controls")]
pub fn validate_csv_from_str(
cddl: &str,
csv_data: &str,
has_header: Option<bool>,
enabled_features: Option<&[&str]>,
) -> Result {
let cddl_ast = cddl_from_str(cddl, true).map_err(Error::CDDLParsing)?;
let json_value = parse_csv_to_json(csv_data, has_header).map_err(Error::CSVParsing)?;
let mut jv = json::JSONValidator::new(&cddl_ast, json_value, enabled_features);
use super::Validator;
jv.validate().map_err(Error::JSONValidation)
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "additional-controls"))]
pub fn validate_csv_from_str(cddl: &str, csv_data: &str, has_header: Option<bool>) -> Result {
let cddl_ast = cddl_from_str(cddl, true).map_err(Error::CDDLParsing)?;
let json_value = parse_csv_to_json(csv_data, has_header).map_err(Error::CSVParsing)?;
let mut jv = json::JSONValidator::new(&cddl_ast, json_value);
use super::Validator;
jv.validate().map_err(Error::JSONValidation)
}
#[cfg(target_arch = "wasm32")]
#[cfg(feature = "additional-controls")]
#[wasm_bindgen]
pub fn validate_csv_from_str(
cddl: &str,
csv_data: &str,
has_header: Option<bool>,
enabled_features: Option<Box<[JsValue]>>,
) -> std::result::Result<JsValue, JsValue> {
let c = pest_bridge::cddl_from_pest_str(cddl).map_err(|e| {
if let parser::Error::PARSER {
#[cfg(feature = "ast-span")]
position,
msg,
} = &e
{
let errors = vec![ParserError {
#[cfg(feature = "ast-span")]
position: *position,
msg: msg.clone(),
}];
serde_wasm_bindgen::to_value(&errors).unwrap_or_else(|e| JsValue::from(e.to_string()))
} else {
JsValue::from(e.to_string())
}
})?;
let json_value =
parse_csv_to_json(csv_data, has_header).map_err(|e| JsValue::from(e.to_string()))?;
let mut jv = json::JSONValidator::new(&c, json_value, enabled_features);
use super::Validator;
jv.validate()
.map_err(|e| JsValue::from(e.to_string()))
.map(|_| JsValue::default())
}
#[cfg(target_arch = "wasm32")]
#[cfg(not(feature = "additional-controls"))]
#[wasm_bindgen]
pub fn validate_csv_from_str(
cddl: &str,
csv_data: &str,
has_header: Option<bool>,
) -> std::result::Result<JsValue, JsValue> {
let c = pest_bridge::cddl_from_pest_str(cddl).map_err(|e| {
if let parser::Error::PARSER {
#[cfg(feature = "ast-span")]
position,
msg,
} = &e
{
let errors = vec![ParserError {
#[cfg(feature = "ast-span")]
position: *position,
msg: msg.clone(),
}];
serde_wasm_bindgen::to_value(&errors).unwrap_or_else(|e| JsValue::from(e.to_string()))
} else {
JsValue::from(e.to_string())
}
})?;
let json_value =
parse_csv_to_json(csv_data, has_header).map_err(|e| JsValue::from(e.to_string()))?;
let mut jv = json::JSONValidator::new(&c, json_value);
use super::Validator;
jv.validate()
.map_err(|e| JsValue::from(e.to_string()))
.map(|_| JsValue::default())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_coerce_field_empty() {
assert_eq!(coerce_field(""), serde_json::Value::String(String::new()));
}
#[test]
fn test_coerce_field_uint() {
assert_eq!(coerce_field("42"), serde_json::json!(42));
assert_eq!(coerce_field("0"), serde_json::json!(0));
assert_eq!(coerce_field("1700"), serde_json::json!(1700));
}
#[test]
fn test_coerce_field_negative_int() {
assert_eq!(coerce_field("-1"), serde_json::json!(-1));
assert_eq!(coerce_field("-100"), serde_json::json!(-100));
}
#[test]
fn test_coerce_field_float() {
assert_eq!(coerce_field("3.14"), serde_json::json!(3.14));
assert_eq!(coerce_field("-2.5"), serde_json::json!(-2.5));
}
#[test]
fn test_coerce_field_text() {
assert_eq!(
coerce_field("hello"),
serde_json::Value::String("hello".to_string())
);
assert_eq!(
coerce_field("ietf-system"),
serde_json::Value::String("ietf-system".to_string())
);
}
#[test]
fn test_parse_csv_no_header() {
let csv = "a,b,c\n1,2,3\n";
let result = parse_csv_to_json(csv, None).unwrap();
let expected = serde_json::json!([["a", "b", "c"], [1, 2, 3]]);
assert_eq!(result, expected);
}
#[test]
fn test_parse_csv_with_header() {
let csv = "name,value\nhello,42\n";
let result = parse_csv_to_json(csv, Some(true)).unwrap();
let expected = serde_json::json!([["name", "value"], ["hello", 42]]);
assert_eq!(result, expected);
}
#[test]
fn test_parse_csv_empty_fields() {
let csv = "a,,c\n";
let result = parse_csv_to_json(csv, None).unwrap();
let expected = serde_json::json!([["a", "", "c"]]);
assert_eq!(result, expected);
}
}