1use crate::Result;
7use futures::{Stream, StreamExt};
8use serde_json::Value;
9use stac::api::{ItemCollection, Search};
10use std::{future::Future, io::Write, pin::Pin};
11
12pub type ItemStream = Pin<Box<dyn Stream<Item = Result<Value>> + Send>>;
14
15pub type Finalize = Box<
18 dyn FnOnce(
19 Option<Value>,
20 Option<Value>,
21 u64,
22 ) -> Pin<Box<dyn Future<Output = Result<ItemCollection>> + Send>>
23 + Send,
24>;
25
26pub struct StreamedSearch {
28 pub items: ItemStream,
30 pub finalize: Finalize,
32}
33
34pub trait StreamSearch: Send + Sync {
40 fn stream_search(
42 &self,
43 search: Search,
44 max_items: Option<usize>,
45 context: bool,
46 self_href: Option<String>,
47 ) -> impl Future<Output = Result<StreamedSearch>> + Send;
48
49 fn write_search<W: Write>(
52 &self,
53 search: Search,
54 max_items: Option<usize>,
55 context: bool,
56 self_href: Option<String>,
57 writer: W,
58 pretty: bool,
59 ) -> impl Future<Output = Result<u64>> {
60 async move {
61 let StreamedSearch { items, finalize } = self
62 .stream_search(search, max_items, context, self_href)
63 .await?;
64 write_item_collection(writer, items, pretty, finalize).await
65 }
66 }
67}
68
69pub async fn write_item_collection<W, S, F, Fut>(
73 mut writer: W,
74 items: S,
75 pretty: bool,
76 finalize: F,
77) -> Result<u64>
78where
79 W: Write,
80 S: Stream<Item = Result<Value>>,
81 F: FnOnce(Option<Value>, Option<Value>, u64) -> Fut,
82 Fut: Future<Output = Result<ItemCollection>>,
83{
84 writer.write_all(if pretty {
85 b"{\n \"type\": \"FeatureCollection\",\n \"features\": ["
86 } else {
87 b"{\"type\":\"FeatureCollection\",\"features\":["
88 })?;
89
90 futures::pin_mut!(items);
91 let mut first: Option<Value> = None;
92 let mut pending: Option<Value> = None;
93 let mut count: u64 = 0;
94 while let Some(item) = items.next().await {
95 let item = item?;
96 if let Some(previous) = pending.take() {
97 write_element(&mut writer, &previous, count, pretty)?;
98 count += 1;
99 } else {
100 first = Some(item.clone());
101 }
102 pending = Some(item);
103 }
104 if let Some(last) = &pending {
105 write_element(&mut writer, last, count, pretty)?;
106 count += 1;
107 }
108 writer.write_all(if pretty && count > 0 { b"\n ]" } else { b"]" })?;
109
110 let mut collection = finalize(first, pending, count).await?;
114 collection.number_returned = Some(count);
115 let value = serde_json::to_value(&collection)?;
116 let members: serde_json::Map<String, Value> = value
117 .as_object()
118 .expect("an ItemCollection serializes to a JSON object")
119 .iter()
120 .filter(|(key, _)| key.as_str() != "type" && key.as_str() != "features")
121 .map(|(key, value)| (key.clone(), value.clone()))
122 .collect();
123 if !members.is_empty() {
124 let object = if pretty {
125 serde_json::to_string_pretty(&Value::Object(members))?
126 } else {
127 serde_json::to_string(&Value::Object(members))?
128 };
129 let inner = object
130 .strip_prefix('{')
131 .and_then(|rest| rest.strip_suffix('}'))
132 .expect("serde_json serializes an object with braces");
133 writer.write_all(b",")?;
134 writer.write_all(inner.trim_end().as_bytes())?;
135 }
136
137 writer.write_all(if pretty { b"\n}" } else { b"}" })?;
138 Ok(count)
139}
140
141fn write_element<W: Write>(writer: &mut W, item: &Value, index: u64, pretty: bool) -> Result<()> {
144 if pretty {
145 writer.write_all(if index == 0 { b"\n" } else { b",\n" })?;
146 let element = serde_json::to_string_pretty(item)?;
147 for (line_index, line) in element.lines().enumerate() {
148 if line_index > 0 {
149 writer.write_all(b"\n")?;
150 }
151 writer.write_all(b" ")?;
153 writer.write_all(line.as_bytes())?;
154 }
155 } else {
156 if index > 0 {
157 writer.write_all(b",")?;
158 }
159 serde_json::to_writer(&mut *writer, item)?;
160 }
161 Ok(())
162}
163
164#[cfg(test)]
165mod tests {
166 use super::write_item_collection;
167 use futures::stream;
168 use serde_json::Value;
169 use stac::{Item, Link, api::ItemCollection};
170
171 fn items(n: usize) -> (Vec<Value>, Vec<stac::api::Item>) {
174 let api: Vec<stac::api::Item> = (0..n)
175 .map(|i| Item::new(format!("item-{i}")).try_into().unwrap())
176 .collect();
177 let values = api
178 .iter()
179 .map(|i| serde_json::to_value(i).unwrap())
180 .collect();
181 (values, api)
182 }
183
184 async fn run(
185 values: Vec<Value>,
186 links: Vec<Link>,
187 matched: Option<u64>,
188 pretty: bool,
189 ) -> Vec<u8> {
190 let footer_links = links;
191 let mut buf = Vec::new();
192 write_item_collection(
193 &mut buf,
194 stream::iter(values.into_iter().map(Ok)),
195 pretty,
196 |_first, _last, _count| async move {
197 let mut collection = ItemCollection::new(Vec::<stac::api::Item>::new()).unwrap();
198 collection.links = footer_links;
199 collection.number_matched = matched;
200 Ok(collection)
201 },
202 )
203 .await
204 .unwrap();
205 buf
206 }
207
208 #[tokio::test]
209 async fn byte_identical_to_buffered() {
210 let links = vec![Link::new("http://example.com/next?token=abc", "next")];
211 for n in [0usize, 1, 2, 5] {
212 for pretty in [false, true] {
213 let matched = Some(n as u64 + 100);
214 let (values, api) = items(n);
215 let got = run(values, links.clone(), matched, pretty).await;
216
217 let mut want_ic = ItemCollection::new(api).unwrap();
218 want_ic.links = links.clone();
219 want_ic.number_matched = matched;
220 let want = if pretty {
221 serde_json::to_vec_pretty(&want_ic).unwrap()
222 } else {
223 serde_json::to_vec(&want_ic).unwrap()
224 };
225 assert_eq!(
226 String::from_utf8(got).unwrap(),
227 String::from_utf8(want).unwrap(),
228 "n={n} pretty={pretty}"
229 );
230 }
231 }
232 }
233}