1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
use crate::select2::grouped::{Group, Groupable, GroupableRecord, GroupedResults};
use crate::select2::{Pagination, Record, Request};
use std::cmp::{Eq, PartialEq};
use std::collections::BTreeMap;
use std::fmt::{Debug, Display};
use std::hash::Hash;
use std::io::{Error, ErrorKind};
use std::string::ToString;
// -----------------------------------------------------------------------------
impl Request {
/// This function does not perform the `term` or `q` search for the client
/// request. The search much be performed by the caller using the
/// `search_select2` method. The search results are passed to this function
/// to build the response.
///
/// If no search is requested, the caller can pass the entire collection (in
/// the form of a slice) to this function to be processed into the `Select2`
/// format.
#[tracing::instrument(
level = "trace",
name = "build grouped results",
skip(self, search_results_keys, search_results_values)
)]
#[allow(clippy::option_if_let_else)]
pub fn grouped_response<
K: Clone + Debug + Display + Eq + Hash + PartialEq + ToString,
G: Groupable,
>(
&self,
items_per_page: &Option<usize>,
selected_record: &Option<String>,
search_results_keys: &[&K],
search_results_values: &[&G],
) -> Result<GroupedResults, Error> {
// Error checking. Ensure that there are the same number of keys and
// values:
if search_results_keys.len() != search_results_values.len() {
let error_message = format!(
"{} keys and {} values were supplied to `grouped_response`. \
the number of keys and values must be the same.",
search_results_keys.len(),
search_results_values.len(),
); // format!
tracing::error!("{}", error_message);
return Err(Error::new(ErrorKind::InvalidData, error_message));
} else if search_results_keys.is_empty() {
let error_message = "list of keys and values is empty. \
returning empty response."
.to_string();
tracing::debug!("{}", error_message);
return Ok(GroupedResults::default());
} // if
// Observe pagination. If the caller specifies a maximum number of items
// per page, then consider pagination turned on:
// self.request_type == Some("query_append".to_string())
let groupable_results: (bool, Vec<(&K, &G)>) = if let Some(items_per_page) = items_per_page
{
// Paginated response:
// Get the `page` number from the request:
let page: usize = self.page_number();
// This function works on the resolved output of a search, or the
// records dumped from a key-value store:
let paginated_results: Vec<(&K, &G)> = search_results_keys
// Iterate over each key:
.iter()
// Track the number of keys we've iterated over, so we can
// look-up the corresponding values from the
// `search_results_values` slice:
.enumerate()
// Skip records so we start at beginning of specified `page`:
.skip(items_per_page * (page - 1))
// Only take a page's worth of records:
.take(*items_per_page)
// Look-up the `Groupable` value from the enumeration or index:
.map(|(index, key)| (*key, search_results_values[index]))
// Collect all Select2 records into a `Vec<GroupableRecord>`:
.collect();
// Return pagination status and results to outer scope:
(
items_per_page * page < search_results_keys.len(),
paginated_results,
)
} else {
// Unpaginated response:
// This function works on the resolved output of a search, or the
// records dumped from a key-value store:
let unpaginated_results: Vec<(&K, &G)> = search_results_keys
// Iterate over each key:
.iter()
// Track the number of keys we've iterated over, so we can
// look-up the corresponding values from the
// `search_results_values` slice:
.enumerate()
// Look-up the `Groupable` value from the enumeration or index:
.map(|(index, key)| (*key, search_results_values[index]))
// Collect all select2 records into a `Vec<GroupableRecord>`:
.collect();
// Return pagination status and results to outer scope:
(false, unpaginated_results)
}; // if
// This `BTreeMap` is used to organize the records into their groups.
// The `key` represents the group, and the `value` represents the
// records in the group:
let mut grouped_results: BTreeMap<String, Vec<Record>> = BTreeMap::new();
// Iterate over the results and insert them into their respective
// groups:
groupable_results
.1
// Iterate over the results records:
.into_iter()
// For each record in the results:
.for_each(|(key, value)| {
// Convert the record from a `&G` into a `GroupableRecord`:
let groupable_record: GroupableRecord = value.record();
// Convert the `GroupableRecord` to a `Select2` `Record`:
let mut record = groupable_record.to_record(key);
// Check if the `selected_record` was set...
if let Some(selected_record) = selected_record {
// ...was set. Update record with comparison result and
// return record:
record.selected = record.id == *selected_record;
} // if
// Attempt to get mutuable reference to the group entry in
// the B-tree map:
match grouped_results.get_mut(&groupable_record.group) {
// If group exists in hash map, add record to the group:
Some(group) => group.push(record),
// If group does not exist, initialize with this record:
None => {
grouped_results.insert(groupable_record.group.clone(), vec![record]);
} // None
} // match
}); // for_each
// Convert `BTreeMap<String, Vec<Record>>` structure used to organize
// records into `Vec<Group>` which will be returned as the response:
let grouped_results: Vec<Group> = grouped_results
.into_iter()
.map(|(group, records)| Group {
text: group,
children: records,
}) // map
.collect();
// Return Select2 `GroupedResults` to caller:
Ok(GroupedResults {
results: grouped_results,
pagination: Pagination {
more: groupable_results.0,
}, // Pagination
}) // GroupedResults
} // fn
} // impl