geojson/
feature_writer.rs

1use crate::ser::to_feature_writer;
2use crate::{Error, Feature, Result};
3
4use serde::Serialize;
5use std::io::Write;
6
7#[derive(PartialEq)]
8enum State {
9    New,
10    WritingFeatures,
11    WritingForeignMembers,
12    Finished,
13}
14
15/// Write Features to a FeatureCollection
16pub struct FeatureWriter<W: Write> {
17    writer: W,
18    state: State,
19}
20
21impl<W: Write> FeatureWriter<W> {
22    /// Create a FeatureWriter from the given `writer`.
23    ///
24    /// To append features from your custom structs, use [`FeatureWriter::serialize`].
25    ///
26    /// To append features from [`Feature`] use [`FeatureWriter::write_feature`].
27    ///
28    /// To write a foreign member, use [`FeatureWriter::write_foreign_member`] before appending any
29    /// features.
30    pub fn from_writer(writer: W) -> Self {
31        Self {
32            writer,
33            state: State::New,
34        }
35    }
36
37    /// Write a [`crate::Feature`] struct to the output stream. If you'd like to
38    /// serialize your own custom structs, see [`FeatureWriter::serialize`] instead.
39    pub fn write_feature(&mut self, feature: &Feature) -> Result<()> {
40        match self.state {
41            State::Finished => {
42                return Err(Error::InvalidWriterState(
43                    "cannot write another Feature when writer has already finished",
44                ))
45            }
46            State::New => {
47                self.write_prefix()?;
48                self.state = State::WritingFeatures;
49            }
50            State::WritingFeatures => {
51                self.write_str(",")?;
52            }
53            State::WritingForeignMembers => {
54                self.write_str(r#" "features": ["#)?;
55                self.state = State::WritingFeatures;
56            }
57        }
58        serde_json::to_writer(&mut self.writer, feature)?;
59        Ok(())
60    }
61
62    /// Serialize your own custom struct to the features of a FeatureCollection using the
63    /// [`serde`] crate.
64    ///
65    /// # Examples
66    ///
67    /// Your struct must implement or derive [`serde::Serialize`].
68    ///
69    /// If you have enabled the `geo-types` feature, which is enabled by default, you can
70    /// serialize directly from a useful geometry type.
71    ///
72    /// ```rust,ignore
73    /// use geojson::{FeatureWriter, ser::serialize_geometry};
74    ///
75    /// #[derive(serde::Serialize)]
76    /// struct MyStruct {
77    ///     #[serde(serialize_with = "serialize_geometry")]
78    ///     geometry: geo_types::Point<f64>,
79    ///     name: String,
80    ///     age: u64,
81    /// }
82    /// ```
83    ///
84    /// Then you can serialize the FeatureCollection directly from your type.
85    #[cfg_attr(feature = "geo-types", doc = "```")]
86    #[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
87    /// #
88    /// # use geojson::{FeatureWriter, ser::serialize_geometry};
89    /// #
90    /// # #[derive(serde::Serialize)]
91    /// # struct MyStruct {
92    /// #     #[serde(serialize_with = "serialize_geometry")]
93    /// #     geometry: geo_types::Point<f64>,
94    /// #     name: String,
95    /// #     age: u64,
96    /// # }
97    ///
98    /// let dinagat = MyStruct {
99    ///     geometry: geo_types::point!(x: 125.6, y: 10.1),
100    ///     name: "Dinagat Islands".to_string(),
101    ///     age: 123
102    /// };
103    ///
104    /// let neverland = MyStruct {
105    ///     geometry: geo_types::point!(x: 2.3, y: 4.5),
106    ///     name: "Neverland".to_string(),
107    ///     age: 456
108    /// };
109    ///
110    /// let mut output: Vec<u8> = vec![];
111    /// {
112    ///     let io_writer = std::io::BufWriter::new(&mut output);
113    ///     let mut feature_writer = FeatureWriter::from_writer(io_writer);
114    ///     feature_writer.serialize(&dinagat).unwrap();
115    ///     feature_writer.serialize(&neverland).unwrap();
116    /// }
117    ///
118    /// let expected_output = r#"{
119    ///     "type": "FeatureCollection",
120    ///     "features": [
121    ///         {
122    ///            "type": "Feature",
123    ///            "geometry": { "type": "Point", "coordinates": [125.6, 10.1] },
124    ///            "properties": {
125    ///              "name": "Dinagat Islands",
126    ///              "age": 123
127    ///            }
128    ///         },
129    ///         {
130    ///            "type": "Feature",
131    ///            "geometry": { "type": "Point", "coordinates": [2.3, 4.5] },
132    ///            "properties": {
133    ///              "name": "Neverland",
134    ///              "age": 456
135    ///            }
136    ///          }
137    ///    ]
138    /// }"#.as_bytes();
139    ///
140    /// fn assert_eq_json(bytes_1: &[u8], bytes_2: &[u8]) {
141    ///     // check for semantic equality, discarding any formatting/whitespace differences
142    ///     let json_1: serde_json::Value = serde_json::from_slice(bytes_1).unwrap();
143    ///     let json_2: serde_json::Value = serde_json::from_slice(bytes_2).unwrap();
144    ///     assert_eq!(json_1, json_2);
145    /// }
146    ///
147    /// assert_eq_json(expected_output, &output);
148    /// ```
149    ///
150    /// If you're not using [`geo-types`](geo_types), you can deserialize to a `geojson::Geometry` instead.
151    /// ```rust,ignore
152    /// use serde::Deserialize;
153    /// #[derive(Deserialize)]
154    /// struct MyStruct {
155    ///     geometry: geojson::Geometry,
156    ///     name: String,
157    ///     age: u64,
158    /// }
159    /// ```
160    pub fn serialize<S: Serialize>(&mut self, value: &S) -> Result<()> {
161        match self.state {
162            State::Finished => {
163                return Err(Error::InvalidWriterState(
164                    "cannot serialize another record when writer has already finished",
165                ))
166            }
167            State::New => {
168                self.write_prefix()?;
169                self.state = State::WritingFeatures;
170            }
171            State::WritingFeatures => {
172                self.write_str(",")?;
173            }
174            State::WritingForeignMembers => {
175                self.write_str(r#" "features": ["#)?;
176                self.state = State::WritingFeatures;
177            }
178        }
179        to_feature_writer(&mut self.writer, value)
180    }
181
182    /// Write a [foreign member](https://datatracker.ietf.org/doc/html/rfc7946#section-6) to the
183    /// output stream. This must be done before appending any features.
184    pub fn write_foreign_member<T: ?Sized + Serialize>(
185        &mut self,
186        key: &str,
187        value: &T,
188    ) -> Result<()> {
189        match self.state {
190            State::Finished => Err(Error::InvalidWriterState(
191                "cannot write foreign member when writer has already finished",
192            )),
193            State::New => {
194                self.write_str(r#"{ "type": "FeatureCollection", "#)?;
195                write!(self.writer, "\"{key}\": ")?;
196                serde_json::to_writer(&mut self.writer, value)?;
197                self.write_str(",")?;
198
199                self.state = State::WritingForeignMembers;
200                Ok(())
201            }
202            State::WritingFeatures => Err(Error::InvalidWriterState(
203                "must write foreign members before any features",
204            )),
205            State::WritingForeignMembers => {
206                write!(self.writer, "\"{key}\": ")?;
207                serde_json::to_writer(&mut self.writer, value)?;
208                self.write_str(",")?;
209                Ok(())
210            }
211        }
212    }
213
214    /// Writes the closing syntax for the FeatureCollection.
215    ///
216    /// You shouldn't normally need to call this manually, as the writer will close itself upon
217    /// being dropped.
218    pub fn finish(&mut self) -> Result<()> {
219        match self.state {
220            State::Finished => {
221                return Err(Error::InvalidWriterState(
222                    "cannot finish writer - it's already finished",
223                ))
224            }
225            State::New => {
226                self.state = State::Finished;
227                self.write_prefix()?;
228                self.write_suffix()?;
229            }
230            State::WritingFeatures | State::WritingForeignMembers => {
231                self.state = State::Finished;
232                self.write_suffix()?;
233            }
234        }
235        Ok(())
236    }
237
238    /// Flush the underlying writer buffer.
239    ///
240    /// You shouldn't normally need to call this manually, as the writer will flush itself upon
241    /// being dropped.
242    pub fn flush(&mut self) -> Result<()> {
243        Ok(self.writer.flush()?)
244    }
245
246    fn write_prefix(&mut self) -> Result<()> {
247        self.write_str(r#"{ "type": "FeatureCollection", "features": ["#)
248    }
249
250    fn write_suffix(&mut self) -> Result<()> {
251        self.write_str("]}")
252    }
253
254    fn write_str(&mut self, text: &str) -> Result<()> {
255        self.writer.write_all(text.as_bytes())?;
256        Ok(())
257    }
258}
259
260impl<W: Write> Drop for FeatureWriter<W> {
261    fn drop(&mut self) {
262        if self.state != State::Finished {
263            _ = self.finish().map_err(|e| {
264               log::error!("FeatureWriter errored while finishing in Drop impl. To handle errors like this, explicitly call `FeatureWriter::finish`. Error: {}", e);
265            });
266        }
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273    use crate::JsonValue;
274
275    use serde_json::json;
276
277    // an example struct that we want to serialize
278    #[derive(Serialize)]
279    struct MyRecord {
280        geometry: crate::Geometry,
281        name: String,
282        age: u64,
283    }
284
285    #[test]
286    fn write_empty() {
287        let mut buffer: Vec<u8> = vec![];
288        {
289            let mut writer = FeatureWriter::from_writer(&mut buffer);
290            writer.finish().unwrap();
291        }
292
293        let expected = json!({
294            "type": "FeatureCollection",
295            "features": []
296        });
297
298        let actual_json: JsonValue = serde_json::from_slice(&buffer).unwrap();
299        assert_eq!(actual_json, expected);
300    }
301
302    #[test]
303    fn finish_on_drop() {
304        let mut buffer: Vec<u8> = vec![];
305        {
306            _ = FeatureWriter::from_writer(&mut buffer);
307        }
308
309        let expected = json!({
310            "type": "FeatureCollection",
311            "features": []
312        });
313
314        let actual_json: JsonValue = serde_json::from_slice(&buffer).unwrap();
315        assert_eq!(actual_json, expected);
316    }
317
318    #[test]
319    fn write_feature() {
320        let mut buffer: Vec<u8> = vec![];
321        {
322            let mut writer = FeatureWriter::from_writer(&mut buffer);
323
324            let record_1 = {
325                let mut props = serde_json::Map::new();
326                props.insert("name".to_string(), "Mishka".into());
327                props.insert("age".to_string(), 12.into());
328
329                Feature {
330                    bbox: None,
331                    geometry: Some(crate::Geometry::from(crate::Value::Point(vec![1.1, 1.2]))),
332                    id: None,
333                    properties: Some(props),
334                    foreign_members: None,
335                }
336            };
337
338            let record_2 = {
339                let mut props = serde_json::Map::new();
340                props.insert("name".to_string(), "Jane".into());
341                props.insert("age".to_string(), 22.into());
342
343                Feature {
344                    bbox: None,
345                    geometry: Some(crate::Geometry::from(crate::Value::Point(vec![2.1, 2.2]))),
346                    id: None,
347                    properties: Some(props),
348                    foreign_members: None,
349                }
350            };
351
352            writer.write_feature(&record_1).unwrap();
353            writer.write_feature(&record_2).unwrap();
354            writer.flush().unwrap();
355        }
356
357        let expected = json!({
358            "type": "FeatureCollection",
359            "features": [
360                {
361                    "type": "Feature",
362                    "geometry": { "type": "Point", "coordinates": [1.1, 1.2] },
363                    "properties": { "name": "Mishka", "age": 12
364                    }
365                },
366                {
367                    "type": "Feature",
368                    "geometry": { "type": "Point", "coordinates": [2.1, 2.2] },
369                    "properties": {
370                        "name": "Jane",
371                        "age": 22
372                    }
373                }
374            ]
375        });
376
377        let actual_json: JsonValue = serde_json::from_slice(&buffer).expect("valid json");
378        assert_eq!(actual_json, expected)
379    }
380
381    #[test]
382    fn serialize() {
383        let mut buffer: Vec<u8> = vec![];
384        {
385            let mut writer = FeatureWriter::from_writer(&mut buffer);
386            let record_1 = MyRecord {
387                geometry: crate::Geometry::from(crate::Value::Point(vec![1.1, 1.2])),
388                name: "Mishka".to_string(),
389                age: 12,
390            };
391            let record_2 = MyRecord {
392                geometry: crate::Geometry::from(crate::Value::Point(vec![2.1, 2.2])),
393                name: "Jane".to_string(),
394                age: 22,
395            };
396            writer.serialize(&record_1).unwrap();
397            writer.serialize(&record_2).unwrap();
398            writer.flush().unwrap();
399        }
400
401        let expected = json!({
402            "type": "FeatureCollection",
403            "features": [
404                {
405                    "type": "Feature",
406                    "geometry": { "type": "Point", "coordinates": [1.1, 1.2] },
407                    "properties": { "name": "Mishka", "age": 12
408                    }
409                },
410                {
411                    "type": "Feature",
412                    "geometry": { "type": "Point", "coordinates": [2.1, 2.2] },
413                    "properties": {
414                        "name": "Jane",
415                        "age": 22
416                    }
417                }
418            ]
419        });
420
421        let actual_json: JsonValue = serde_json::from_slice(&buffer).expect("valid json");
422        assert_eq!(actual_json, expected)
423    }
424
425    #[test]
426    fn write_foreign_members() {
427        let mut buffer: Vec<u8> = vec![];
428        {
429            let mut writer = FeatureWriter::from_writer(&mut buffer);
430
431            writer.write_foreign_member("extra", "string").unwrap();
432            writer.write_foreign_member("list", &[1, 2, 3]).unwrap();
433            writer
434                .write_foreign_member("nested", &json!({"foo": "bar"}))
435                .unwrap();
436
437            let record_1 = {
438                let mut props = serde_json::Map::new();
439                props.insert("name".to_string(), "Mishka".into());
440                props.insert("age".to_string(), 12.into());
441
442                Feature {
443                    bbox: None,
444                    geometry: Some(crate::Geometry::from(crate::Value::Point(vec![1.1, 1.2]))),
445                    id: None,
446                    properties: Some(props),
447                    foreign_members: None,
448                }
449            };
450
451            writer.write_feature(&record_1).unwrap();
452            writer.flush().unwrap();
453        }
454
455        let expected = json!({
456            "type": "FeatureCollection",
457            "extra": "string",
458            "list": [1, 2, 3],
459            "nested": {
460                "foo": "bar",
461            },
462            "features": [
463                {
464                    "type": "Feature",
465                    "geometry": { "type": "Point", "coordinates": [1.1, 1.2] },
466                    "properties": { "name": "Mishka", "age": 12
467                    }
468                },
469            ]
470        });
471
472        println!("{}", String::from_utf8(buffer.clone()).unwrap());
473        let actual_json: JsonValue = serde_json::from_slice(&buffer).expect("valid json");
474        assert_eq!(actual_json, expected)
475    }
476
477    #[cfg(feature = "geo-types")]
478    mod test_geo_types {
479        use super::*;
480        use crate::ser::serialize_geometry;
481
482        // an example struct that we want to serialize
483        #[derive(Serialize)]
484        struct MyGeoRecord {
485            #[serde(serialize_with = "serialize_geometry")]
486            geometry: geo_types::Point,
487            name: String,
488            age: u64,
489        }
490
491        #[test]
492        fn serialize_geo_types() {
493            let mut buffer: Vec<u8> = vec![];
494            {
495                let mut writer = FeatureWriter::from_writer(&mut buffer);
496                let record_1 = MyGeoRecord {
497                    geometry: geo_types::point!(x: 1.1, y: 1.2),
498                    name: "Mishka".to_string(),
499                    age: 12,
500                };
501                let record_2 = MyGeoRecord {
502                    geometry: geo_types::point!(x: 2.1, y: 2.2),
503                    name: "Jane".to_string(),
504                    age: 22,
505                };
506                writer.serialize(&record_1).unwrap();
507                writer.serialize(&record_2).unwrap();
508                writer.flush().unwrap();
509            }
510
511            let expected = json!({
512                "type": "FeatureCollection",
513                "features": [
514                    {
515                        "type": "Feature",
516                        "geometry": { "type": "Point", "coordinates": [1.1, 1.2] },
517                        "properties": {
518                            "name": "Mishka",
519                            "age": 12
520                        }
521                    },
522                    {
523                        "type": "Feature",
524                        "geometry": { "type": "Point", "coordinates": [2.1, 2.2] },
525                        "properties": {
526                            "name": "Jane",
527                            "age": 22
528                        }
529                    }
530                ]
531            });
532
533            let actual_json: JsonValue = serde_json::from_slice(&buffer).expect("valid json");
534            assert_eq!(actual_json, expected)
535        }
536    }
537}