popgetter_core/
data_request_spec.rs

1use itertools::Itertools;
2use nonempty::nonempty;
3use serde::{Deserialize, Serialize};
4
5use crate::geo::BBox;
6use crate::search::{
7    CaseSensitivity, DownloadParams, GeometryLevel, MatchType, MetricId, Params, SearchConfig,
8    SearchContext, SearchParams, SearchText, YearRange,
9};
10
11#[derive(Serialize, Deserialize, Clone, Debug, Default)]
12pub struct DataRequestSpec {
13    pub geometry: Option<GeometrySpec>,
14    pub region: Vec<RegionSpec>,
15    pub metrics: Vec<MetricSpec>,
16    pub years: Option<Vec<String>>,
17}
18
19// Since `DataRequestSpec` contains parameters relevant to both `SearchParams` and `DownloadParams`,
20// the conversion is implemented for `Params`.
21impl TryFrom<DataRequestSpec> for Params {
22    type Error = anyhow::Error;
23    fn try_from(value: DataRequestSpec) -> Result<Self, Self::Error> {
24        // TODO: handle MetricSpec::DataProduct variant
25        Ok(Self {
26            search: SearchParams {
27                // TODO: consider defaults during implementing [#81](https://github.com/Urban-Analytics-Technology-Platform/popgetter-cli/issues/81)
28                text: value
29                    .metrics
30                    .iter()
31                    .filter_map(|metric| match metric {
32                        MetricSpec::MetricText(text) => Some(SearchText {
33                            text: text.clone(),
34                            context: nonempty![
35                                SearchContext::HumanReadableName,
36                                SearchContext::Hxl,
37                                SearchContext::Description
38                            ],
39                            config: SearchConfig {
40                                match_type: MatchType::Regex,
41                                case_sensitivity: CaseSensitivity::Insensitive,
42                            },
43                        }),
44                        _ => None,
45                    })
46                    .collect_vec(),
47                year_range: if let Some(v) = value.years {
48                    Some(
49                        v.iter()
50                            .map(|year| year.parse::<YearRange>())
51                            .collect::<Result<Vec<_>, anyhow::Error>>()?,
52                    )
53                } else {
54                    None
55                },
56                metric_id: value
57                    .metrics
58                    .iter()
59                    .filter_map(|metric| match metric {
60                        MetricSpec::MetricId(m) => Some(m.clone()),
61                        _ => None,
62                    })
63                    .collect_vec(),
64                geometry_level: value.geometry.as_ref().and_then(|geometry| {
65                    geometry
66                        .geometry_level
67                        .to_owned()
68                        .map(|geometry_level| GeometryLevel {
69                            value: geometry_level,
70                            config: SearchConfig {
71                                match_type: MatchType::Exact,
72                                case_sensitivity: CaseSensitivity::Insensitive,
73                            },
74                        })
75                }),
76                source_data_release: None,
77                source_download_url: None,
78                data_publisher: None,
79                country: None,
80                source_metric_id: None,
81                region_spec: value.region.clone(),
82            },
83            download: DownloadParams {
84                include_geoms: value.geometry.unwrap_or_default().include_geoms,
85                region_spec: value.region,
86            },
87        })
88    }
89}
90
91#[derive(Clone, Serialize, Deserialize, Debug)]
92pub enum MetricSpec {
93    MetricId(MetricId),
94    MetricText(String),
95    DataProduct(String),
96}
97
98#[derive(Serialize, Deserialize, Debug, Clone)]
99pub struct GeometrySpec {
100    pub geometry_level: Option<String>,
101    pub include_geoms: bool,
102}
103
104impl Default for GeometrySpec {
105    fn default() -> Self {
106        Self {
107            include_geoms: true,
108            geometry_level: None,
109        }
110    }
111}
112
113#[derive(Clone, Serialize, Deserialize, Debug)]
114pub enum RegionSpec {
115    BoundingBox(BBox),
116    Polygon(Polygon),
117    NamedArea(String),
118}
119
120impl RegionSpec {
121    pub fn bbox(&self) -> Option<BBox> {
122        match self {
123            RegionSpec::BoundingBox(bbox) => Some(bbox.clone()),
124            _ => None,
125        }
126    }
127}
128
129#[derive(Clone, Serialize, Deserialize, Debug)]
130pub struct Polygon;
131
132#[cfg(test)]
133mod tests {}