• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-engine / geoengine / 16354606328

17 Jul 2025 07:51PM UTC coverage: 88.876%. First build
16354606328

Pull #1061

github

web-flow
Merge 019f6a1a8 into b8910c811
Pull Request #1061: feat(operators): skip empty tiles and merge masks in onnx; remove trace/debug in release mode

415 of 570 new or added lines in 44 files covered. (72.81%)

111626 of 125597 relevant lines covered (88.88%)

80287.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.32
/datatypes/src/machine_learning.rs
1
use crate::{
2
    dataset::{SYSTEM_NAMESPACE, is_invalid_name_char},
3
    raster::{GridShape2D, GridSize},
4
};
5
use postgres_types::{FromSql, ToSql};
6
use serde::{Deserialize, Serialize, de::Visitor};
7
use snafu::Snafu;
8
use std::{fmt::Display, str::FromStr};
9
use strum::IntoStaticStr;
10

11
const NAME_DELIMITER: char = ':';
12

NEW
13
#[derive(Debug, Clone, Hash, Eq, PartialEq, ToSql, FromSql)]
×
14
pub struct MlModelName {
15
    pub namespace: Option<String>,
16
    pub name: String,
17
}
18

19
#[derive(Snafu, IntoStaticStr, Debug)]
20
#[snafu(visibility(pub(crate)))]
21
#[snafu(context(suffix(false)))] // disables default `Snafu` suffix
22
pub enum MlModelNameError {
23
    #[snafu(display("MlModelName is empty"))]
24
    IsEmpty,
25
    #[snafu(display("invalid character '{invalid_char}' in named model"))]
26
    InvalidCharacter { invalid_char: String },
27
    #[snafu(display("ml model name must consist of at most two parts"))]
28
    TooManyParts,
29
}
30

31
impl MlModelName {
32
    /// Canonicalize a name that reflects the system namespace and model.
33
    fn canonicalize<S: Into<String> + PartialEq<&'static str>>(
7✔
34
        name: S,
7✔
35
        system_name: &'static str,
7✔
36
    ) -> Option<String> {
7✔
37
        if name == system_name {
7✔
38
            None
1✔
39
        } else {
40
            Some(name.into())
6✔
41
        }
42
    }
7✔
43

44
    pub fn new_unchecked<S: Into<String>>(namespace: Option<String>, name: S) -> Self {
2✔
45
        Self {
2✔
46
            namespace,
2✔
47
            name: name.into(),
2✔
48
        }
2✔
49
    }
2✔
50

51
    pub fn try_new<S: Into<String>, N: Into<String>>(
11✔
52
        namespace: Option<N>,
11✔
53
        name: S,
11✔
54
    ) -> Result<Self, MlModelNameError> {
11✔
55
        let name: String = name.into();
11✔
56
        let namespace: Option<String> = namespace.map(Into::into);
11✔
57

58
        if name.is_empty() {
11✔
NEW
59
            return Err(MlModelNameError::IsEmpty);
×
60
        }
11✔
61

62
        if let Some(c) = name.matches(is_invalid_name_char).next() {
11✔
NEW
63
            return Err(MlModelNameError::InvalidCharacter {
×
NEW
64
                invalid_char: c.to_string(),
×
NEW
65
            });
×
66
        }
11✔
67

68
        let ns = match namespace {
11✔
69
            None => Ok(None),
4✔
70
            Some(n) if n.is_empty() => Ok(None),
7✔
71
            Some(n) => {
7✔
72
                if let Some(c) = n.matches(is_invalid_name_char).next() {
7✔
NEW
73
                    Err(MlModelNameError::InvalidCharacter {
×
NEW
74
                        invalid_char: c.to_string(),
×
NEW
75
                    })
×
76
                } else {
77
                    Ok(Self::canonicalize(n, SYSTEM_NAMESPACE))
7✔
78
                }
79
            }
NEW
80
        }?;
×
81

82
        Ok(Self {
11✔
83
            namespace: ns,
11✔
84
            name,
11✔
85
        })
11✔
86
    }
11✔
87
}
88

89
impl Serialize for MlModelName {
90
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
8✔
91
    where
8✔
92
        S: serde::Serializer,
8✔
93
    {
94
        let serialized = self.to_string();
8✔
95

96
        serializer.serialize_str(&serialized)
8✔
97
    }
8✔
98
}
99

100
impl<'de> Deserialize<'de> for MlModelName {
101
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4✔
102
    where
4✔
103
        D: serde::Deserializer<'de>,
4✔
104
    {
105
        deserializer.deserialize_str(MlModelNameDeserializeVisitor)
4✔
106
    }
4✔
107
}
108

109
impl FromStr for MlModelName {
110
    type Err = MlModelNameError;
111

112
    fn from_str(s: &str) -> Result<Self, Self::Err> {
9✔
113
        let split: Vec<_> = s.split(NAME_DELIMITER).collect();
9✔
114

115
        match split[..] {
9✔
116
            [] => Err(MlModelNameError::IsEmpty),
9✔
117
            [name] => Self::try_new(None::<&str>, name),
3✔
118
            [ns, name] => Self::try_new(Some(ns), name),
6✔
NEW
119
            _ => Err(MlModelNameError::TooManyParts),
×
120
        }
121
    }
9✔
122
}
123

124
struct MlModelNameDeserializeVisitor;
125

126
impl Visitor<'_> for MlModelNameDeserializeVisitor {
127
    type Value = MlModelName;
128

129
    /// always keep in sync with [`is_allowed_name_char`]
130
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
×
131
        write!(
×
132
            formatter,
×
133
            "a string consisting of a namespace and name name, separated by a colon, only using alphanumeric characters, underscores & dashes"
×
134
        )
135
    }
×
136

137
    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
4✔
138
    where
4✔
139
        E: serde::de::Error,
4✔
140
    {
141
        MlModelName::from_str(s).map_err(|e| E::custom(e.to_string()))
4✔
142
    }
4✔
143
}
144

145
impl Display for MlModelName {
146
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12✔
147
        let d = NAME_DELIMITER;
12✔
148
        let s = match (&self.namespace, &self.name) {
12✔
149
            (None, name) => name.to_string(),
7✔
150
            (Some(namespace), name) => {
5✔
151
                format!("{namespace}{d}{name}")
5✔
152
            }
153
        };
154

155
        f.write_str(&s)
12✔
156
    }
12✔
157
}
158

159
/// A struct describing tensor shape for `MlModelMetadata`
NEW
160
#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize, Serialize, ToSql, FromSql)]
×
161
pub struct MlTensorShape3D {
162
    pub y: u32,
163
    pub x: u32,
164
    pub bands: u32, // TODO: named attributes?
165
}
166

167
impl MlTensorShape3D {
168
    pub fn new_y_x_bands(y: u32, x: u32, bands: u32) -> Self {
5✔
169
        Self { y, x, bands }
5✔
170
    }
5✔
171

172
    pub fn new_single_pixel_bands(bands: u32) -> Self {
10✔
173
        Self { y: 1, x: 1, bands }
10✔
174
    }
10✔
175

176
    pub fn new_single_pixel_single_band() -> Self {
4✔
177
        Self::new_single_pixel_bands(1)
4✔
178
    }
4✔
179

180
    pub fn axis_size_y(&self) -> u32 {
12✔
181
        self.y
12✔
182
    }
12✔
183

184
    pub fn axis_size_x(&self) -> u32 {
12✔
185
        self.x
12✔
186
    }
12✔
187

188
    pub fn yx_matches_tile_shape(&self, tile_shape: &GridShape2D) -> bool {
4✔
189
        self.axis_size_x() as usize == tile_shape.axis_size_x()
4✔
190
            && self.axis_size_y() as usize == tile_shape.axis_size_y()
4✔
191
    }
4✔
192
}
193

194
#[cfg(test)]
195
mod tests {
196
    use super::*;
197

198
    #[test]
199
    fn ml_model_name_from_str() {
1✔
200
        const ML_MODEL_NAME: &str = "myModelName";
201
        let mln = MlModelName::from_str(ML_MODEL_NAME).unwrap();
1✔
202
        assert_eq!(mln.name, ML_MODEL_NAME);
1✔
203
        assert!(mln.namespace.is_none());
1✔
204
    }
1✔
205

206
    #[test]
207
    fn ml_model_name_from_str_prefixed() {
1✔
208
        const ML_MODEL_NAME: &str = "d5328854-6190-4af9-ad69-4e74b0961ac9:myModelName";
209
        let mln = MlModelName::from_str(ML_MODEL_NAME).unwrap();
1✔
210
        assert_eq!(mln.name, "myModelName".to_string());
1✔
211
        assert_eq!(
1✔
212
            mln.namespace,
213
            Some("d5328854-6190-4af9-ad69-4e74b0961ac9".to_string())
1✔
214
        );
215
    }
1✔
216

217
    #[test]
218
    fn ml_model_name_from_str_system() {
1✔
219
        const ML_MODEL_NAME: &str = "_:myModelName";
220
        let mln = MlModelName::from_str(ML_MODEL_NAME).unwrap();
1✔
221
        assert_eq!(mln.name, "myModelName".to_string());
1✔
222
        assert!(mln.namespace.is_none());
1✔
223
    }
1✔
224
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc