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

geo-engine / geoengine / 24391614752

14 Apr 2026 09:30AM UTC coverage: 87.527% (+0.1%) from 87.39%
24391614752

push

github

web-flow
fix: openapi enum variant names (#1139)

* fix openapi enum variant names

* fix degenerated openapi enum names OneOf

263 of 267 new or added lines in 4 files covered. (98.5%)

6 existing lines in 3 files now uncovered.

114087 of 130345 relevant lines covered (87.53%)

496692.92 hits per line

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

97.46
/geoengine/services/src/api/model/processing_graphs/plots.rs
1
use crate::api::model::processing_graphs::source_parameters::{
2
    MultipleRasterOrSingleVectorSource, SingleVectorOrRasterSource,
3
};
4
use geoengine_macros::{api_operator, type_tag};
5
use ordered_float::NotNan;
6
use serde::{Deserialize, Serialize};
7
use utoipa::ToSchema;
8

9
/// The `Histogram` is a _plot operator_ that computes a histogram plot either over attributes of a vector dataset or values of a raster source.
10
/// The output is a plot in [Vega-Lite](https://vega.github.io/vega-lite/) specification.
11
///
12
/// For instance, you want to plot the data distribution of numeric attributes of a feature collection.
13
/// Then you can use a histogram with a suitable number of buckets to visualize and assess this.
14
///
15
/// ## Errors
16
///
17
/// The operator returns an error if the selected column (`columnName`) does not exist or is not numeric.
18
///
19
/// ## Notes
20
///
21
/// If `bounds` or `buckets` are not defined, the operator will determine these values by itself which requires processing the data twice.
22
///
23
/// If the `buckets` parameter is set to `squareRootChoiceRule`, the operator estimates it using the square root of the number of elements in the data.
24
///
25
#[api_operator(examples(json!({
30✔
26
    "type": "Histogram",
27
    "params": {
28
        "columnName": "foobar",
29
        "bounds": {
30
            "min": 5.0,
31
            "max": 10.0
32
        },
33
        "buckets": {
34
            "type": "number",
35
            "value": 15
36
        },
37
        "interactive": false
38
    },
39
    "sources": {
40
        "vector": {
41
            "type": "OgrSource",
42
            "params": {
43
                "data": "ndvi"
44
            }
45
        }
46
    }
47
})))]
48
pub struct Histogram {
49
    pub params: HistogramParameters,
50
    pub sources: SingleVectorOrRasterSource,
51
}
52

53
/// The parameter spec for `Histogram`
54
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
55
#[serde(rename_all = "camelCase")]
56
pub struct HistogramParameters {
57
    /// Name of the (numeric) vector attribute or raster band to compute the histogram on.
58
    #[schema(examples("temperature"))]
59
    pub column_name: String,
60
    /// If `data`, it computes the bounds of the underlying data.
61
    /// If `{ "min": ..., "max": ... }`, one can specify custom bounds.
62
    #[schema(examples(json!({ "min": 0.0, "max": 20.0 }), "data"))]
63
    pub bounds: HistogramBounds,
64
    /// The number of buckets. The value can be specified or calculated.
65
    #[schema(examples(json!({ "type": "number", "value": 20 })))]
66
    pub buckets: HistogramBuckets,
67
    /// Flag, if the histogram should have user interactions for a range selection. It is `false` by default.
68
    #[serde(default)]
69
    #[schema(examples(true))]
70
    pub interactive: bool,
71
}
72

73
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize, ToSchema)]
74
pub enum Data {
75
    #[default]
76
    #[serde(rename = "data")]
77
    Data,
78
}
79

80
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
81
#[serde(untagged)]
82
pub enum HistogramBounds {
83
    Data(Data),
84
    Values(HistogramBoundsValues),
85
}
86

87
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
88
pub struct HistogramBoundsValues {
89
    #[schema(value_type = f64)]
90
    pub min: NotNan<f64>,
91
    #[schema(value_type = f64)]
92
    pub max: NotNan<f64>,
93
}
94

95
fn default_max_number_of_buckets() -> u8 {
×
96
    20
×
97
}
×
98

99
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
100
#[serde(rename_all = "camelCase", untagged)]
101
#[schema(discriminator = "type")]
102
pub enum HistogramBuckets {
103
    Number(HistogramBucketsNumber),
104
    SquareRootChoiceRule(HistogramBucketsSquareRootChoiceRule),
105
}
106

107
#[type_tag(value = "number")]
108
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
109
#[serde(rename_all = "camelCase")]
110
pub struct HistogramBucketsNumber {
111
    pub value: u8,
112
}
113

114
#[type_tag(value = "squareRootChoiceRule")]
115
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
116
#[serde(rename_all = "camelCase")]
117
pub struct HistogramBucketsSquareRootChoiceRule {
118
    #[serde(default = "default_max_number_of_buckets")]
119
    pub max_number_of_buckets: u8,
120
}
121

122
impl TryFrom<Histogram> for geoengine_operators::plot::Histogram {
123
    type Error = anyhow::Error;
124
    fn try_from(value: Histogram) -> Result<Self, Self::Error> {
2✔
125
        let params = geoengine_operators::plot::HistogramParams {
2✔
126
            attribute_name: value.params.column_name,
2✔
127
            bounds: match value.params.bounds {
2✔
128
                HistogramBounds::Data(_) => {
129
                    geoengine_operators::plot::HistogramBounds::Data(Default::default())
1✔
130
                }
131
                HistogramBounds::Values(HistogramBoundsValues { min, max }) => {
1✔
132
                    geoengine_operators::plot::HistogramBounds::Values {
1✔
133
                        min: *min,
1✔
134
                        max: *max,
1✔
135
                    }
1✔
136
                }
137
            },
138
            buckets: match value.params.buckets {
2✔
139
                HistogramBuckets::Number(HistogramBucketsNumber { r#type: _, value }) => {
2✔
140
                    geoengine_operators::plot::HistogramBuckets::Number { value }
2✔
141
                }
142
                HistogramBuckets::SquareRootChoiceRule(HistogramBucketsSquareRootChoiceRule {
143
                    r#type: _,
144
                    max_number_of_buckets,
×
NEW
145
                }) => geoengine_operators::plot::HistogramBuckets::SquareRootChoiceRule {
×
146
                    max_number_of_buckets,
×
147
                },
×
148
            },
149
            interactive: value.params.interactive,
2✔
150
        };
151
        let sources = value.sources.try_into()?;
2✔
152
        Ok(Self { params, sources })
2✔
153
    }
2✔
154
}
155

156
/// The `Statistics` operator is a _plot operator_ that computes count statistics over
157
///
158
/// - a selection of numerical columns of a single vector dataset, or
159
/// - multiple raster datasets.
160
///
161
/// The output is a JSON description.
162
///
163
/// For instance, you want to get an overview of a raster data source.
164
/// Then, you can use this operator to get basic count statistics.
165
///
166
/// ## Vector Data
167
///
168
/// In the case of vector data, the operator generates one statistic for each of the selected numerical attributes.
169
/// The operator returns an error if one of the selected attributes is not numeric.
170
///
171
/// ## Raster Data
172
///
173
/// For raster data, the operator generates one statistic for each input raster.
174
///
175
/// ## Inputs
176
///
177
/// The operator consumes exactly one _vector_ or multiple _raster_ operators.
178
///
179
/// | Parameter | Type                                 |
180
/// | --------- | ------------------------------------ |
181
/// | `source`  | `MultipleRasterOrSingleVectorSource` |
182
///
183
/// ## Errors
184
///
185
/// The operator returns an error in the following cases.
186
///
187
/// - Vector data: The `attribute` for one of the given `columnNames` is not numeric.
188
/// - Vector data: The `attribute` for one of the given `columnNames` does not exist.
189
/// - Raster data: The length of the `columnNames` parameter does not match the number of input rasters.
190
///
191
/// ### Example Output
192
///
193
/// ```json
194
/// {
195
///   "A": {
196
///     "valueCount": 6,
197
///     "validCount": 6,
198
///     "min": 1.0,
199
///     "max": 6.0,
200
///     "mean": 3.5,
201
///     "stddev": 1.707,
202
///     "percentiles": [
203
///       {
204
///         "percentile": 0.25,
205
///         "value": 2.0
206
///       },
207
///       {
208
///         "percentile": 0.5,
209
///         "value": 3.5
210
///       },
211
///       {
212
///         "percentile": 0.75,
213
///         "value": 5.0
214
///       }
215
///     ]
216
///   }
217
/// }
218
/// ```
219
///
220
#[api_operator(examples(json!({
30✔
221
    "type": "Statistics",
222
    "params": {
223
        "columnNames": ["A"],
224
        "percentiles": [0.25, 0.5, 0.75]
225
    },
226
    "sources": {
227
        "source": [{
228
            "type": "GdalSource",
229
            "params": {
230
            "data": "ndvi"
231
            }
232
        }]
233
    }
234
})))]
235
pub struct Statistics {
236
    pub params: StatisticsParameters,
237
    pub sources: MultipleRasterOrSingleVectorSource,
238
}
239

240
/// The parameter spec for `Statistics`
241
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
242
#[serde(rename_all = "camelCase")]
243
pub struct StatisticsParameters {
244
    /// # Vector data
245
    /// The names of the attributes to generate statistics for.
246
    ///
247
    /// # Raster data
248
    /// _Optional_: An alias for each input source.
249
    /// The operator will automatically name the rasters `Raster-1`, `Raster-2`, … if this parameter is empty.
250
    /// If aliases are given, the number of aliases must match the number of input rasters.
251
    /// Otherwise an error is returned.
252
    #[schema(examples(json!(["x", "y"])))]
253
    #[serde(default)]
254
    pub column_names: Vec<String>,
255
    /// The percentiles to compute for each attribute.
256
    #[serde(default)]
257
    #[schema(value_type = Vec<f64>, examples(json!([0.25, 0.5, 0.75])))]
258
    pub percentiles: Vec<NotNan<f64>>,
259
}
260

261
impl TryFrom<Statistics> for geoengine_operators::plot::Statistics {
262
    type Error = anyhow::Error;
263
    fn try_from(value: Statistics) -> Result<Self, Self::Error> {
4✔
264
        let params = geoengine_operators::plot::StatisticsParams {
4✔
265
            column_names: value.params.column_names,
4✔
266
            percentiles: value.params.percentiles.clone(),
4✔
267
        };
4✔
268
        let sources = value.sources.try_into()?;
4✔
269
        Ok(Self { params, sources })
4✔
270
    }
4✔
271
}
272

273
#[cfg(test)]
274
mod tests {
275
    use super::*;
276
    use crate::api::model::processing_graphs::{
277
        GdalSource, GdalSourceParameters, PlotOperator, RasterOperator, VectorOperator,
278
        source::{MockPointSource, MockPointSourceParameters, OgrSource, OgrSourceParameters},
279
        source_parameters::{
280
            MultipleRasterOrSingleVectorOperator, MultipleRasterOrSingleVectorSource,
281
            SingleRasterOrVectorOperator, SingleVectorOrRasterSource,
282
        },
283
    };
284
    use crate::api::model::{
285
        datatypes::Coordinate2D, processing_graphs::parameters::SpatialBoundsDerive,
286
    };
287
    use geoengine_operators::engine::PlotOperator as OperatorsPlotOperatorTrait;
288
    use ordered_float::NotNan;
289

290
    // ---------------------------------------------------------------------------
291
    // Histogram
292
    // ---------------------------------------------------------------------------
293

294
    #[test]
295
    fn it_parses_histogram_api_example() {
1✔
296
        let example = serde_json::json!({
1✔
297
            "type": "Histogram",
1✔
298
            "params": {
1✔
299
                "columnName": "foobar",
1✔
300
                "bounds": {
1✔
301
                    "min": 5.0,
1✔
302
                    "max": 10.0
1✔
303
                },
304
                "buckets": {
1✔
305
                    "type": "number",
1✔
306
                    "value": 15
1✔
307
                },
308
                "interactive": false
1✔
309
            },
310
            "sources": {
1✔
311
                "vector": {
1✔
312
                    "type": "OgrSource",
1✔
313
                    "params": {
1✔
314
                        "data": "ndvi"
1✔
315
                    }
316
                }
317
            }
318
        });
319

320
        let parsed: Histogram = serde_json::from_value(example).expect("example must parse");
1✔
321

322
        assert_eq!(parsed.params.column_name, "foobar");
1✔
323
        assert!(matches!(parsed.params.bounds, HistogramBounds::Values(_)));
1✔
324
    }
1✔
325

326
    #[test]
327
    fn it_parses_histogram_data_bounds() {
1✔
328
        let example = serde_json::json!({
1✔
329
            "type": "Histogram",
1✔
330
            "params": {
1✔
331
                "columnName": "temperature",
1✔
332
                "bounds": "data",
1✔
333
                "buckets": {
1✔
334
                    "type": "number",
1✔
335
                    "value": 10
1✔
336
                },
337
                "interactive": false
1✔
338
            },
339
            "sources": {
1✔
340
                "vector": {
1✔
341
                    "type": "OgrSource",
1✔
342
                    "params": {
1✔
343
                        "data": "ndvi"
1✔
344
                    }
345
                }
346
            }
347
        });
348

349
        let parsed: Histogram =
1✔
350
            serde_json::from_value(example).expect(r#""data" bounds must parse"#);
1✔
351

352
        assert_eq!(parsed.params.column_name, "temperature");
1✔
353
        assert!(matches!(parsed.params.bounds, HistogramBounds::Data(_)));
1✔
354
    }
1✔
355

356
    #[test]
357
    fn it_serializes_histogram_data_bounds() {
1✔
358
        let histogram = Histogram {
1✔
359
            r#type: Default::default(),
1✔
360
            params: HistogramParameters {
1✔
361
                column_name: "temperature".to_string(),
1✔
362
                bounds: HistogramBounds::Data(Data::Data),
1✔
363
                buckets: HistogramBuckets::Number(HistogramBucketsNumber {
1✔
364
                    r#type: Default::default(),
1✔
365
                    value: 10,
1✔
366
                }),
1✔
367
                interactive: false,
1✔
368
            },
1✔
369
            sources: SingleVectorOrRasterSource {
1✔
370
                vector: SingleRasterOrVectorOperator::Vector(VectorOperator::OgrSource(
1✔
371
                    OgrSource {
1✔
372
                        r#type: Default::default(),
1✔
373
                        params: OgrSourceParameters {
1✔
374
                            data: "ndvi".to_string(),
1✔
375
                            attribute_projection: None,
1✔
376
                        },
1✔
377
                    },
1✔
378
                )),
1✔
379
            },
1✔
380
        };
1✔
381

382
        let json = serde_json::to_value(&histogram).expect("must serialize");
1✔
383
        assert_eq!(
1✔
384
            json["params"]["bounds"],
1✔
385
            serde_json::json!("data"),
1✔
386
            "bounds should serialize to the string \"data\""
387
        );
388

389
        // round-trip: deserialize back and check variant
390
        let round_tripped: Histogram = serde_json::from_value(json).expect("round-trip must parse");
1✔
391
        assert!(matches!(
1✔
392
            round_tripped.params.bounds,
1✔
393
            HistogramBounds::Data(_)
394
        ));
395
    }
1✔
396

397
    #[test]
398
    fn it_serializes_histogram_values_bounds() {
1✔
399
        let histogram = Histogram {
1✔
400
            r#type: Default::default(),
1✔
401
            params: HistogramParameters {
1✔
402
                column_name: "foobar".to_string(),
1✔
403
                bounds: HistogramBounds::Values(HistogramBoundsValues {
1✔
404
                    min: NotNan::new(5.0).unwrap(),
1✔
405
                    max: NotNan::new(10.0).unwrap(),
1✔
406
                }),
1✔
407
                buckets: HistogramBuckets::Number(HistogramBucketsNumber {
1✔
408
                    r#type: Default::default(),
1✔
409
                    value: 15,
1✔
410
                }),
1✔
411
                interactive: false,
1✔
412
            },
1✔
413
            sources: SingleVectorOrRasterSource {
1✔
414
                vector: SingleRasterOrVectorOperator::Vector(VectorOperator::OgrSource(
1✔
415
                    OgrSource {
1✔
416
                        r#type: Default::default(),
1✔
417
                        params: OgrSourceParameters {
1✔
418
                            data: "ndvi".to_string(),
1✔
419
                            attribute_projection: None,
1✔
420
                        },
1✔
421
                    },
1✔
422
                )),
1✔
423
            },
1✔
424
        };
1✔
425

426
        let json = serde_json::to_value(&histogram).expect("must serialize");
1✔
427
        assert_eq!(
1✔
428
            json["params"]["bounds"],
1✔
429
            serde_json::json!({"min": 5.0, "max": 10.0}),
1✔
430
            "bounds should serialize to {{min, max}} object"
431
        );
432

433
        // round-trip
434
        let round_tripped: Histogram = serde_json::from_value(json).expect("round-trip must parse");
1✔
435
        assert!(matches!(
1✔
436
            round_tripped.params.bounds,
1✔
437
            HistogramBounds::Values(_)
438
        ));
439
    }
1✔
440

441
    #[test]
442
    fn it_converts_histogram_example_to_operator() {
1✔
443
        let histogram = Histogram {
1✔
444
            r#type: Default::default(),
1✔
445
            params: HistogramParameters {
1✔
446
                column_name: "foobar".to_string(),
1✔
447
                bounds: HistogramBounds::Values(HistogramBoundsValues {
1✔
448
                    min: NotNan::new(5.0).unwrap(),
1✔
449
                    max: NotNan::new(10.0).unwrap(),
1✔
450
                }),
1✔
451
                buckets: HistogramBuckets::Number(HistogramBucketsNumber {
1✔
452
                    r#type: Default::default(),
1✔
453
                    value: 15,
1✔
454
                }),
1✔
455
                interactive: false,
1✔
456
            },
1✔
457
            sources: SingleVectorOrRasterSource {
1✔
458
                vector: SingleRasterOrVectorOperator::Vector(VectorOperator::OgrSource(
1✔
459
                    OgrSource {
1✔
460
                        r#type: Default::default(),
1✔
461
                        params: OgrSourceParameters {
1✔
462
                            data: "ndvi".to_string(),
1✔
463
                            attribute_projection: None,
1✔
464
                        },
1✔
465
                    },
1✔
466
                )),
1✔
467
            },
1✔
468
        };
1✔
469

470
        let plot_operator = PlotOperator::Histogram(histogram);
1✔
471
        Box::<dyn OperatorsPlotOperatorTrait>::try_from(plot_operator)
1✔
472
            .map(|_| ())
1✔
473
            .expect("histogram with OgrSource must convert to operator");
1✔
474
    }
1✔
475

476
    #[test]
477
    fn it_converts_histogram_operators() {
1✔
478
        let hist = Histogram {
1✔
479
            r#type: Default::default(),
1✔
480
            params: HistogramParameters {
1✔
481
                column_name: "temperature".to_string(),
1✔
482
                bounds: HistogramBounds::Data(Data::Data),
1✔
483
                buckets: HistogramBuckets::Number(HistogramBucketsNumber {
1✔
484
                    r#type: Default::default(),
1✔
485
                    value: 10,
1✔
486
                }),
1✔
487
                interactive: false,
1✔
488
            },
1✔
489
            sources: SingleVectorOrRasterSource {
1✔
490
                vector: SingleRasterOrVectorOperator::Vector(VectorOperator::MockPointSource(
1✔
491
                    MockPointSource {
1✔
492
                        r#type: Default::default(),
1✔
493
                        params: MockPointSourceParameters {
1✔
494
                            points: vec![Coordinate2D { x: 1.0, y: 2.0 }],
1✔
495
                            spatial_bounds: SpatialBoundsDerive::Derive(Default::default()),
1✔
496
                        },
1✔
497
                    },
1✔
498
                )),
1✔
499
            },
1✔
500
        };
1✔
501

502
        let operators: geoengine_operators::plot::Histogram =
1✔
503
            hist.try_into().expect("conversion failed");
1✔
504

505
        assert_eq!(operators.params.attribute_name, "temperature");
1✔
506
        assert!(matches!(
1✔
507
            operators.params.bounds,
1✔
508
            geoengine_operators::plot::HistogramBounds::Data(_)
509
        ));
510
        assert!(matches!(
1✔
511
            operators.params.buckets,
1✔
512
            geoengine_operators::plot::HistogramBuckets::Number { value: 10 }
513
        ));
514
    }
1✔
515

516
    // ---------------------------------------------------------------------------
517
    // Statistics
518
    // ---------------------------------------------------------------------------
519

520
    #[test]
521
    fn it_parses_statistics_api_example() {
1✔
522
        let example = serde_json::json!({
1✔
523
            "type": "Statistics",
1✔
524
            "params": {
1✔
525
                "columnNames": ["A"],
1✔
526
                "percentiles": [0.25, 0.5, 0.75]
1✔
527
            },
528
            "sources": {
1✔
529
                "source": [{
1✔
530
                    "type": "GdalSource",
1✔
531
                    "params": {
1✔
532
                        "data": "ndvi"
1✔
533
                    }
534
                }]
535
            }
536
        });
537

538
        let parsed: Statistics = serde_json::from_value(example).expect("example must parse");
1✔
539

540
        assert_eq!(parsed.params.column_names, vec!["A".to_string()]);
1✔
541
        assert_eq!(parsed.params.percentiles.len(), 3);
1✔
542
    }
1✔
543

544
    #[test]
545
    fn it_converts_statistics_example_to_operator() {
1✔
546
        let statistics = Statistics {
1✔
547
            r#type: Default::default(),
1✔
548
            params: StatisticsParameters {
1✔
549
                column_names: vec!["A".to_string()],
1✔
550
                percentiles: vec![
1✔
551
                    NotNan::new(0.25).unwrap(),
1✔
552
                    NotNan::new(0.5).unwrap(),
1✔
553
                    NotNan::new(0.75).unwrap(),
1✔
554
                ],
1✔
555
            },
1✔
556
            sources: MultipleRasterOrSingleVectorSource {
1✔
557
                source: MultipleRasterOrSingleVectorOperator::Raster(vec![
1✔
558
                    RasterOperator::GdalSource(GdalSource {
1✔
559
                        r#type: Default::default(),
1✔
560
                        params: GdalSourceParameters {
1✔
561
                            data: "ndvi".to_string(),
1✔
562
                            overview_level: None,
1✔
563
                        },
1✔
564
                    }),
1✔
565
                ]),
1✔
566
            },
1✔
567
        };
1✔
568

569
        let plot_operator = PlotOperator::Statistics(statistics);
1✔
570
        Box::<dyn OperatorsPlotOperatorTrait>::try_from(plot_operator)
1✔
571
            .map(|_| ())
1✔
572
            .expect("statistics with GdalSource must convert to operator");
1✔
573
    }
1✔
574

575
    #[test]
576
    fn it_converts_statistics_operators() {
1✔
577
        let stats = Statistics {
1✔
578
            r#type: Default::default(),
1✔
579
            params: StatisticsParameters {
1✔
580
                column_names: vec!["A".to_string()],
1✔
581
                percentiles: vec![NotNan::new(0.5).unwrap()],
1✔
582
            },
1✔
583
            sources: MultipleRasterOrSingleVectorSource {
1✔
584
                source: MultipleRasterOrSingleVectorOperator::Vector(
1✔
585
                    VectorOperator::MockPointSource(MockPointSource {
1✔
586
                        r#type: Default::default(),
1✔
587
                        params: MockPointSourceParameters {
1✔
588
                            points: vec![Coordinate2D { x: 1.0, y: 2.0 }],
1✔
589
                            spatial_bounds: SpatialBoundsDerive::Derive(Default::default()),
1✔
590
                        },
1✔
591
                    }),
1✔
592
                ),
1✔
593
            },
1✔
594
        };
1✔
595

596
        let operators: geoengine_operators::plot::Statistics =
1✔
597
            stats.try_into().expect("conversion failed");
1✔
598

599
        assert_eq!(operators.params.column_names, vec!["A".to_string()]);
1✔
600
        assert_eq!(operators.params.percentiles.len(), 1);
1✔
601
        assert_eq!(
1✔
602
            operators.params.percentiles[0].clone(),
1✔
603
            NotNan::new(0.5).unwrap()
1✔
604
        );
605
    }
1✔
606
}
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