blob: af98ead5dd0e3439db5cc5b178c2552def828260 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Matching Actions
///
/// Matches parameters types to a list of input types (with duplicates)
/// and matches a text query against the text in the Action.
use crate::models::Action;
use std::cmp::min;
/// true if elements in a are covered by elements in b.
///
/// This is a list implementation of a multiset.
///
/// Assumes a and b are sorted.
///
/// [1, 3] is covered by [1, 2, 3]
/// [1, 1] is covered by [1, 1, 2, 3]
/// [1, 1] is not covered by [1, 2, 3]
///
#[cfg(test)]
fn covers(a: &Vec<&str>, b: &Vec<&str>) -> bool {
// return false when any element in a is not in b
let mut it = b.into_iter();
for a_val in a {
loop {
match it.next() {
Some(x) if x == a_val => break, // step to next a_val
Some(_) => continue, // keep looking
None => return false, // not found in types
}
}
}
true
}
/// Match the query against text.
///
/// returns true if query is not empty and
/// - query is a case insensitive prefix of any word in text
/// - text is empty.
///
pub fn query_text_match(query: &str, text: &str) -> bool {
!query.is_empty()
&& (text.is_empty()
|| text
.split_whitespace()
.any(|w| w[0..min(query.len(), w.len())].eq_ignore_ascii_case(query)))
}
/// Match the query against text in the Action
///
/// returns true if query string matches action keywords
///
// TODO: match against keywords rather than display_info.title
pub fn query_action_match(action: &Action, query: &str) -> bool {
action
.action_display
.as_ref()
.and_then(|action_display| action_display.display_info.as_ref())
.and_then(|display_info| display_info.title.as_ref())
.map(|title| query_text_match(query, &title))
.unwrap_or(false)
}
/// Match a vector of actions.
///
/// types must be sorted
///
/// returns a vector of the indices of all matching actions
///
#[cfg(test)]
pub fn action_match(query: &str, actions: &Vec<Action>, types: &Vec<&str>) -> Vec<usize> {
// from_iter(types) results in double borrow &&str error, so add dereference
let mut output = vec![];
for index in 0..actions.len() {
let mut params: Vec<&str> =
actions[index].parameters.iter().map(|p| p.parameter_type.as_str()).collect();
params.sort();
if covers(&params, &types) && query_action_match(&actions[index], query) {
output.push(index);
}
}
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
// Test the query_text_match function
fn test_query_text_match() {
let text = "Weather in $place";
assert!(query_text_match("Weather", text), "First word didn't match");
assert!(query_text_match("weather", text), "Lowercase word failed");
assert!(query_text_match("Wea", text), "Prefix failed");
assert!(!query_text_match(text, "Wea"), "Long query failed");
assert!(query_text_match("In", text), "Middle word failed");
assert!(!query_text_match("", text), "Empty query failed");
assert!(query_text_match("Weather", ""), "Empty text failed");
}
#[test]
// Test the action_match function
fn test_action_match() {
let actions: Vec<Action> =
serde_json::from_str(include_str!("../test_data/test_actions.json")).unwrap();
let types = vec!["https://schema.org/Place"];
let matches = action_match("weather", &actions, &types);
assert_eq!(matches.len(), 1);
assert_eq!(matches[0], 1);
let matches = action_match("neither", &actions, &types);
assert_eq!(matches.len(), 0);
let matches = action_match("Listen", &actions, &types);
assert_eq!(matches.len(), 0);
let types = vec!["https://schema.org/MusicGroup"];
let matches = action_match("Listen", &actions, &types);
assert_eq!(matches.len(), 1);
assert_eq!(matches[0], 0);
// match an action with no parameter types
let types = vec![];
let matches = action_match("nouns", &actions, &types);
assert_eq!(matches.len(), 1);
}
#[test]
// Test the covers function
fn test_covers() {
let mut types = vec![
"https://schema.org/MusicGroup",
"https://schema.org/Place",
"https://schema.org/Place",
];
types.sort();
let params = vec!["https://schema.org/MusicGroup"];
assert!(covers(&params, &types), "[MusicGroup] is not covered");
let params = vec!["https://schema.org/Song"];
assert!(!covers(&params, &types), "[Song] is covered");
let params = vec!["https://schema.org/Place"];
assert!(covers(&params, &types), "[Place] is not covered");
let params = vec!["https://schema.org/Place", "https://schema.org/Place"];
assert!(covers(&params, &types), "[Place, Place] is not covered");
let params = vec![
"https://schema.org/Place",
"https://schema.org/Place",
"https://schema.org/Place",
];
assert!(!covers(&params, &types), "[Place, Place, Place] is covered");
}
}