diff --git a/Cargo.toml b/Cargo.toml index 6b4acf53..815126e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,21 +46,22 @@ crossterm = { version = "0.25.0", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1.6" -wasm-bindgen = { version = "0.2.75", features = ["serde-serialize"], optional = true } +serde-wasm-bindgen = { version = "0.4", optional = true } +wasm-bindgen = { version = "0.2", optional = true } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3.25" [features] default = ["std", "ast-span", "ast-comments", "json", "cbor", "additional-controls"] -std = ["base16/alloc", "base64/alloc", "serde_json", "ciborium", "serde", "chrono", "wasm-bindgen", "clap", "crossterm", "uriparse", "base64-url", "regex-syntax"] +std = ["base16/alloc", "base64/alloc", "serde_json", "ciborium", "serde", "chrono", "wasm-bindgen", "serde-wasm-bindgen", "clap", "crossterm", "uriparse", "base64-url", "regex-syntax"] lsp = ["std"] additional-controls = [] ast-span = [] ast-comments = [] json = ["std"] cbor = ["std"] -web = ["ast-span", "wasm-bindgen", "serde"] +web = ["ast-span", "wasm-bindgen", "serde-wasm-bindgen", "serde"] [[bin]] name = "cddl" diff --git a/src/ast.rs b/src/ast.rs index 92b4f026..14f51b41 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -2,7 +2,10 @@ extern crate console_error_panic_hook; use super::token::{ByteValue, RangeValue, SocketPlug, Token, Value}; -use std::{fmt, marker::PhantomData}; +use std::{ + fmt::{self, Write}, + marker::PhantomData, +}; #[cfg(feature = "std")] use std::borrow::Cow; @@ -23,7 +26,7 @@ use alloc::{ pub type Span = (usize, usize, usize); #[cfg(feature = "ast-comments")] -#[derive(Default, Debug, PartialEq, Clone)] +#[derive(Default, Debug, PartialEq, Eq, Clone)] #[doc(hidden)] pub struct Comments<'a>(pub Vec<&'a str>); @@ -51,7 +54,7 @@ impl<'a> fmt::Display for Comments<'a> { if *comment == "\n" { comment_str.push('\n') } else { - comment_str.push_str(&format!(";{}\n", comment)); + let _ = writeln!(comment_str, ";{}", comment); } } @@ -99,15 +102,15 @@ impl<'a> fmt::Display for CDDL<'a> { cddl_output.push_str(&rule.to_string()); previous_comments_after_rule = true; } else if idx == self.rules.len() - 1 || rule.has_single_line_type() { - cddl_output.push_str(&format!("{}\n", rule.to_string().trim_end())); + let _ = writeln!(cddl_output, "{}", rule.to_string().trim_end()); previous_single_line_type = true; previous_comments_after_rule = false; } else if previous_single_line_type && !previous_comments_after_rule { - cddl_output.push_str(&format!("\n{}\n\n", rule.to_string().trim_end())); + let _ = write!(cddl_output, "\n{}\n\n", rule.to_string().trim_end()); previous_single_line_type = false; previous_comments_after_rule = false; } else { - cddl_output.push_str(&format!("{}\n\n", rule.to_string().trim_end())); + let _ = write!(cddl_output, "{}\n\n", rule.to_string().trim_end()); previous_comments_after_rule = false; } } @@ -115,13 +118,13 @@ impl<'a> fmt::Display for CDDL<'a> { #[cfg(not(feature = "ast-comments"))] for (idx, rule) in self.rules.iter().enumerate() { if idx == self.rules.len() - 1 || rule.has_single_line_type() { - cddl_output.push_str(&format!("{}\n", rule.to_string().trim_end())); + let _ = writeln!(cddl_output, "{}", rule.to_string().trim_end()); previous_single_line_type = true; } else if previous_single_line_type { - cddl_output.push_str(&format!("\n{}\n\n", rule.to_string().trim_end())); + let _ = write!(cddl_output, "\n{}\n\n", rule.to_string().trim_end()); previous_single_line_type = false; } else { - cddl_output.push_str(&format!("{}\n\n", rule.to_string().trim_end())); + let _ = write!(cddl_output, "{}\n\n", rule.to_string().trim_end()); } } @@ -324,7 +327,7 @@ impl<'a> fmt::Display for Rule<'a> { if let Some(&"\n") = comments.0.first() { rule_str.push_str(&comments.to_string()); } else { - rule_str.push_str(&format!(" {}", comments)); + let _ = write!(rule_str, " {}", comments); } } } @@ -347,7 +350,7 @@ impl<'a> fmt::Display for Rule<'a> { if let Some(&"\n") = comments.0.first() { rule_str.push_str(&comments.to_string()); } else { - rule_str.push_str(&format!(" {}", comments)); + let _ = write!(rule_str, " {}", comments); } } } @@ -727,9 +730,9 @@ impl<'a> fmt::Display for Type<'a> { } if self.type_choices.len() > 2 { - type_str.push_str(&format!("\n\t/ {}", tc.type1)); + let _ = write!(type_str, "\n\t/ {}", tc.type1); } else { - type_str.push_str(&format!(" / {}", tc.type1)); + let _ = write!(type_str, " / {}", tc.type1); } #[cfg(feature = "ast-comments")] @@ -914,7 +917,7 @@ impl<'a> fmt::Display for Type1<'a> { t1_str.push_str(&o.type2.to_string()); } else if let Some(comments) = &self.comments_after_type { if comments.any_non_newline() { - t1_str.push_str(&format!(" {}", comments)); + let _ = write!(t1_str, " {}", comments); } } @@ -940,7 +943,7 @@ impl<'a> fmt::Display for Type1<'a> { /// ctlop = "." id /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum RangeCtlOp<'a> { /// Range operator RangeOp { @@ -1259,7 +1262,7 @@ impl<'a> fmt::Display for Type2<'a> { #[cfg(feature = "ast-comments")] if let Some(comments) = comments_before_type { if comments.any_non_newline() { - pt_str.push_str(&format!(" {}\t", comments)); + let _ = write!(pt_str, " {}\t", comments); pt_str.push_str(pt.to_string().trim_start()); } else { pt_str.push_str(&pt.to_string()); @@ -1299,7 +1302,7 @@ impl<'a> fmt::Display for Type2<'a> { if let Some(comments) = comments_before_group { if comments.any_non_newline() { non_newline_comments_before_group = true; - t2_str.push_str(&format!(" {}\t", comments)); + let _ = write!(t2_str, " {}\t", comments); t2_str.push_str(group.to_string().trim_start()); } else { t2_str.push_str(&group.to_string()); @@ -1357,16 +1360,16 @@ impl<'a> fmt::Display for Type2<'a> { for (idx, comment) in comments.0.iter().enumerate() { if *comment != "\n" { if idx == 0 { - t2_str.push_str(&format!(" ;{}", comment)); + let _ = write!(t2_str, " ;{}", comment); } else { - t2_str.push_str(&format!("\t;{}\n", comment)); + let _ = writeln!(t2_str, "\t;{}", comment); } } else { t2_str.push('\n'); } } - t2_str.push_str(&format!("\t{}", group.to_string().trim_start())); + let _ = write!(t2_str, "\t{}", group.to_string().trim_start()); } else { t2_str.push_str(&group.to_string()); } @@ -1416,7 +1419,7 @@ impl<'a> fmt::Display for Type2<'a> { } if let Some(args) = generic_args { - t2_str.push_str(&format!("{}{}", ident, args)); + let _ = write!(t2_str, "{}{}", ident, args); } else { t2_str.push_str(&ident.to_string()); } @@ -1477,7 +1480,7 @@ impl<'a> fmt::Display for Type2<'a> { } if let Some(ga) = generic_args { - t2_str.push_str(&format!("{}{}", ident, ga)); + let _ = write!(t2_str, "{}{}", ident, ga); } else { t2_str.push_str(&ident.to_string()); } @@ -1496,7 +1499,7 @@ impl<'a> fmt::Display for Type2<'a> { let mut t2_str = String::from("#6"); if let Some(tag_uint) = tag { - t2_str.push_str(&format!(".{}", tag_uint)); + let _ = write!(t2_str, ".{}", tag_uint); } t2_str.push('('); @@ -1504,7 +1507,7 @@ impl<'a> fmt::Display for Type2<'a> { #[cfg(feature = "ast-comments")] if let Some(comments) = comments_before_type { if comments.any_non_newline() { - t2_str.push_str(&format!(" {}", comments)); + let _ = write!(t2_str, " {}", comments); } } @@ -1513,7 +1516,7 @@ impl<'a> fmt::Display for Type2<'a> { #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_type { if comments.any_non_newline() { - t2_str.push_str(&format!(" {}", comments)); + let _ = write!(t2_str, " {}", comments); } } @@ -1937,9 +1940,9 @@ impl<'a> fmt::Display for Group<'a> { } if self.group_choices.len() <= 2 { - group_str.push_str(&format!("// {} ", gc_str)); + let _ = write!(group_str, "// {} ", gc_str); } else { - group_str.push_str(&format!("\t// {}\n", gc_str)); + let _ = writeln!(group_str, "\t// {}", gc_str); } } @@ -2031,10 +2034,11 @@ impl<'a> fmt::Display for GroupChoice<'a> { let mut gc_str = String::new(); if self.group_entries.len() == 1 { - gc_str.push_str(&format!( + let _ = write!( + gc_str, " {}{}", self.group_entries[0].0, self.group_entries[0].1 - )); + ); #[cfg(feature = "ast-comments")] if !self.group_entries[0].1.has_trailing_comments() { @@ -2122,23 +2126,24 @@ impl<'a> fmt::Display for GroupChoice<'a> { if entries_with_comment_before_comma.iter().any(|e| e.1) { if idx == 0 { if entries_with_comment_before_comma[idx].1 { - gc_str.push_str(&format!("{}", ge.0)); + let _ = write!(gc_str, "{}", ge.0); } else { - gc_str.push_str(&format!("{}\n", ge.0)); + let _ = writeln!(gc_str, "{}", ge.0); } } else if entries_with_comment_before_comma[idx].1 { - gc_str.push_str(&format!(", {}", ge.0)); + let _ = write!(gc_str, ", {}", ge.0); } else if idx != self.group_entries.len() - 1 { - gc_str.push_str(&format!(", {}\n", ge.0.to_string().trim_end())); + let _ = writeln!(gc_str, ", {}", ge.0.to_string().trim_end()); } else { - gc_str.push_str(&format!(", {}", ge.0.to_string().trim_end())); + let _ = write!(gc_str, ", {}", ge.0.to_string().trim_end()); } } else { - gc_str.push_str(&format!( + let _ = write!( + gc_str, "{}{}", ge.0.to_string().trim_end(), ge.1.to_string().trim_end() - )); + ); // if idx != self.group_entries.len() - 1 { // gc_str.push_str(","); @@ -2151,11 +2156,12 @@ impl<'a> fmt::Display for GroupChoice<'a> { #[cfg(not(feature = "ast-comments"))] { - gc_str.push_str(&format!( + let _ = write!( + gc_str, "{}{}", ge.0.to_string().trim_end(), ge.1.to_string().trim_end() - )); + ); // if idx != self.group_entries.len() - 1 { // gc_str.push_str(","); @@ -2307,13 +2313,13 @@ impl<'a> fmt::Display for OptionalComma<'a> { for (idx, &comment) in comments.0.iter().enumerate() { if idx == 0 && comment != "\n" { - optcomma_str.push_str(&format!(";{}\n", comment)); + let _ = writeln!(optcomma_str, ";{}", comment); } else if idx == 0 { optcomma_str.push_str(comment); } else if comment != "\n" { - optcomma_str.push_str(&format!("\t;{}\n", comment)); + let _ = writeln!(optcomma_str, "\t;{}", comment); } else { - optcomma_str.push_str(&format!("\t{}", comment)); + let _ = write!(optcomma_str, "\t{}", comment); } } } @@ -2357,7 +2363,7 @@ impl<'a> fmt::Display for GroupEntry<'a> { #[cfg(feature = "ast-comments")] if let Some(comments) = trailing_comments { if comments.any_non_newline() { - ge_str.push_str(&format!(" {}", comments)); + let _ = write!(ge_str, " {}", comments); } } @@ -2383,7 +2389,7 @@ impl<'a> fmt::Display for GroupEntry<'a> { #[cfg(feature = "ast-comments")] if let Some(comments) = trailing_comments { if comments.any_non_newline() { - ge_str.push_str(&format!(" {}", comments)); + let _ = write!(ge_str, " {}", comments); } } @@ -2401,7 +2407,7 @@ impl<'a> fmt::Display for GroupEntry<'a> { let mut ge_str = String::new(); if let Some(o) = occur { - ge_str.push_str(&format!("{} ", o.occur)); + let _ = write!(ge_str, "{} ", o.occur); #[cfg(feature = "ast-comments")] if let Some(comments) = &o.comments { @@ -2419,14 +2425,14 @@ impl<'a> fmt::Display for GroupEntry<'a> { if comments.any_non_newline() { non_newline_comments_before_group = true; - ge_str.push_str(&format!(" {}", comments)); + let _ = write!(ge_str, " {}", comments); if !group .group_choices .iter() .all(|gc| gc.group_entries.is_empty()) { - ge_str.push_str(&format!("\t{}", group.to_string().trim_start())); + let _ = write!(ge_str, "\t{}", group.to_string().trim_start()); } } else { ge_str.push_str(&group.to_string()); @@ -2521,11 +2527,11 @@ impl<'a> fmt::Display for ValueMemberKeyEntry<'a> { let mut vmke_str = String::new(); if let Some(o) = &self.occur { - vmke_str.push_str(&format!("{} ", o)); + let _ = write!(vmke_str, "{} ", o); } if let Some(mk) = &self.member_key { - vmke_str.push_str(&format!("{} ", mk)); + let _ = write!(vmke_str, "{} ", mk); } vmke_str.push_str(&self.entry_type.to_string()); @@ -2552,7 +2558,7 @@ impl<'a> fmt::Display for TypeGroupnameEntry<'a> { let mut tge_str = String::new(); if let Some(o) = &self.occur { - tge_str.push_str(&format!("{} ", o)); + let _ = write!(tge_str, "{} ", o); } tge_str.push_str(&self.name.to_string()); @@ -2694,7 +2700,7 @@ impl<'a> fmt::Display for MemberKey<'a> { #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_arrowmap { if comments.any_non_newline() { - mk_str.push_str(&format!(" {}", comments)); + let _ = write!(mk_str, " {}", comments); } } @@ -2713,7 +2719,7 @@ impl<'a> fmt::Display for MemberKey<'a> { #[cfg(feature = "ast-comments")] if let Some(comments) = comments { if comments.any_non_newline() { - mk_str.push_str(&format!(" {}", comments)); + let _ = write!(mk_str, " {}", comments); } } @@ -2722,7 +2728,7 @@ impl<'a> fmt::Display for MemberKey<'a> { #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_colon { if comments.any_non_newline() { - mk_str.push_str(&format!(" {}", comments)); + let _ = write!(mk_str, " {}", comments); } } @@ -2741,7 +2747,7 @@ impl<'a> fmt::Display for MemberKey<'a> { #[cfg(feature = "ast-comments")] if let Some(comments) = comments { if comments.any_non_newline() { - mk_str.push_str(&format!(" {}", comments)); + let _ = write!(mk_str, " {}", comments); } } @@ -2750,7 +2756,7 @@ impl<'a> fmt::Display for MemberKey<'a> { #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_colon { if comments.any_non_newline() { - mk_str.push_str(&format!(" {}", comments)); + let _ = write!(mk_str, " {}", comments); } } @@ -2813,7 +2819,7 @@ impl<'a> fmt::Display for MemberKey<'a> { /// / "?" /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Occur { /// Occurrence indicator in the form n*m, where n is an optional lower limit /// and m is an optional upper limit diff --git a/src/bin/cli.rs b/src/bin/cli.rs index 4e5327d3..2dcb7a8c 100644 --- a/src/bin/cli.rs +++ b/src/bin/cli.rs @@ -11,6 +11,7 @@ use clap::{ArgGroup, Args, Parser, Subcommand}; use simplelog::*; use std::{ error::Error, + fmt::Write, fs::{self, File}, io::{self, BufReader, Read}, path::Path, @@ -126,9 +127,9 @@ fn main() -> Result<(), Box> { let mut feature_str = String::from("enabled features: ["); for (idx, feature) in enabled_features.iter().enumerate() { if idx == 0 { - feature_str.push_str(&format!("\"{}\"", feature)); + let _ = write!(feature_str, "\"{}\"", feature); } else { - feature_str.push_str(&format!(", \"{}\"", feature)); + let _ = write!(feature_str, ", \"{}\"", feature); } } feature_str.push(']'); diff --git a/src/lexer.rs b/src/lexer.rs index 3f786590..f4dc4872 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -1015,11 +1015,11 @@ fn is_ealpha(ch: char) -> bool { } fn is_digit(ch: char) -> bool { - ch.is_digit(10) + ch.is_ascii_digit() } fn is_hexdigit(ch: char) -> bool { - ch.is_digit(16) + ch.is_ascii_hexdigit() } #[cfg(test)] diff --git a/src/parser.rs b/src/parser.rs index e4ff513b..087f6481 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3482,7 +3482,7 @@ impl<'a> CDDL<'a> { /// ``` #[cfg(not(target_arch = "wasm32"))] #[cfg(not(feature = "std"))] -pub fn cddl_from_str<'a>(input: &'a str) -> std::result::Result, String> { +pub fn cddl_from_str(input: &str) -> std::result::Result { match Parser::new(input, Box::new(lexer::lexer_from_str(input).iter())).map_err(|e| e.to_string()) { Ok(mut p) => match p.parse_cddl() { @@ -3529,11 +3529,11 @@ pub fn cddl_from_str(input: &str) -> result::Result { match Parser::new(input, Box::new(lexer::Lexer::new(input).iter())) { Ok(mut p) => match p.parse_cddl() { - Ok(c) => JsValue::from_serde(&c).map_err(|e| JsValue::from(e.to_string())), + Ok(c) => serde_wasm_bindgen::to_value(&c).map_err(|e| JsValue::from(e.to_string())), Err(Error::INCREMENTAL) => { if !p.errors.is_empty() { return Err( - JsValue::from_serde( + serde_wasm_bindgen::to_value( &p.errors .iter() .filter_map(|e| { diff --git a/src/token.rs b/src/token.rs index bc90bfd0..5910ef1b 100644 --- a/src/token.rs +++ b/src/token.rs @@ -404,7 +404,7 @@ impl<'a> From<&'a str> for Value<'a> { /// Byte string values #[cfg_attr(target_arch = "wasm32", derive(Serialize, Deserialize))] -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum ByteValue<'a> { /// Unprefixed byte string value UTF8(Cow<'a, [u8]>), @@ -438,7 +438,7 @@ impl<'a> fmt::Display for ByteValue<'a> { /// Socket/plug prefix #[cfg_attr(target_arch = "wasm32", derive(Serialize, Deserialize))] -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SocketPlug { /// Type socket `$` TYPE, @@ -466,7 +466,7 @@ impl std::str::FromStr for SocketPlug { } } -impl<'a> fmt::Display for SocketPlug { +impl fmt::Display for SocketPlug { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { SocketPlug::TYPE => write!(f, "$"), diff --git a/src/validator/cbor.rs b/src/validator/cbor.rs index fce1ec4d..35901050 100644 --- a/src/validator/cbor.rs +++ b/src/validator/cbor.rs @@ -9,7 +9,12 @@ use crate::{ visitor::{self, *}, }; -use std::{borrow::Cow, collections::HashMap, convert::TryFrom, fmt}; +use std::{ + borrow::Cow, + collections::HashMap, + convert::TryFrom, + fmt::{self, Write}, +}; use chrono::{TimeZone, Utc}; use ciborium::value::Value; @@ -44,7 +49,7 @@ impl fmt::Display for Error { Error::Validation(errors) => { let mut error_str = String::new(); for e in errors.iter() { - error_str.push_str(&format!("{}\n", e)); + let _ = writeln!(error_str, "{}", e); } write!(f, "{}", error_str) } @@ -97,7 +102,7 @@ impl fmt::Display for ValidationError { error_str.push_str(" type choice in group to choice enumeration"); } if let Some(entry) = &self.type_group_name_entry { - error_str.push_str(&format!(" group entry associated with rule \"{}\"", entry)); + let _ = write!(error_str, " group entry associated with rule \"{}\"", entry); } write!( @@ -368,7 +373,7 @@ impl<'a> CBORValidator<'a> { } match validate_array_occurrence( - self.occurrence.as_ref().take(), + self.occurrence.as_ref(), self.entry_counts.as_ref().map(|ec| &ec[..]), a, ) { @@ -392,8 +397,7 @@ impl<'a> CBORValidator<'a> { cv.eval_generic_rule = self.eval_generic_rule; cv.ctrl = self.ctrl.clone(); cv.is_multi_type_choice = self.is_multi_type_choice; - cv.cbor_location - .push_str(&format!("{}/{}", self.cbor_location, idx)); + let _ = write!(cv.cbor_location, "{}/{}", self.cbor_location, idx); match token { ArrayItemToken::Value(value) => cv.visit_value(value)?, @@ -446,8 +450,7 @@ impl<'a> CBORValidator<'a> { cv.eval_generic_rule = self.eval_generic_rule; cv.is_multi_type_choice = self.is_multi_type_choice; cv.ctrl = self.ctrl.clone(); - cv.cbor_location - .push_str(&format!("{}/{}", self.cbor_location, idx)); + let _ = write!(cv.cbor_location, "{}/{}", self.cbor_location, idx); match token { ArrayItemToken::Value(value) => cv.visit_value(value)?, @@ -2293,220 +2296,187 @@ where Value::Array(_) => self.validate_array_items(&ArrayItemToken::Identifier(ident)), Value::Map(m) => { if let Some(occur) = &self.occurrence { - #[cfg(feature = "ast-span")] - if let Occur::ZeroOrMore(_) | Occur::OneOrMore(_) = occur { - if let Occur::OneOrMore(_) = occur { - if m.is_empty() { - self.add_error(format!( - "map cannot be empty, one or more entries with key type {} required", - ident - )); - return Ok(()); - } - } - - if is_ident_string_data_type(self.cddl, ident) { - let mut errors = Vec::new(); - let values_to_validate = m - .iter() - .filter_map(|(k, v)| { - if let Some(keys) = &self.validated_keys { - if !keys.contains(k) { - if matches!(k, Value::Text(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } + let mut errors = Vec::new(); + + if is_ident_string_data_type(self.cddl, ident) { + let values_to_validate = m + .iter() + .filter_map(|(k, v)| { + if let Some(keys) = &self.validated_keys { + if !keys.contains(k) { + if matches!(k, Value::Text(_)) { + Some(v.clone()) } else { + errors.push(format!("key of type {} required, got {:?}", ident, k)); None } - } else if matches!(k, Value::Text(_)) { - Some(v.clone()) } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); None } - }) - .collect::>(); - - self.values_to_validate = Some(values_to_validate); - for e in errors.into_iter() { - self.add_error(e); - } + } else if matches!(k, Value::Text(_)) { + Some(v.clone()) + } else { + errors.push(format!("key of type {} required, got {:?}", ident, k)); + None + } + }) + .collect::>(); - return Ok(()); - } + self.values_to_validate = Some(values_to_validate); + } - if is_ident_integer_data_type(self.cddl, ident) { - let mut errors = Vec::new(); - let values_to_validate = m - .iter() - .filter_map(|(k, v)| { - if let Some(keys) = &self.validated_keys { - if !keys.contains(k) { - if matches!(k, Value::Integer(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } + if is_ident_integer_data_type(self.cddl, ident) { + let mut errors = Vec::new(); + let values_to_validate = m + .iter() + .filter_map(|(k, v)| { + if let Some(keys) = &self.validated_keys { + if !keys.contains(k) { + if matches!(k, Value::Integer(_)) { + Some(v.clone()) } else { + errors.push(format!("key of type {} required, got {:?}", ident, k)); None } - } else if matches!(k, Value::Integer(_)) { - Some(v.clone()) } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); None } - }) - .collect::>(); - - self.values_to_validate = Some(values_to_validate); - for e in errors.into_iter() { - self.add_error(e); - } + } else if matches!(k, Value::Integer(_)) { + Some(v.clone()) + } else { + errors.push(format!("key of type {} required, got {:?}", ident, k)); + None + } + }) + .collect::>(); - return Ok(()); - } + self.values_to_validate = Some(values_to_validate); + } - if is_ident_bool_data_type(self.cddl, ident) { - let mut errors = Vec::new(); - let values_to_validate = m - .iter() - .filter_map(|(k, v)| { - if let Some(keys) = &self.validated_keys { - if !keys.contains(k) { - if matches!(k, Value::Bool(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } + if is_ident_bool_data_type(self.cddl, ident) { + let mut errors = Vec::new(); + let values_to_validate = m + .iter() + .filter_map(|(k, v)| { + if let Some(keys) = &self.validated_keys { + if !keys.contains(k) { + if matches!(k, Value::Bool(_)) { + Some(v.clone()) } else { + errors.push(format!("key of type {} required, got {:?}", ident, k)); None } - } else if matches!(k, Value::Bool(_)) { - Some(v.clone()) } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); None } - }) - .collect::>(); - - self.values_to_validate = Some(values_to_validate); - for e in errors.into_iter() { - self.add_error(e); - } + } else if matches!(k, Value::Bool(_)) { + Some(v.clone()) + } else { + errors.push(format!("key of type {} required, got {:?}", ident, k)); + None + } + }) + .collect::>(); - return Ok(()); - } + self.values_to_validate = Some(values_to_validate); + } - if is_ident_byte_string_data_type(self.cddl, ident) { - let mut errors = Vec::new(); - let values_to_validate = m - .iter() - .filter_map(|(k, v)| { - if let Some(keys) = &self.validated_keys { - if !keys.contains(k) { - if matches!(k, Value::Bytes(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } + if is_ident_byte_string_data_type(self.cddl, ident) { + let mut errors = Vec::new(); + let values_to_validate = m + .iter() + .filter_map(|(k, v)| { + if let Some(keys) = &self.validated_keys { + if !keys.contains(k) { + if matches!(k, Value::Bytes(_)) { + Some(v.clone()) } else { + errors.push(format!("key of type {} required, got {:?}", ident, k)); None } - } else if matches!(k, Value::Bytes(_)) { - Some(v.clone()) } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); None } - }) - .collect::>(); - - self.values_to_validate = Some(values_to_validate); - for e in errors.into_iter() { - self.add_error(e); - } + } else if matches!(k, Value::Bytes(_)) { + Some(v.clone()) + } else { + errors.push(format!("key of type {} required, got {:?}", ident, k)); + None + } + }) + .collect::>(); - return Ok(()); - } + self.values_to_validate = Some(values_to_validate); + } - if is_ident_null_data_type(self.cddl, ident) { - let mut errors = Vec::new(); - let values_to_validate = m - .iter() - .filter_map(|(k, v)| { - if let Some(keys) = &self.validated_keys { - if !keys.contains(k) { - if matches!(k, Value::Null) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } + if is_ident_null_data_type(self.cddl, ident) { + let mut errors = Vec::new(); + let values_to_validate = m + .iter() + .filter_map(|(k, v)| { + if let Some(keys) = &self.validated_keys { + if !keys.contains(k) { + if matches!(k, Value::Null) { + Some(v.clone()) } else { + errors.push(format!("key of type {} required, got {:?}", ident, k)); None } - } else if matches!(k, Value::Null) { - Some(v.clone()) } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); None } - }) - .collect::>(); - - self.values_to_validate = Some(values_to_validate); - for e in errors.into_iter() { - self.add_error(e); - } + } else if matches!(k, Value::Null) { + Some(v.clone()) + } else { + errors.push(format!("key of type {} required, got {:?}", ident, k)); + None + } + }) + .collect::>(); - return Ok(()); - } + self.values_to_validate = Some(values_to_validate); + } - if is_ident_float_data_type(self.cddl, ident) { - let mut errors = Vec::new(); - let values_to_validate = m - .iter() - .filter_map(|(k, v)| { - if let Some(keys) = &self.validated_keys { - if !keys.contains(k) { - if matches!(k, Value::Float(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } + if is_ident_float_data_type(self.cddl, ident) { + let mut errors = Vec::new(); + let values_to_validate = m + .iter() + .filter_map(|(k, v)| { + if let Some(keys) = &self.validated_keys { + if !keys.contains(k) { + if matches!(k, Value::Float(_)) { + Some(v.clone()) } else { + errors.push(format!("key of type {} required, got {:?}", ident, k)); None } - } else if matches!(k, Value::Float(_)) { - Some(v.clone()) } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); None } - }) - .collect::>(); + } else if matches!(k, Value::Float(_)) { + Some(v.clone()) + } else { + errors.push(format!("key of type {} required, got {:?}", ident, k)); + None + } + }) + .collect::>(); - self.values_to_validate = Some(values_to_validate); - for e in errors.into_iter() { - self.add_error(e); - } + self.values_to_validate = Some(values_to_validate); + } - return Ok(()); + // If key validation error occurs, return early before checking occurrences + if !errors.is_empty() { + for e in errors.into_iter() { + self.add_error(e); } + + return Ok(()); } - #[cfg(not(feature = "ast-span"))] - if let Occur::ZeroOrMore | Occur::OneOrMore = occur { - if let Occur::OneOrMore = occur { + #[cfg(feature = "ast-span")] + if let Occur::ZeroOrMore(_) | Occur::OneOrMore(_) = occur { + if let Occur::OneOrMore(_) = occur { if m.is_empty() { self.add_error(format!( "map cannot be empty, one or more entries with key type {} required", @@ -2515,200 +2485,103 @@ where return Ok(()); } } - - if is_ident_string_data_type(self.cddl, ident) { - let mut errors = Vec::new(); - let values_to_validate = m - .iter() - .filter_map(|(k, v)| { - if let Some(keys) = &self.validated_keys { - if !keys.contains(k) { - if matches!(k, Value::Text(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } + } else if let Occur::Exact { lower, upper, .. } = occur { + if let Some(values_to_validate) = &self.values_to_validate { + if let Some(lower) = lower { + if let Some(upper) = upper { + if values_to_validate.len() < *lower || values_to_validate.len() > *upper { + if lower == upper { + self.add_error(format!( + "object must contain exactly {} entries of key of type {}", + lower, ident, + )); } else { - None + self.add_error(format!( + "object must contain between {} and {} entries of key of type {}", + lower, upper, ident, + )); } - } else if matches!(k, Value::Text(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } - }) - .collect::>(); - - self.values_to_validate = Some(values_to_validate); - for e in errors.into_iter() { - self.add_error(e); - } - - return Ok(()); - } - if is_ident_integer_data_type(self.cddl, ident) { - let mut errors = Vec::new(); - let values_to_validate = m - .iter() - .filter_map(|(k, v)| { - if let Some(keys) = &self.validated_keys { - if !keys.contains(k) { - if matches!(k, Value::Integer(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } - } else { - None - } - } else if matches!(k, Value::Integer(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None + return Ok(()); } - }) - .collect::>(); + } - self.values_to_validate = Some(values_to_validate); - for e in errors.into_iter() { - self.add_error(e); - } + if values_to_validate.len() < *lower { + self.add_error(format!( + "object must contain at least {} entries of key of type {}", + lower, ident, + )); - return Ok(()); - } + return Ok(()); + } + } - if is_ident_bool_data_type(self.cddl, ident) { - let mut errors = Vec::new(); - let values_to_validate = m - .iter() - .filter_map(|(k, v)| { - if let Some(keys) = &self.validated_keys { - if !keys.contains(k) { - if matches!(k, Value::Bool(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } - } else { - None - } - } else if matches!(k, Value::Bool(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } - }) - .collect::>(); + if let Some(upper) = upper { + if values_to_validate.len() > *upper { + self.add_error(format!( + "object must contain no more than {} entries of key of type {}", + upper, ident, + )); - self.values_to_validate = Some(values_to_validate); - for e in errors.into_iter() { - self.add_error(e); + return Ok(()); + } } return Ok(()); } + } - if is_ident_byte_string_data_type(self.cddl, ident) { - let mut errors = Vec::new(); - let values_to_validate = m - .iter() - .filter_map(|(k, v)| { - if let Some(keys) = &self.validated_keys { - if !keys.contains(k) { - if matches!(k, Value::Bytes(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } - } else { - None - } - } else if matches!(k, Value::Bytes(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } - }) - .collect::>(); - - self.values_to_validate = Some(values_to_validate); - for e in errors.into_iter() { - self.add_error(e); + #[cfg(not(feature = "ast-span"))] + if let Occur::ZeroOrMore | Occur::OneOrMore = occur { + if let Occur::OneOrMore = occur { + if o.is_empty() { + self.add_error(format!( + "object cannot be empty, one or more entries with key type {} required", + ident + )); + return Ok(()); } - - return Ok(()); } - - if is_ident_null_data_type(self.cddl, ident) { - let mut errors = Vec::new(); - let values_to_validate = m - .iter() - .filter_map(|(k, v)| { - if let Some(keys) = &self.validated_keys { - if !keys.contains(k) { - if matches!(k, Value::Null) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } + } else if let Occur::Exact { lower, upper } = occur { + if let Some(values_to_validate) = &self.values_to_validate { + if let Some(lower) = lower { + if let Some(upper) = upper { + if values_to_validate.len() < *lower || values_to_validate.len() > *upper { + if lower == upper { + self.add_error(format!( + "object must contain exactly {} entries of key of type {}", + lower, ident, + )); } else { - None + self.add_error(format!( + "object must contain between {} and {} entries of key of type {}", + lower, upper, ident, + )); } - } else if matches!(k, Value::Null) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None + + return Ok(()); } - }) - .collect::>(); + } - self.values_to_validate = Some(values_to_validate); - for e in errors.into_iter() { - self.add_error(e); - } + if values_to_validate.len() < *lower { + self.add_error(format!( + "object must contain at least {} entries of key of type {}", + lower, ident, + )); - return Ok(()); - } + return Ok(()); + } + } - if is_ident_float_data_type(self.cddl, ident) { - let mut errors = Vec::new(); - let values_to_validate = m - .iter() - .filter_map(|(k, v)| { - if let Some(keys) = &self.validated_keys { - if !keys.contains(k) { - if matches!(k, Value::Float(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } - } else { - None - } - } else if matches!(k, Value::Float(_)) { - Some(v.clone()) - } else { - errors.push(format!("key of type {} required, got {:?}", ident, k)); - None - } - }) - .collect::>(); + if let Some(upper) = upper { + if values_to_validate.len() > *upper { + self.add_error(format!( + "object must contain no more than {} entries of key of type {}", + upper, ident, + )); - self.values_to_validate = Some(values_to_validate); - for e in errors.into_iter() { - self.add_error(e); + return Ok(()); + } } return Ok(()); @@ -2723,7 +2596,7 @@ where .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); - self.cbor_location.push_str(&format!("/{:?}", v)); + let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } @@ -2738,7 +2611,7 @@ where .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); - self.cbor_location.push_str(&format!("/{:?}", v)); + let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } @@ -2752,7 +2625,7 @@ where .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); - self.cbor_location.push_str(&format!("/{:?}", v)); + let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } @@ -2766,7 +2639,7 @@ where .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); - self.cbor_location.push_str(&format!("/{:?}", v)); + let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } @@ -2780,7 +2653,7 @@ where .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); - self.cbor_location.push_str(&format!("/{:?}", v)); + let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } @@ -2794,7 +2667,7 @@ where .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); - self.cbor_location.push_str(&format!("/{:?}", v)); + let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } @@ -3323,7 +3196,7 @@ where { self.validated_keys.get_or_insert(vec![k.clone()]).push(k); self.object_value = Some(v.clone()); - self.cbor_location.push_str(&format!("/{}", value)); + let _ = write!(self.cbor_location, "/{}", value); None } else if let Some(Occur::Optional(_)) | Some(Occur::ZeroOrMore(_)) = diff --git a/src/validator/json.rs b/src/validator/json.rs index f19cff6f..12d04262 100644 --- a/src/validator/json.rs +++ b/src/validator/json.rs @@ -9,7 +9,12 @@ use crate::{ visitor::{self, *}, }; -use std::{borrow::Cow, collections::HashMap, convert::TryFrom, fmt}; +use std::{ + borrow::Cow, + collections::HashMap, + convert::TryFrom, + fmt::{self, Write}, +}; use chrono::{TimeZone, Utc}; use serde_json::Value; @@ -41,7 +46,7 @@ impl fmt::Display for Error { Error::Validation(errors) => { let mut error_str = String::new(); for e in errors.iter() { - error_str.push_str(&format!("{}\n", e)); + let _ = writeln!(error_str, "{}", e); } write!(f, "{}", error_str) } @@ -108,7 +113,7 @@ impl fmt::Display for ValidationError { error_str.push_str(" type choice in group to choice enumeration"); } if let Some(entry) = &self.type_group_name_entry { - error_str.push_str(&format!(" group entry associated with rule \"{}\"", entry)); + let _ = write!(error_str, " group entry associated with rule \"{}\"", entry); } if self.json_location.is_empty() { @@ -374,7 +379,7 @@ impl<'a> JSONValidator<'a> { } match validate_array_occurrence( - self.occurrence.as_ref().take(), + self.occurrence.as_ref(), self.entry_counts.as_ref().map(|ec| &ec[..]), a, ) { @@ -398,8 +403,7 @@ impl<'a> JSONValidator<'a> { jv.eval_generic_rule = self.eval_generic_rule; jv.is_multi_type_choice = self.is_multi_type_choice; jv.ctrl = self.ctrl.clone(); - jv.json_location - .push_str(&format!("{}/{}", self.json_location, idx)); + let _ = write!(jv.json_location, "{}/{}", self.json_location, idx); match token { ArrayItemToken::Value(value) => jv.visit_value(value)?, @@ -444,8 +448,7 @@ impl<'a> JSONValidator<'a> { jv.eval_generic_rule = self.eval_generic_rule; jv.is_multi_type_choice = self.is_multi_type_choice; jv.ctrl = self.ctrl.clone(); - jv.json_location - .push_str(&format!("{}/{}", self.json_location, idx)); + let _ = write!(jv.json_location, "{}/{}", self.json_location, idx); match token { ArrayItemToken::Value(value) => jv.visit_value(value)?, @@ -496,7 +499,7 @@ impl<'a> JSONValidator<'a> { .get_or_insert(vec![t.to_string()]) .push(t.to_string()); self.object_value = Some(v.clone()); - self.json_location.push_str(&format!("/{}", t)); + let _ = write!(self.json_location, "/{}", t); return Ok(()); } else if let Some(Occur::Optional(_)) | Some(Occur::ZeroOrMore(_)) = @@ -1896,6 +1899,19 @@ impl<'a> Visitor<'a, Error> for JSONValidator<'a> { Value::Array(_) => self.validate_array_items(&ArrayItemToken::Identifier(ident)), Value::Object(o) => { if let Some(occur) = &self.occurrence { + if is_ident_string_data_type(self.cddl, ident) { + let values_to_validate = o + .iter() + .filter_map(|(k, v)| match &self.validated_keys { + Some(keys) if !keys.contains(k) => Some(v.clone()), + Some(_) => None, + None => Some(v.clone()), + }) + .collect::>(); + + self.values_to_validate = Some(values_to_validate); + } + #[cfg(feature = "ast-span")] if let Occur::ZeroOrMore(_) | Occur::OneOrMore(_) = occur { if let Occur::OneOrMore(_) = occur { @@ -1907,18 +1923,47 @@ impl<'a> Visitor<'a, Error> for JSONValidator<'a> { return Ok(()); } } + } else if let Occur::Exact { lower, upper, .. } = occur { + if let Some(values_to_validate) = &self.values_to_validate { + if let Some(lower) = lower { + if let Some(upper) = upper { + if values_to_validate.len() < *lower || values_to_validate.len() > *upper { + if lower == upper { + self.add_error(format!( + "object must contain exactly {} entries of key of type {}", + lower, ident, + )); + } else { + self.add_error(format!( + "object must contain between {} and {} entries of key of type {}", + lower, upper, ident, + )); + } + + return Ok(()); + } + } - if is_ident_string_data_type(self.cddl, ident) { - let values_to_validate = o - .iter() - .filter_map(|(k, v)| match &self.validated_keys { - Some(keys) if !keys.contains(k) => Some(v.clone()), - Some(_) => None, - None => Some(v.clone()), - }) - .collect::>(); + if values_to_validate.len() < *lower { + self.add_error(format!( + "object must contain at least {} entries of key of type {}", + lower, ident, + )); - self.values_to_validate = Some(values_to_validate); + return Ok(()); + } + } + + if let Some(upper) = upper { + if values_to_validate.len() > *upper { + self.add_error(format!( + "object must contain no more than {} entries of key of type {}", + upper, ident, + )); + + return Ok(()); + } + } return Ok(()); } @@ -1935,22 +1980,53 @@ impl<'a> Visitor<'a, Error> for JSONValidator<'a> { return Ok(()); } } + } else if let Occur::Exact { lower, upper } = occur { + if let Some(values_to_validate) = &self.values_to_validate { + if let Some(lower) = lower { + if let Some(upper) = upper { + if values_to_validate.len() < *lower || values_to_validate.len() > *upper { + if lower == upper { + self.add_error(format!( + "object must contain exactly {} entries of key of type {}", + lower, ident, + )); + } else { + self.add_error(format!( + "object must contain between {} and {} entries of key of type {}", + lower, upper, ident, + )); + } - if is_ident_string_data_type(self.cddl, ident) { - let values_to_validate = o - .iter() - .filter_map(|(k, v)| match &self.validated_keys { - Some(keys) if !keys.contains(k) => Some(v.clone()), - Some(_) => None, - None => Some(v.clone()), - }) - .collect::>(); + return Ok(()); + } + } + + if values_to_validate.len() < *lower { + self.add_error(format!( + "object must contain at least {} entries of key of type {}", + lower, ident, + )); - self.values_to_validate = Some(values_to_validate); + return Ok(()); + } + } + + if let Some(upper) = upper { + if values_to_validate.len() > *upper { + self.add_error(format!( + "object must contain no more than {} entries of key of type {}", + upper, ident, + )); + + return Ok(()); + } + } return Ok(()); } } + + return Ok(()); } if token::lookup_ident(ident.ident) @@ -2626,4 +2702,29 @@ mod tests { Ok(()) } + + #[test] + fn validate_occurrences_in_object() -> std::result::Result<(), Box> { + let cddl = indoc!( + r#" + limited = { 1* tstr => tstr } + "# + ); + + let json = r#"{ "A": "B" }"#; + + let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); + if let Err(e) = &cddl { + println!("{}", e); + } + + let json = serde_json::from_str::(json).map_err(json::Error::JSONParsing)?; + + let cddl = cddl.unwrap(); + + let mut jv = JSONValidator::new(&cddl, json, None); + jv.validate().unwrap(); + + Ok(()) + } } diff --git a/src/validator/mod.rs b/src/validator/mod.rs index 86d65f70..1f549f21 100644 --- a/src/validator/mod.rs +++ b/src/validator/mod.rs @@ -117,7 +117,7 @@ pub fn validate_json_from_str( let c = p.parse_cddl().map_err(|e| JsValue::from(e.to_string()))?; if !p.errors.is_empty() { return Err( - JsValue::from_serde( + serde_wasm_bindgen::to_value( &p.errors .iter() .filter_map(|e| { @@ -231,7 +231,7 @@ pub fn validate_cbor_from_slice( let c = p.parse_cddl().map_err(|e| JsValue::from(e.to_string()))?; if !p.errors.is_empty() { return Err( - JsValue::from_serde( + serde_wasm_bindgen::to_value( &p.errors .iter() .filter_map(|e| { @@ -303,10 +303,10 @@ pub fn validate_cbor_from_slice( /// Find non-choice alternate rule from a given identifier pub fn rule_from_ident<'a>(cddl: &'a CDDL, ident: &Identifier) -> Option<&'a Rule<'a>> { - cddl.rules.iter().find_map(|r| match r { - Rule::Type { rule, .. } if rule.name == *ident && !rule.is_type_choice_alternate => Some(r), - Rule::Group { rule, .. } if rule.name == *ident && !rule.is_group_choice_alternate => Some(r), - _ => None, + cddl.rules.iter().find(|r| match r { + Rule::Type { rule, .. } if rule.name == *ident && !rule.is_type_choice_alternate => true, + Rule::Group { rule, .. } if rule.name == *ident && !rule.is_group_choice_alternate => true, + _ => false, }) }