zebflow 0.11.14

Reactive web automation platform — pipelines, templates, and real-time collaboration in a single binary.
Documentation
use std::fs::File;
use std::io::BufReader;
use std::path::Path;

use serde::de::{DeserializeSeed, IgnoredAny, MapAccess, SeqAccess, Visitor};
use serde_json::Value;

pub fn stream_feature_collection_from_path(
    path: &Path,
    mut on_feature: impl FnMut(Value) -> Result<(), String>,
) -> Result<(), String> {
    let file = File::open(path)
        .map_err(|err| format!("failed reading geojson file {}: {err}", path.display()))?;
    let reader = BufReader::new(file);
    let mut de = serde_json::Deserializer::from_reader(reader);
    FeatureCollectionSeed {
        on_feature: &mut on_feature,
    }
    .deserialize(&mut de)
    .map_err(|err| format!("failed parsing geojson feature collection: {err}"))?;
    Ok(())
}

struct FeatureCollectionSeed<'a, F> {
    on_feature: &'a mut F,
}

impl<'de, F> DeserializeSeed<'de> for FeatureCollectionSeed<'_, F>
where
    F: FnMut(Value) -> Result<(), String>,
{
    type Value = ();

    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_map(self)
    }
}

impl<'de, F> Visitor<'de> for FeatureCollectionSeed<'_, F>
where
    F: FnMut(Value) -> Result<(), String>,
{
    type Value = ();

    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter.write_str("a GeoJSON FeatureCollection object")
    }

    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
    where
        A: MapAccess<'de>,
    {
        let mut saw_type = false;
        let mut saw_features = false;
        while let Some(key) = map.next_key::<String>()? {
            match key.as_str() {
                "type" => {
                    let value = map.next_value::<String>()?;
                    if value != "FeatureCollection" {
                        return Err(serde::de::Error::custom(
                            "source is not a GeoJSON FeatureCollection",
                        ));
                    }
                    saw_type = true;
                }
                "features" => {
                    map.next_value_seed(FeaturesSeed {
                        on_feature: self.on_feature,
                    })?;
                    saw_features = true;
                }
                _ => {
                    let _ = map.next_value::<IgnoredAny>()?;
                }
            }
        }
        if !saw_type {
            return Err(serde::de::Error::custom(
                "GeoJSON missing type=FeatureCollection",
            ));
        }
        if !saw_features {
            return Err(serde::de::Error::custom("GeoJSON missing features array"));
        }
        Ok(())
    }
}

struct FeaturesSeed<'a, F> {
    on_feature: &'a mut F,
}

impl<'de, F> DeserializeSeed<'de> for FeaturesSeed<'_, F>
where
    F: FnMut(Value) -> Result<(), String>,
{
    type Value = ();

    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_seq(self)
    }
}

impl<'de, F> Visitor<'de> for FeaturesSeed<'_, F>
where
    F: FnMut(Value) -> Result<(), String>,
{
    type Value = ();

    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter.write_str("a GeoJSON features array")
    }

    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
    where
        A: SeqAccess<'de>,
    {
        while let Some(feature) = seq.next_element::<Value>()? {
            (self.on_feature)(feature).map_err(serde::de::Error::custom)?;
        }
        Ok(())
    }
}