diff options
Diffstat (limited to 'src/bindings.rs')
-rw-r--r-- | src/bindings.rs | 201 |
1 files changed, 165 insertions, 36 deletions
diff --git a/src/bindings.rs b/src/bindings.rs index b937edb..825b895 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -3,6 +3,7 @@ use super::MatchedSteps; use super::PartialStep; use super::ScenarioStep; use super::StepKind; +use crate::Warning; use crate::{resource, SubplotError}; use serde::{Deserialize, Serialize}; @@ -167,6 +168,7 @@ pub struct Binding { impls: HashMap<String, Arc<BindingImpl>>, types: HashMap<String, CaptureType>, doc: Option<String>, + filename: Arc<Path>, } impl Binding { @@ -175,19 +177,14 @@ impl Binding { kind: StepKind, pattern: &str, case_sensitive: bool, - mut types: HashMap<String, CaptureType>, + types: HashMap<String, CaptureType>, doc: Option<String>, + filename: Arc<Path>, ) -> Result<Binding, SubplotError> { let regex = RegexBuilder::new(&format!("^{pattern}$")) .case_insensitive(!case_sensitive) .build() .map_err(|err| SubplotError::Regex(pattern.to_string(), err))?; - // For every named capture, ensure we have a known type for it. - // If the type is missing from the map, we default to `text` which is - // the .* pattern - for capture in regex.capture_names().flatten() { - types.entry(capture.into()).or_insert(CaptureType::Text); - } Ok(Binding { kind, @@ -196,6 +193,7 @@ impl Binding { impls: HashMap::new(), types, doc, + filename, }) } @@ -283,15 +281,14 @@ impl Binding { let cap = cap.as_str(); // These unwraps are safe because we ensured the map is complete // in the constructor, and that all the types are known. - let ty = self.types.get(name).unwrap(); - let rx = &KIND_PATTERNS.get(ty).unwrap(); + let kind = self.types.get(name).copied().unwrap_or(CaptureType::Text); + let rx = KIND_PATTERNS.get(&kind).unwrap(); if !rx.is_match(cap) { // This capture doesn't match the kind so it's not // valid for this binding. return None; } - let kind = self.types.get(name).unwrap_or(&CaptureType::Text); - PartialStep::text(name, cap, *kind) + PartialStep::text(name, cap, kind) } }; @@ -307,6 +304,39 @@ impl Binding { Some(m) } + + fn filename(&self) -> &Path { + &self.filename + } + + fn check(&self, warnings: &mut crate::Warnings) -> Result<(), SubplotError> { + fn nth(i: usize) -> &'static str { + match i % 10 { + 1 => "st", + 2 => "nd", + 3 => "rd", + _ => "th", + } + } + for (nr, capture) in self.regex.capture_names().enumerate().skip(1) { + if let Some(name) = capture { + if !self.types.contains_key(name) { + warnings.push(Warning::MissingCaptureType( + self.filename().to_owned(), + format!("{}: {}", self.kind(), self.pattern()), + name.to_string(), + )); + } + } else { + warnings.push(Warning::MissingCaptureName( + self.filename().to_owned(), + format!("{}: {}", self.kind(), self.pattern()), + format!("{nr}{}", nth(nr)), + )); + } + } + Ok(()) + } } impl PartialEq for Binding { @@ -325,10 +355,25 @@ mod test_binding { use crate::ScenarioStep; use crate::StepKind; use std::collections::HashMap; + use std::path::Path; + use std::path::PathBuf; + use std::sync::Arc; + + fn path() -> Arc<Path> { + PathBuf::new().into() + } #[test] fn creates_new() { - let b = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new(), None).unwrap(); + let b = Binding::new( + StepKind::Given, + "I am Tomjon", + false, + HashMap::new(), + None, + path(), + ) + .unwrap(); assert_eq!(b.kind(), StepKind::Given); assert!(b.regex().is_match("I am Tomjon")); assert!(!b.regex().is_match("I am Tomjon of Lancre")); @@ -337,20 +382,45 @@ mod test_binding { #[test] fn equal() { - let a = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new(), None).unwrap(); - let b = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new(), None).unwrap(); + let a = Binding::new( + StepKind::Given, + "I am Tomjon", + false, + HashMap::new(), + None, + path(), + ) + .unwrap(); + let b = Binding::new( + StepKind::Given, + "I am Tomjon", + false, + HashMap::new(), + None, + path(), + ) + .unwrap(); assert_eq!(a, b); } #[test] fn not_equal() { - let a = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new(), None).unwrap(); + let a = Binding::new( + StepKind::Given, + "I am Tomjon", + false, + HashMap::new(), + None, + path(), + ) + .unwrap(); let b = Binding::new( StepKind::Given, "I am Tomjon of Lancre", false, HashMap::new(), None, + path(), ) .unwrap(); assert_ne!(a, b); @@ -359,21 +429,21 @@ mod test_binding { #[test] fn does_not_match_with_wrong_kind() { let step = ScenarioStep::new(StepKind::Given, "given", "yo", Location::Unknown); - let b = Binding::new(StepKind::When, "yo", false, HashMap::new(), None).unwrap(); + let b = Binding::new(StepKind::When, "yo", false, HashMap::new(), None, path()).unwrap(); assert!(b.match_with_step("", &step).is_none()); } #[test] fn does_not_match_with_wrong_text() { let step = ScenarioStep::new(StepKind::Given, "given", "foo", Location::Unknown); - let b = Binding::new(StepKind::Given, "bar", false, HashMap::new(), None).unwrap(); + let b = Binding::new(StepKind::Given, "bar", false, HashMap::new(), None, path()).unwrap(); assert!(b.match_with_step("", &step).is_none()); } #[test] fn match_with_fixed_pattern() { let step = ScenarioStep::new(StepKind::Given, "given", "foo", Location::Unknown); - let b = Binding::new(StepKind::Given, "foo", false, HashMap::new(), None).unwrap(); + let b = Binding::new(StepKind::Given, "foo", false, HashMap::new(), None, path()).unwrap(); let m = b.match_with_step("", &step).unwrap(); assert_eq!(m.kind(), StepKind::Given); let mut parts = m.parts(); @@ -396,6 +466,7 @@ mod test_binding { false, HashMap::new(), None, + path(), ) .unwrap(); let m = b.match_with_step("", &step).unwrap(); @@ -413,9 +484,25 @@ mod test_binding { #[test] fn case_sensitive_mismatch() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); - let b = Binding::new(StepKind::Given, r"i am tomjon", false, HashMap::new(), None).unwrap(); + let b = Binding::new( + StepKind::Given, + r"i am tomjon", + false, + HashMap::new(), + None, + path(), + ) + .unwrap(); assert!(b.match_with_step("", &step).is_some()); - let b = Binding::new(StepKind::Given, r"i am tomjon", true, HashMap::new(), None).unwrap(); + let b = Binding::new( + StepKind::Given, + r"i am tomjon", + true, + HashMap::new(), + None, + path(), + ) + .unwrap(); assert!(b.match_with_step("", &step).is_none()); } } @@ -493,11 +580,11 @@ impl Bindings { } /// Add bindings from a YAML string - pub fn add_from_yaml(&mut self, yaml: &str) -> Result<(), SubplotError> { + pub fn add_from_yaml(&mut self, yaml: &str, filename: Arc<Path>) -> Result<(), SubplotError> { let bindings: Vec<ParsedBindingWrapper> = serde_yaml::from_str(yaml).map_err(SubplotError::Metadata)?; for wrapper in bindings { - self.add(from_hashmap(&wrapper.binding)?); + self.add(from_hashmap(&wrapper.binding, Arc::clone(&filename))?); } Ok(()) } @@ -539,12 +626,12 @@ impl Bindings { where P: AsRef<Path> + Debug, { - let yaml = resource::read_as_string(filename.as_ref(), template) - .map_err(|e| SubplotError::BindingsFileNotFound(filename.as_ref().into(), e))?; + let filename = filename.as_ref(); + let yaml = resource::read_as_string(filename, template) + .map_err(|e| SubplotError::BindingsFileNotFound(filename.into(), e))?; trace!("Loaded file content"); - self.add_from_yaml(&yaml).map_err(|e| { - SubplotError::BindingFileParseError(filename.as_ref().to_owned(), Box::new(e)) - })?; + self.add_from_yaml(&yaml, filename.to_owned().into()) + .map_err(|e| SubplotError::BindingFileParseError(filename.to_owned(), Box::new(e)))?; Ok(()) } @@ -556,9 +643,17 @@ impl Bindings { .filter(|b| b.kind() == kind && b.pattern() == pattern); m.count() == 1 } + + /// Check these bindings for any warnings which users might need to know about + pub fn check(&self, warnings: &mut crate::Warnings) -> Result<(), SubplotError> { + for binding in self.bindings() { + binding.check(warnings)?; + } + Ok(()) + } } -fn from_hashmap(parsed: &ParsedBinding) -> Result<Binding, SubplotError> { +fn from_hashmap(parsed: &ParsedBinding, filename: Arc<Path>) -> Result<Binding, SubplotError> { let given: i32 = parsed.given.is_some().into(); let when: i32 = parsed.when.is_some().into(); let then: i32 = parsed.then.is_some().into(); @@ -601,6 +696,7 @@ fn from_hashmap(parsed: &ParsedBinding) -> Result<Binding, SubplotError> { parsed.case_sensitive, types, parsed.doc.clone(), + filename, )?; trace!("Binding parsed OK"); for (template, pimpl) in &parsed.impls { @@ -622,6 +718,13 @@ mod test_bindings { use crate::SubplotError; use std::collections::HashMap; + use std::path::Path; + use std::path::PathBuf; + use std::sync::Arc; + + fn path() -> Arc<Path> { + PathBuf::new().into() + } #[test] fn has_no_bindings_initially() { @@ -637,6 +740,7 @@ mod test_bindings { false, HashMap::new(), None, + path(), ) .unwrap(); let mut bindings = Bindings::new(); @@ -672,7 +776,7 @@ mod test_bindings { total: word "; let mut bindings = Bindings::new(); - bindings.add_from_yaml(yaml).unwrap(); + bindings.add_from_yaml(yaml, path()).unwrap(); println!("test: {bindings:?}"); assert!(bindings.has(StepKind::Given, "I am Tomjon")); assert!(bindings.has(StepKind::When, "I declare myself king")); @@ -692,7 +796,7 @@ mod test_bindings { python: FUNCTION: set_name "; - match Bindings::new().add_from_yaml(yaml) { + match Bindings::new().add_from_yaml(yaml, path()) { Ok(_) => unreachable!(), Err(SubplotError::BindingHasManyKeywords(_)) => (), Err(e) => panic!("Incorrect error: {}", e), @@ -709,7 +813,7 @@ mod test_bindings { types: age: number "; - match Bindings::new().add_from_yaml(yaml) { + match Bindings::new().add_from_yaml(yaml, path()) { Ok(_) => unreachable!(), Err(SubplotError::SimplePatternKindMismatch(_)) => (), Err(e) => panic!("Incorrect error: {}", e), @@ -719,8 +823,15 @@ mod test_bindings { #[test] fn does_not_find_match_for_unmatching_kind() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); - let binding = - Binding::new(StepKind::When, r"I am Tomjon", false, HashMap::new(), None).unwrap(); + let binding = Binding::new( + StepKind::When, + r"I am Tomjon", + false, + HashMap::new(), + None, + path(), + ) + .unwrap(); let mut bindings = Bindings::new(); bindings.add(binding); assert!(matches!( @@ -738,6 +849,7 @@ mod test_bindings { false, HashMap::new(), None, + path(), ) .unwrap(); let mut bindings = Bindings::new(); @@ -753,7 +865,15 @@ mod test_bindings { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); let mut bindings = Bindings::default(); bindings.add( - Binding::new(StepKind::Given, r"I am Tomjon", false, HashMap::new(), None).unwrap(), + Binding::new( + StepKind::Given, + r"I am Tomjon", + false, + HashMap::new(), + None, + path(), + ) + .unwrap(), ); bindings.add( Binding::new( @@ -763,6 +883,7 @@ mod test_bindings { false, HashMap::new(), None, + path(), ) .unwrap(), ); @@ -775,8 +896,15 @@ mod test_bindings { #[test] fn finds_match_for_fixed_string_pattern() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); - let binding = - Binding::new(StepKind::Given, r"I am Tomjon", false, HashMap::new(), None).unwrap(); + let binding = Binding::new( + StepKind::Given, + r"I am Tomjon", + false, + HashMap::new(), + None, + path(), + ) + .unwrap(); let mut bindings = Bindings::new(); bindings.add(binding); let m = bindings.find("", &step).unwrap(); @@ -799,6 +927,7 @@ mod test_bindings { false, HashMap::new(), None, + path(), ) .unwrap(); let mut bindings = Bindings::new(); |