From f4cdecf33bb1927a123591066d847cfcf20f4734 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Tue, 12 Nov 2024 14:32:18 +0100 Subject: [PATCH] remove all winnow-related code --- Cargo.lock | 10 - core/Cargo.toml | 1 - core/src/parsers/common.rs | 179 ------- core/src/parsers/json.rs | 593 ------------------------ core/src/parsers/mod.rs | 4 - core/src/parsers/pyreport/chunks.rs | 14 +- core/src/parsers/pyreport/utils.rs | 12 +- core/src/report/models.rs | 6 +- core/src/report/pyreport/chunks.rs | 3 +- core/src/report/pyreport/report_json.rs | 3 +- core/src/report/pyreport/types.rs | 3 +- core/src/report/sqlite/models.rs | 6 +- 12 files changed, 22 insertions(+), 812 deletions(-) delete mode 100644 core/src/parsers/common.rs delete mode 100644 core/src/parsers/json.rs diff --git a/Cargo.lock b/Cargo.lock index cc1a718..0e0ff9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,7 +149,6 @@ dependencies = [ "tempfile", "test_utils", "thiserror", - "winnow", ] [[package]] @@ -1123,15 +1122,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - [[package]] name = "zerocopy" version = "0.7.35" diff --git a/core/Cargo.toml b/core/Cargo.toml index a422907..90d1a89 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -23,7 +23,6 @@ seahash = "4.1.0" serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" thiserror = "1.0.64" -winnow = "0.5.34" [dev-dependencies] criterion = { version = "2.7.2", package = "codspeed-criterion-compat" } diff --git a/core/src/parsers/common.rs b/core/src/parsers/common.rs deleted file mode 100644 index 8a5ba3c..0000000 --- a/core/src/parsers/common.rs +++ /dev/null @@ -1,179 +0,0 @@ -use std::{fmt, fmt::Debug, marker::PhantomData}; - -use crate::report::{Report, ReportBuilder}; - -#[derive(PartialEq)] -pub struct ReportBuilderCtx> { - pub report_builder: B, - _phantom: PhantomData, -} - -impl> ReportBuilderCtx { - pub fn new(report_builder: B) -> ReportBuilderCtx { - ReportBuilderCtx { - report_builder, - _phantom: PhantomData, - } - } -} - -impl> Debug for ReportBuilderCtx { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ReportBuilderCtx") - // .field("report_builder", &self.report_builder) - .finish() - } -} - -pub mod winnow { - use winnow::{ - ascii::float, - combinator::alt, - error::ParserError, - stream::{AsBStr, Compare, ParseSlice, Stream, StreamIsPartial}, - token::take_while, - PResult, Parser, - }; - - pub trait CharStream = Stream + StreamIsPartial; - pub trait StrStream = CharStream + for<'a> Compare<&'a str> + AsBStr - where - ::IterOffsets: Clone, - ::Slice: ParseSlice; - - /// Characters considered whitespace for the `ws` parser. - const WHITESPACE: &[char] = &[' ', '\t', '\n', '\r']; - - /// Parses a series of whitespace characters, returning the series as a - /// slice. - pub fn ws(buf: &mut S) -> PResult<::Slice> { - take_while(0.., WHITESPACE).parse_next(buf) - } - - /// Parses an unsigned decimal number with support for scientific notation. - /// Truncates floats, clamps numbers not in the `u32` range. - pub fn parse_u32(buf: &mut S) -> PResult { - float.map(move |x: f64| x as u32).parse_next(buf) - } - - /// Combinator that will match the passed-in parser or `null`. - /// - If the passed-in parser matches, return `Some(output)` - /// - Of `null` matches, return `None` - /// - Otherwise, backtrack or whatever - pub fn nullable( - parser: ParseNext, - ) -> impl Parser, Error> - where - ParseNext: Parser, - Error: ParserError, - Output: Clone, - { - alt((parser.map(Some), "null".value(None::))) - } - - #[cfg(test)] - mod tests { - use winnow::{ - ascii::{alpha1, dec_uint}, - error::{ContextError, ErrMode}, - }; - - use super::*; - - #[test] - fn test_ws() { - assert_eq!(ws.parse_peek(" \r\t\n"), Ok(("", " \r\t\n"))); - assert_eq!(ws.parse_peek(" asd"), Ok(("asd", " "))); - assert_eq!(ws.parse_peek("asd "), Ok(("asd ", ""))); - } - - #[test] - fn test_parse_u32() { - assert_eq!(parse_u32.parse_peek("30"), Ok(("", 30))); - assert_eq!(parse_u32.parse_peek("30 "), Ok((" ", 30))); - - // Floats are truncated, not rounded - assert_eq!(parse_u32.parse_peek("30.6 "), Ok((" ", 30))); - assert_eq!(parse_u32.parse_peek("30.1 "), Ok((" ", 30))); - - // Scientific notation - assert_eq!(parse_u32.parse_peek("1e+0"), Ok(("", 1))); - assert_eq!(parse_u32.parse_peek("5.2e+5"), Ok(("", 520000))); - assert_eq!(parse_u32.parse_peek("1.2345e+2"), Ok(("", 123))); - assert_eq!(parse_u32.parse_peek("2.7e-5"), Ok(("", 0))); - - // Numbers are clamped to `u32` range - assert_eq!(parse_u32.parse_peek("5000000000"), Ok(("", 4294967295))); - assert_eq!(parse_u32.parse_peek("2.7e+20"), Ok(("", 4294967295))); - assert_eq!(parse_u32.parse_peek("-1"), Ok(("", 0))); - assert_eq!(parse_u32.parse_peek("-100"), Ok(("", 0))); - assert_eq!(parse_u32.parse_peek("-4.2"), Ok(("", 0))); - assert_eq!(parse_u32.parse_peek("-4.2e-1"), Ok(("", 0))); - - // Malformed - assert_eq!( - parse_u32.parse_peek(" 30"), - Err(ErrMode::Backtrack(ContextError::new())) - ); - assert_eq!( - parse_u32.parse_peek("x30"), - Err(ErrMode::Backtrack(ContextError::new())) - ); - } - - #[test] - fn test_nullable() { - // with floats - assert_eq!( - nullable(float::<&str, f64, ContextError>).parse_peek("3.4"), - Ok(("", Some(3.4))) - ); - assert_eq!( - nullable(float::<&str, f64, ContextError>).parse_peek("null"), - Ok(("", None)) - ); - assert_eq!( - nullable(float::<&str, f64, ContextError>).parse_peek("malformed"), - Err(ErrMode::Backtrack(ContextError::new())), - ); - assert_eq!( - nullable(float::<&str, f64, ContextError>).parse_peek("nul"), - Err(ErrMode::Backtrack(ContextError::new())), - ); - - // with decimals - assert_eq!( - nullable(dec_uint::<&str, u64, ContextError>).parse_peek("3.4"), - Ok((".4", Some(3))) - ); - assert_eq!( - nullable(dec_uint::<&str, u64, ContextError>).parse_peek("null"), - Ok(("", None)) - ); - assert_eq!( - nullable(dec_uint::<&str, u64, ContextError>).parse_peek("malformed"), - Err(ErrMode::Backtrack(ContextError::new())), - ); - assert_eq!( - nullable(dec_uint::<&str, u64, ContextError>).parse_peek("nul"), - Err(ErrMode::Backtrack(ContextError::new())), - ); - - // with chars - assert_eq!( - nullable(alpha1::<&str, ContextError>).parse_peek("abcde"), - Ok(("", Some("abcde"))) - ); - // this is an edge case - `alpha1` has no problem matching `"null"` so we should - // let it - assert_eq!( - nullable(alpha1::<&str, ContextError>).parse_peek("null"), - Ok(("", Some("null"))) - ); - assert_eq!( - nullable(alpha1::<&str, ContextError>).parse_peek(".123."), - Err(ErrMode::Backtrack(ContextError::new())), - ); - } - } -} diff --git a/core/src/parsers/json.rs b/core/src/parsers/json.rs deleted file mode 100644 index f7ebf2b..0000000 --- a/core/src/parsers/json.rs +++ /dev/null @@ -1,593 +0,0 @@ -pub use serde_json::{ - value::{Map as JsonMap, Number as JsonNumber}, - Value as JsonVal, -}; -use winnow::{ - ascii::float, - combinator::{alt, delimited, opt, preceded, repeat, separated, separated_pair}, - error::{ContextError, ErrMode, ErrorKind, ParserError}, - stream::Stream, - token::none_of, - PResult, Parser, -}; - -use super::common::winnow::*; - -/* - * Parsers in this section return raw Rust types and may be useful to other - * parsers. - */ - -/// Parses the string "null", returning "null" as a slice. -pub fn parse_null(buf: &mut S) -> PResult<::Slice> { - "null".parse_next(buf) -} - -/// Parses the strings "true" and "false", returning the corresponding `bool`s. -pub fn parse_bool(buf: &mut S) -> PResult { - alt(("true".value(true), "false".value(false))).parse_next(buf) -} - -/// Parses numeric strings, returning the value as an f64. -/// Handles scientific notation. -pub fn parse_num(buf: &mut S) -> PResult { - float.verify_map(JsonNumber::from_f64).parse_next(buf) -} - -/// Parses a single character (which may be escaped), returning a `char`. -/// -/// ``` -/// # use codecov_rs::parsers::json::parse_char; -/// # use winnow::Parser; -/// assert_eq!(parse_char.parse_peek("a"), Ok(("", 'a'))); -/// assert_eq!(parse_char.parse_peek("\\n"), Ok(("", '\n'))); -/// ``` -/// -/// Consumes two characters if the first is a `\`. -pub fn parse_char(buf: &mut S) -> PResult { - let c = none_of('"').parse_next(buf); - match c { - Ok('\\') => { - let escaped = buf - .next_token() - .ok_or_else(|| ErrMode::from_error_kind(buf, ErrorKind::Token))?; - match escaped { - '"' | '\'' | '\\' => Ok(escaped), - 'n' => Ok('\n'), - 'r' => Ok('\r'), - 't' => Ok('\t'), - _ => panic!("Unrecognized escape: {}", escaped), - } - } - _ => c, - } -} - -/// Parses a series of characters between two `'"'` delimiters, returning a -/// `String`. -/// -/// Characters are parsed with `parse_char` and thus may be escaped. -pub fn parse_str(buf: &mut S) -> PResult { - delimited( - '"', - repeat(0.., parse_char).fold(String::new, |mut s, c| { - s.push(c); - s - }), - '"', - ) - .parse_next(buf) -} - -/* - * Parsers in this section return collections which may contain multiple - * types. They use the JsonVal enum to express that within Rust's type system - * and are thus json-specific. - */ - -/// Parses a series of json objects between `[]`s and separated by a comma, -/// returning a `Vec`. -pub fn parse_array(buf: &mut S) -> PResult> { - delimited(('[', ws), separated(0.., json_value, ','), (ws, ']')).parse_next(buf) -} - -/// Parses a key-value pair separated by a `:`, returning the key and value in a -/// tuple. -/// -/// The key is parsed with `parse_str` and the value is a `JsonVal`. -pub fn parse_kv(buf: &mut S) -> PResult<(String, JsonVal)> { - separated_pair(parse_str, (ws, ':', ws), json_value).parse_next(buf) -} - -/// Parses a series of key-value pairs separated by a ':' and surrounded by -/// `{}`s, returning a `Map`. -pub fn parse_object(buf: &mut S) -> PResult> { - // parse_kv.map(std::iter::once).map(serde_json::value::Map::from_iter). - // let start_map = parse_kv - // .map(std::iter::once) - // .map(serde_json::value::Map::from_iter); - let add_to_map = |mut m: JsonMap, (k, v)| { - m.insert(k, v); - m - }; - delimited( - ('{', ws), - repeat(0.., preceded(opt((ws, ',', ws)), parse_kv)).fold(JsonMap::new, add_to_map), - (ws, '}'), - ) - .parse_next(buf) -} - -/// Parses any json value, returning a `JsonVal`. -/// -/// Whitespace is stripped before/after valid json values. -pub fn json_value(buf: &mut S) -> PResult { - delimited( - ws, - alt(( - parse_null.value(JsonVal::Null), - parse_bool.map(JsonVal::Bool), - parse_num.map(JsonVal::Number), - parse_str.map(JsonVal::String), - parse_array.map(JsonVal::Array), - parse_object.map(JsonVal::Object), - )), - ws, - ) - .parse_next(buf) -} - -/// Parses the next key + `:` delimiter and asserts that the key matches the -/// passed-in value. To get the corresponding value, parse with something like: -/// -/// ``` -/// # use codecov_rs::parsers::json::{specific_key, json_value, JsonVal}; -/// # use winnow::combinator::preceded; -/// # use winnow::Parser; -/// let expected = Ok(("", JsonVal::Array(vec![]))); -/// let result = preceded(specific_key("files"), json_value).parse_peek("\"files\": []"); -/// assert_eq!(expected, result); -/// ``` -/// -/// Not used in generic json parsing but helpful when writing parsers for json -/// data that adheres to a schema. -pub fn specific_key(key: &str) -> impl Parser + '_ { - move |i: &mut S| { - delimited(ws, parse_str, (ws, ':', ws)) - .verify(move |s: &String| s == key) - .parse_next(i) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_null() { - // test that an exact match succeeds - assert_eq!(parse_null.parse_peek("null"), Ok(("", "null"))); - - // test that trailing whitespace is not consumed / that trailing - // characters don't fail - assert_eq!(parse_null.parse_peek("null "), Ok((" ", "null"))); - - let malformed_test_cases = [ - " null", // test that whitespace is not stripped - "anull", // test that unexpected leading tokens fail - ]; - for test_case in &malformed_test_cases { - assert_eq!( - parse_null.parse_peek(*test_case), - Err(ErrMode::Backtrack(ContextError::new())), - ); - } - } - - #[test] - fn test_parse_bool() { - // test that exact matches succeed - assert_eq!(parse_bool.parse_peek("true"), Ok(("", true))); - assert_eq!(parse_bool.parse_peek("false"), Ok(("", false))); - - // test that trailing whitespace is not consumed / that trailing - // characters don't fail - assert_eq!(parse_bool.parse_peek("true "), Ok((" ", true))); - assert_eq!(parse_bool.parse_peek("false "), Ok((" ", false))); - - let malformed_test_cases = [" true", " false", "atrue", "afalse"]; - for test_case in &malformed_test_cases { - assert_eq!( - parse_bool.parse_peek(*test_case), - Err(ErrMode::Backtrack(ContextError::new())), - ); - } - } - - #[test] - fn test_parse_num() { - let json_num = |f| JsonNumber::from_f64(f).unwrap(); - // integers - assert_eq!(parse_num.parse_peek("34949"), Ok(("", json_num(34949.0)))); - assert_eq!(parse_num.parse_peek("-34949"), Ok(("", json_num(-34949.0)))); - - // decimals - assert_eq!( - parse_num.parse_peek("404.0101"), - Ok(("", json_num(404.0101))) - ); - assert_eq!( - parse_num.parse_peek("-404.0101"), - Ok(("", json_num(-404.0101))) - ); - assert_eq!(parse_num.parse_peek(".05"), Ok(("", json_num(0.05)))); - assert_eq!(parse_num.parse_peek("-.05"), Ok(("", json_num(-0.05)))); - - // scientific notation - assert_eq!(parse_num.parse_peek("3.3e5"), Ok(("", json_num(330000.0)))); - assert_eq!(parse_num.parse_peek("3.3e+5"), Ok(("", json_num(330000.0)))); - assert_eq!(parse_num.parse_peek("3.3e-5"), Ok(("", json_num(0.000033)))); - assert_eq!( - parse_num.parse_peek("-3.3e5"), - Ok(("", json_num(-330000.0))) - ); - assert_eq!( - parse_num.parse_peek("-3.3e+5"), - Ok(("", json_num(-330000.0))) - ); - assert_eq!( - parse_num.parse_peek("-3.3e-5"), - Ok(("", json_num(-0.000033))) - ); - assert_eq!(parse_num.parse_peek("3.3E5"), Ok(("", json_num(330000.0)))); - assert_eq!(parse_num.parse_peek("3.3E+5"), Ok(("", json_num(330000.0)))); - assert_eq!(parse_num.parse_peek("3.3E-5"), Ok(("", json_num(0.000033)))); - assert_eq!( - parse_num.parse_peek("-3.3E5"), - Ok(("", json_num(-330000.0))) - ); - assert_eq!( - parse_num.parse_peek("-3.3E+5"), - Ok(("", json_num(-330000.0))) - ); - assert_eq!( - parse_num.parse_peek("-3.3E-5"), - Ok(("", json_num(-0.000033))) - ); - - // trailing input - assert_eq!( - parse_num.parse_peek("3.abcde"), - Ok(("abcde", json_num(3.0))) - ); - assert_eq!(parse_num.parse_peek("3..."), Ok(("..", json_num(3.0)))); - assert_eq!( - parse_num.parse_peek("3.455.303"), - Ok((".303", json_num(3.455))) - ); - - let malformed_test_cases = [".", "aajad3.405"]; - for test_case in &malformed_test_cases { - assert_eq!( - parse_num.parse_peek(*test_case), - Err(ErrMode::Backtrack(ContextError::new())), - ); - } - } - - #[test] - fn test_parse_char() { - assert_eq!(parse_char.parse_peek("a"), Ok(("", 'a'))); - - // escaping - assert_eq!(parse_char.parse_peek("\\n"), Ok(("", '\n'))); - assert_eq!(parse_char.parse_peek("\\r"), Ok(("", '\r'))); - assert_eq!(parse_char.parse_peek("\\t"), Ok(("", '\t'))); - assert_eq!(parse_char.parse_peek("\\\""), Ok(("", '"'))); - assert_eq!(parse_char.parse_peek("\\\'"), Ok(("", '\''))); - assert_eq!(parse_char.parse_peek("\\\\"), Ok(("", '\\'))); - - // pre-escaped characters - assert_eq!(parse_char.parse_peek("\n"), Ok(("", '\n'))); - assert_eq!(parse_char.parse_peek("\r"), Ok(("", '\r'))); - assert_eq!(parse_char.parse_peek("\t"), Ok(("", '\t'))); - assert_eq!(parse_char.parse_peek("'"), Ok(("", '\''))); - - // trailing input - assert_eq!(parse_char.parse_peek("abcde"), Ok(("bcde", 'a'))); - assert_eq!(parse_char.parse_peek("\\nbcde"), Ok(("bcde", '\n'))); - - // can't lead with " - assert_eq!( - parse_char.parse_peek("\""), - Err(ErrMode::Backtrack(ContextError::new())) - ); - } - - #[test] - fn test_parse_str() { - // normal cases - assert_eq!(parse_str.parse_peek("\"\""), Ok(("", "".to_string()))); - assert_eq!( - parse_str.parse_peek("\"hello world\""), - Ok(("", "hello world".to_string())) - ); - assert_eq!( - parse_str.parse_peek("\"string with\nnewline\""), - Ok(("", "string with\nnewline".to_string())) - ); - assert_eq!( - parse_str.parse_peek("\"string with\\nnewline\""), - Ok(("", "string with\nnewline".to_string())) - ); - assert_eq!( - parse_str.parse_peek("\"str with backslash \\\\\""), - Ok(("", "str with backslash \\".to_string())) - ); - assert_eq!( - parse_str.parse_peek("\"str with escaped quote \\\" \""), - Ok(("", "str with escaped quote \" ".to_string())) - ); - - // trailing input - assert_eq!( - parse_str.parse_peek("\"hello world\", asdjasd"), - Ok((", asdjasd", "hello world".to_string())) - ); - - // malformed - let malformed_test_cases = [ - "no surrounding quotes", - "\"no final quote", - "no beginning quote\"", - "\"str ending on escaped quote\\\"", - ]; - for test_case in &malformed_test_cases { - assert_eq!( - parse_str.parse_peek(*test_case), - Err(ErrMode::Backtrack(ContextError::new())), - ); - } - } - - #[test] - fn test_parse_array() { - assert_eq!(parse_array.parse_peek("[]"), Ok(("", vec![]))); - assert_eq!( - parse_array.parse_peek("[3, null, true, false, \"str\", [], {}]"), - Ok(( - "", - vec![ - JsonVal::Number(JsonNumber::from_f64(3.0).unwrap()), - JsonVal::Null, - JsonVal::Bool(true), - JsonVal::Bool(false), - JsonVal::String("str".to_string()), - JsonVal::Array(vec![]), - JsonVal::Object(JsonMap::new()), - ] - )) - ); - - // same test case as above but with superfluous whitespace peppered around - assert_eq!( - parse_array - .parse_peek("[ 3 ,null , true , \n\t\tfalse, \t \"str\", [\n], {\r \t \n} ]"), - Ok(( - "", - vec![ - JsonVal::Number(JsonNumber::from_f64(3.0).unwrap()), - JsonVal::Null, - JsonVal::Bool(true), - JsonVal::Bool(false), - JsonVal::String("str".to_string()), - JsonVal::Array(vec![]), - JsonVal::Object(JsonMap::new()), - ] - )) - ); - - // trailing input - assert_eq!(parse_array.parse_peek("[]abcde"), Ok(("abcde", vec![]))); - assert_eq!(parse_array.parse_peek("[]]"), Ok(("]", vec![]))); - - // malformed - let malformed_test_cases = [ - "[4", - "[4,]", - "4[]", - "[4, null, unquoted string]", - "[4, null, {\"a\": 4]", - "[4, null, [\"str\", false, true]", - ]; - for test_case in &malformed_test_cases { - assert_eq!( - parse_array.parse_peek(*test_case), - Err(ErrMode::Backtrack(ContextError::new())), - ); - } - } - - #[test] - fn test_parse_kv() { - assert_eq!( - parse_kv.parse_peek("\"key\": null"), - Ok(("", ("key".to_string(), JsonVal::Null))) - ); - assert_eq!( - parse_kv.parse_peek("\"key\": true"), - Ok(("", ("key".to_string(), JsonVal::Bool(true)))) - ); - assert_eq!( - parse_kv.parse_peek("\"key\": false"), - Ok(("", ("key".to_string(), JsonVal::Bool(false)))) - ); - assert_eq!( - parse_kv.parse_peek("\"key\": 4.4"), - Ok(( - "", - ( - "key".to_string(), - JsonVal::Number(JsonNumber::from_f64(4.4).unwrap()) - ) - )), - ); - assert_eq!( - parse_kv.parse_peek("\"key\": \"str value\""), - Ok(( - "", - ("key".to_string(), JsonVal::String("str value".to_string())) - )) - ); - assert_eq!( - parse_kv.parse_peek("\"key\": []"), - Ok(("", ("key".to_string(), JsonVal::Array(vec![])))) - ); - assert_eq!( - parse_kv.parse_peek("\"key\": {}"), - Ok(("", ("key".to_string(), JsonVal::Object(JsonMap::new())))) - ); - - // empty string as a key is fine - assert_eq!( - parse_kv.parse_peek("\"\": null"), - Ok(("", ("".to_string(), JsonVal::Null))) - ); - - // pepper superfluous whitespace around - assert_eq!( - parse_kv.parse_peek("\"key\"\n\t :\n \t null"), - Ok(("", ("key".to_string(), JsonVal::Null))) - ); - - // trailing input - assert_eq!( - parse_kv.parse_peek("\"key\": null, \"key2\": null"), - Ok((", \"key2\": null", ("key".to_string(), JsonVal::Null))) - ); - assert_eq!( - parse_kv.parse_peek("\"key\": null}"), - Ok(("}", ("key".to_string(), JsonVal::Null))) - ); - assert_eq!( - parse_kv.parse_peek("\"key\": null]"), - Ok(("]", ("key".to_string(), JsonVal::Null))) - ); - assert_eq!( - parse_kv.parse_peek("\"key\": nulla"), - Ok(("a", ("key".to_string(), JsonVal::Null))) - ); - - // malformed - let malformed_test_cases = [ - "key: null", - "\"key: null", - "\"key\": ", - "key\": null", - "\"key\"; null", - "key: null", - ]; - for test_case in &malformed_test_cases { - assert_eq!( - parse_kv.parse_peek(*test_case), - Err(ErrMode::Backtrack(ContextError::new())), - ); - } - } - - #[test] - fn test_parse_object() { - assert_eq!(parse_object.parse_peek("{}"), Ok(("", JsonMap::new()))); - assert_eq!( - parse_object.parse_peek("{\"key\": null}"), - Ok(("", JsonMap::from_iter([("key".to_string(), JsonVal::Null)]))) - ); - assert_eq!( - parse_object.parse_peek("{\"key\": null, \"key2\": null}"), - Ok(( - "", - JsonMap::from_iter([ - ("key".to_string(), JsonVal::Null), - ("key2".to_string(), JsonVal::Null) - ]) - )) - ); - assert_eq!( - parse_object.parse_peek("{ \"key\" \n \t:\t\n null\n}"), - Ok(("", JsonMap::from_iter([("key".to_string(), JsonVal::Null)]))) - ); - - // trailing input - assert_eq!( - parse_object.parse_peek("{}abcde"), - Ok(("abcde", JsonMap::new())) - ); - assert_eq!(parse_object.parse_peek("{}}"), Ok(("}", JsonMap::new()))); - assert_eq!(parse_object.parse_peek("{}]"), Ok(("]", JsonMap::new()))); - - // malformed - let malformed_test_cases = [ - "{\"key\": null,}", - "{\"key\": null", - "\"key\": null", - "key: null", - "{\"key\": }", - "{\"key\": , }", - "abcde {\"key\": null}", - ]; - for test_case in &malformed_test_cases { - assert_eq!( - parse_object.parse_peek(*test_case), - Err(ErrMode::Backtrack(ContextError::new())), - ); - } - } - - #[test] - fn test_json_value() { - let test_cases = [ - "null", - "true", - "false", - "3.404", - "\"test string\"", - "[]", - "{}", - " \n\r\tnull\n ", - "\n\r true\r ", - " \n false\n ", - "\n 3.404\t ", - "\n \"test string\"\n ", - "\r\r\n\t []\t \t\r ", - " \r {}\r\r\n", - "[null, true, false, 3.4, \"str\", [], {}]", - "{\"null\": null, \"true\": true, \"false\": false, \"num\": 3.4, \"str\": \"str\", \"array\": [null, 3.3], \"object\": {\"k\": 4.4}}", - ]; - - for test_case in &test_cases { - let expected = serde_json::from_str(test_case).unwrap(); - assert_eq!(json_value.parse_peek(*test_case), Ok(("", expected))); - } - } - - #[test] - fn test_specific_key() { - assert_eq!( - specific_key("files").parse_peek("\"files\": {\"src/report.rs"), - Ok(("{\"src/report.rs", "files".to_string())) - ); - - // malformed - let malformed_test_cases = [ - "files\": {\"src", - "\"files: {\"src", - "leading\"files\": {\"src", - ]; - for test_case in &malformed_test_cases { - assert_eq!( - specific_key("files").parse_peek(*test_case), - Err(ErrMode::Backtrack(ContextError::new())) - ); - } - } -} diff --git a/core/src/parsers/mod.rs b/core/src/parsers/mod.rs index 2987e90..a40d1bf 100644 --- a/core/src/parsers/mod.rs +++ b/core/src/parsers/mod.rs @@ -1,6 +1,2 @@ -pub mod json; - #[cfg(feature = "pyreport")] pub mod pyreport; - -pub mod common; diff --git a/core/src/parsers/pyreport/chunks.rs b/core/src/parsers/pyreport/chunks.rs index d4a8562..8edd8d3 100644 --- a/core/src/parsers/pyreport/chunks.rs +++ b/core/src/parsers/pyreport/chunks.rs @@ -34,7 +34,7 @@ //! `report_line_or_empty` parser which wraps this and supports empty lines //! returns `Ok(())`. -use std::{collections::HashMap, fmt, mem, sync::OnceLock}; +use std::{collections::HashMap, fmt, marker::PhantomData, mem, sync::OnceLock}; use memchr::{memchr, memmem}; use serde::{de, de::IgnoredAny, Deserialize}; @@ -42,7 +42,6 @@ use serde::{de, de::IgnoredAny, Deserialize}; use super::{report_json::ParsedReportJson, utils}; use crate::{ error::CodecovError, - parsers::common::ReportBuilderCtx, report::{ pyreport::{ types::{self, CoverageType, MissingBranch, Partial, PyreportCoverage, ReportLine}, @@ -67,7 +66,9 @@ pub struct ChunkCtx { pub struct ParseCtx> { /// Rather than returning parsed results, we write them to this /// `report_builder`. - pub db: ReportBuilderCtx, + pub report_builder: B, + // FIXME: Rust, you are drunk. We need `R`. + _phantom: PhantomData, /// Tracks the labels that we've already added to the report. The key is the /// identifier for the label inside the chunks file and the value is the @@ -100,7 +101,8 @@ impl> ParseCtx { ) -> ParseCtx { ParseCtx { labels_index: HashMap::new(), - db: ReportBuilderCtx::new(report_builder), + report_builder, + _phantom: PhantomData, chunk: ChunkCtx { index: 0, current_line: 0, @@ -114,7 +116,7 @@ impl> ParseCtx { impl> fmt::Debug for ParseCtx { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ParseCtx") - .field("db", &self.db) + .field("report_builder", &format_args!("...")) .field("labels_index", &self.labels_index) .field("chunk", &self.chunk) .finish() @@ -173,7 +175,7 @@ where for datapoint in datapoints.values() { for label in &datapoint.labels { if !ctx.labels_index.contains_key(label) { - let context = ctx.db.report_builder.insert_context(label)?; + let context = ctx.report_builder.insert_context(label)?; ctx.labels_index.insert(label.into(), context.id); } } diff --git a/core/src/parsers/pyreport/utils.rs b/core/src/parsers/pyreport/utils.rs index fb6e4bd..562c684 100644 --- a/core/src/parsers/pyreport/utils.rs +++ b/core/src/parsers/pyreport/utils.rs @@ -231,7 +231,7 @@ pub fn save_report_lines>( // assigned as a side-effect of this insertion. That lets us populate the // `local_sample_id` foreign key on all of the models associated with each // `CoverageSample`. - ctx.db.report_builder.multi_insert_coverage_sample( + ctx.report_builder.multi_insert_coverage_sample( models .iter_mut() .map(|LineSessionModels { sample, .. }| sample) @@ -240,7 +240,7 @@ pub fn save_report_lines>( // Populate `local_sample_id` and insert all of the context assocs for each // `LineSession` (if there are any) - ctx.db.report_builder.multi_associate_context( + ctx.report_builder.multi_associate_context( models .iter_mut() .flat_map(|LineSessionModels { sample, assocs, .. }| { @@ -254,7 +254,7 @@ pub fn save_report_lines>( // Populate `local_sample_id` and insert all of the `BranchesData` records for // each `LineSession` (if there are any) - ctx.db.report_builder.multi_insert_branches_data( + ctx.report_builder.multi_insert_branches_data( models .iter_mut() .flat_map( @@ -272,7 +272,7 @@ pub fn save_report_lines>( // Populate `local_sample_id` and insert the single `MethodData` record for each // `LineSession` (if there is one) - ctx.db.report_builder.multi_insert_method_data( + ctx.report_builder.multi_insert_method_data( models .iter_mut() .filter_map(|LineSessionModels { sample, method, .. }| { @@ -289,7 +289,7 @@ pub fn save_report_lines>( // Populate `local_sample_id` and insert all of the `SpanData` records for each // `LineSession` (if there are any). In a chunks file, only spans that are // subsets of a single line are recorded. - ctx.db.report_builder.multi_insert_span_data( + ctx.report_builder.multi_insert_span_data( models .iter_mut() .flat_map( @@ -1190,7 +1190,7 @@ mod tests { // Now we actually run the function save_report_lines(&report_lines, &mut test_ctx.parse_ctx).unwrap(); - let report = test_ctx.parse_ctx.db.report_builder.build().unwrap(); + let report = test_ctx.parse_ctx.report_builder.build().unwrap(); // Now we need to set up our mock expectations. There are a lot of them. // First thing that gets inserted is CoverageSample. We expect 4 of them, diff --git a/core/src/report/models.rs b/core/src/report/models.rs index 41ca5d0..e94bc45 100644 --- a/core/src/report/models.rs +++ b/core/src/report/models.rs @@ -97,8 +97,6 @@ use serde::Deserialize; -use crate::parsers::json::JsonVal; - #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)] #[serde(try_from = "&str")] pub enum CoverageType { @@ -378,7 +376,7 @@ pub struct RawUpload { /// /// Ex: `["unit"]` /// Ex: `["integration", "windows"]` - pub flags: Option, + pub flags: Option, /// Key in the report JSON: `"c"` pub provider: Option, @@ -429,7 +427,7 @@ pub struct RawUpload { /// /// Ex: `{"carriedforward_from": /// "bcec3478e2a27bb7950f40388cf191834fb2d5a3"}` - pub session_extras: Option, + pub session_extras: Option, } /// Aggregated coverage metrics for lines, branches, and sessions in a report diff --git a/core/src/report/pyreport/chunks.rs b/core/src/report/pyreport/chunks.rs index 1d45a1e..8a75de1 100644 --- a/core/src/report/pyreport/chunks.rs +++ b/core/src/report/pyreport/chunks.rs @@ -1,11 +1,10 @@ use std::io::Write; -use serde_json::json; +use serde_json::{json, Number as JsonNumber, Value as JsonVal}; use super::{CHUNKS_FILE_END_OF_CHUNK, CHUNKS_FILE_HEADER_TERMINATOR}; use crate::{ error::{CodecovError, Result}, - parsers::json::{JsonNumber, JsonVal}, report::{models, sqlite::json_value_from_sql, SqliteReport}, }; diff --git a/core/src/report/pyreport/report_json.rs b/core/src/report/pyreport/report_json.rs index 6fd882c..d83fcce 100644 --- a/core/src/report/pyreport/report_json.rs +++ b/core/src/report/pyreport/report_json.rs @@ -1,10 +1,9 @@ use std::io::Write; -use serde_json::json; +use serde_json::{json, Value as JsonVal}; use crate::{ error::Result, - parsers::json::JsonVal, report::{models, sqlite::json_value_from_sql, SqliteReport}, }; diff --git a/core/src/report/pyreport/types.rs b/core/src/report/pyreport/types.rs index 8402b80..c0aa1c1 100644 --- a/core/src/report/pyreport/types.rs +++ b/core/src/report/pyreport/types.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use serde::Deserialize; pub use super::super::models::CoverageType; -use crate::parsers::json::JsonVal; #[cfg(doc)] use crate::report::models; @@ -176,7 +175,7 @@ pub struct ReportLine { pub sessions: Vec, /// Long forgotten field that takes up space. - pub _messages: Option>, + pub _messages: Option>, /// An aggregated complexity metric across all of the [`LineSession`]s in /// `sessions`. diff --git a/core/src/report/sqlite/models.rs b/core/src/report/sqlite/models.rs index 3af0c25..bf605b6 100644 --- a/core/src/report/sqlite/models.rs +++ b/core/src/report/sqlite/models.rs @@ -13,7 +13,7 @@ use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use super::super::models::*; -use crate::{error::Result, parsers::json::JsonVal}; +use crate::error::Result; /// Takes care of the boilerplate to insert a model into the database. /// Implementers must provide three things: @@ -142,8 +142,8 @@ pub trait Insertable { /// Can't implement foreign traits (`ToSql`/`FromSql`) on foreign types /// (`serde_json::Value`) so this helper function fills in. -pub fn json_value_from_sql(s: String, col: usize) -> rusqlite::Result { - serde_json::from_str(s.as_str()).map_err(|e| { +pub fn json_value_from_sql(s: String, col: usize) -> rusqlite::Result { + serde_json::from_str(&s).map_err(|e| { rusqlite::Error::FromSqlConversionFailure(col, rusqlite::types::Type::Text, Box::new(e)) }) }