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

geo-engine / geoengine / 5751943360

03 Aug 2023 02:19PM UTC coverage: 89.422% (+0.4%) from 88.974%
5751943360

push

github

web-flow
Merge pull request #840 from geo-engine/remove_in_memory

Remove in memory contexts and dbs

5338 of 5338 new or added lines in 37 files covered. (100.0%)

103772 of 116048 relevant lines covered (89.42%)

62390.21 hits per line

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

84.41
/services/src/util/openapi_examples.rs
1
use crate::contexts::{PostgresContext, SessionId, SimpleApplicationContext};
2
use crate::handlers::ErrorResponse;
3
use actix_web::dev::ServiceResponse;
4
use actix_web::http::{header, Method};
5
use actix_web::test::TestRequest;
6
use actix_web_httpauth::headers::authorization::Bearer;
7
use std::collections::HashMap;
8
use std::future::Future;
9
use tokio_postgres::NoTls;
10
use utoipa::openapi::path::{Parameter, ParameterIn};
11
use utoipa::openapi::schema::AdditionalProperties;
12
use utoipa::openapi::{
13
    Components, KnownFormat, OpenApi, PathItemType, Ref, RefOr, Schema, SchemaFormat, SchemaType,
14
};
15
use uuid::Uuid;
16

17
fn throw_if_invalid_ref(reference: &Ref, components: &Components) {
190✔
18
    const SCHEMA_REF_PREFIX: &str = "#/components/schemas/";
190✔
19
    const RESPONSE_REF_PREFIX: &str = "#/components/responses/";
190✔
20

190✔
21
    if reference.ref_location.starts_with(SCHEMA_REF_PREFIX) {
190✔
22
        let schema_name = &reference.ref_location[SCHEMA_REF_PREFIX.len()..];
170✔
23
        assert!(
170✔
24
            components.schemas.contains_key(schema_name),
170✔
25
            "Referenced the unknown schema `{schema_name}`"
8✔
26
        );
27
    } else if reference.ref_location.starts_with(RESPONSE_REF_PREFIX) {
20✔
28
        let response_name = &reference.ref_location[RESPONSE_REF_PREFIX.len()..];
20✔
29
        assert!(
20✔
30
            components.responses.contains_key(response_name),
20✔
31
            "Referenced the unknown response `{response_name}`"
×
32
        );
33
    } else {
34
        panic!("Invalid reference type");
×
35
    }
36
}
182✔
37

38
/// Recursively checks that schemas referenced in the given schema object exist in the provided map.
39
fn can_resolve_schema(schema: RefOr<Schema>, components: &Components) {
245✔
40
    match schema {
245✔
41
        RefOr::Ref(reference) => {
168✔
42
            throw_if_invalid_ref(&reference, components);
168✔
43
        }
168✔
44
        RefOr::T(concrete) => match concrete {
77✔
45
            Schema::Array(arr) => {
8✔
46
                can_resolve_schema(*arr.items, components);
8✔
47
            }
8✔
48
            Schema::Object(obj) => {
54✔
49
                for property in obj.properties.into_values() {
54✔
50
                    can_resolve_schema(property, components);
2✔
51
                }
2✔
52
                if let Some(additional_properties) = obj.additional_properties {
53✔
53
                    if let AdditionalProperties::RefOr(properties_schema) = *additional_properties {
×
54
                        can_resolve_schema(properties_schema, components);
×
55
                    }
×
56
                }
53✔
57
            }
58
            Schema::OneOf(oo) => {
1✔
59
                for item in oo.items {
2✔
60
                    can_resolve_schema(item, components);
1✔
61
                }
1✔
62
            }
63
            Schema::AllOf(ao) => {
14✔
64
                for item in ao.items {
28✔
65
                    can_resolve_schema(item, components);
14✔
66
                }
14✔
67
            }
68
            _ => panic!("Unknown schema type"),
×
69
        },
70
    }
71
}
242✔
72

73
/// Loops through all registered HTTP handlers and ensures that the referenced schemas
74
/// (inside of request bodies, parameters or responses) exist and can be resolved.
75
pub fn can_resolve_api(api: OpenApi) {
5✔
76
    let components = api.components.expect("api has at least one component");
5✔
77

78
    for path_item in api.paths.paths.into_values() {
59✔
79
        for operation in path_item.operations.into_values() {
67✔
80
            if let Some(request_body) = operation.request_body {
67✔
81
                for content in request_body.content.into_values() {
17✔
82
                    can_resolve_schema(content.schema, &components);
17✔
83
                }
17✔
84
            }
50✔
85

86
            if let Some(parameters) = operation.parameters {
66✔
87
                for parameter in parameters {
203✔
88
                    if let Some(schema) = parameter.schema {
156✔
89
                        can_resolve_schema(schema, &components);
155✔
90
                    }
155✔
91
                }
92
            }
18✔
93

94
            for response in operation.responses.responses.into_values() {
83✔
95
                match response {
83✔
96
                    RefOr::Ref(reference) => {
20✔
97
                        throw_if_invalid_ref(&reference, &components);
20✔
98
                    }
20✔
99
                    RefOr::T(concrete) => {
63✔
100
                        for content in concrete.content.into_values() {
63✔
101
                            can_resolve_schema(content.schema, &components);
44✔
102
                        }
44✔
103
                    }
104
                }
105
            }
106
        }
107
    }
108
}
2✔
109

110
pub struct RunnableExample<'a, C, F, Fut>
111
where
112
    F: Fn(TestRequest, C) -> Fut,
113
    Fut: Future<Output = ServiceResponse>,
114
{
115
    pub(crate) components: &'a Components,
116
    pub(crate) http_method: &'a PathItemType,
117
    pub(crate) uri: &'a str,
118
    pub(crate) parameters: &'a Option<Vec<Parameter>>,
119
    pub(crate) body: serde_json::Value,
120
    pub(crate) with_auth: bool,
121
    pub(crate) ctx: C,
122
    pub(crate) session_id: SessionId,
123
    pub(crate) send_test_request: &'a F,
124
}
125

126
impl<'a, C, F, Fut> RunnableExample<'a, C, F, Fut>
127
where
128
    F: Fn(TestRequest, C) -> Fut,
129
    Fut: Future<Output = ServiceResponse>,
130
{
131
    fn get_actix_http_method(&self) -> Method {
2✔
132
        match self.http_method {
2✔
133
            PathItemType::Get => Method::GET,
×
134
            PathItemType::Post => Method::POST,
2✔
135
            PathItemType::Put => Method::PUT,
×
136
            PathItemType::Delete => Method::DELETE,
×
137
            PathItemType::Options => Method::OPTIONS,
×
138
            PathItemType::Head => Method::HEAD,
×
139
            PathItemType::Patch => Method::PATCH,
×
140
            PathItemType::Trace => Method::TRACE,
×
141
            PathItemType::Connect => Method::CONNECT,
×
142
        }
143
    }
2✔
144

145
    #[allow(clippy::unimplemented)]
146
    fn get_default_parameter_value(schema: &Schema) -> String {
4✔
147
        match schema {
4✔
148
            Schema::Object(obj) => match obj.schema_type {
4✔
149
                SchemaType::String => match &obj.format {
×
150
                    Some(SchemaFormat::KnownFormat(format)) => match format {
×
151
                        KnownFormat::Uuid => Uuid::new_v4().to_string(),
×
152
                        _ => unimplemented!(),
×
153
                    },
154
                    None => "asdf".to_string(),
2✔
155
                    _ => unimplemented!(),
×
156
                },
157
                SchemaType::Integer | SchemaType::Number => "42".to_string(),
2✔
158
                SchemaType::Boolean => "false".to_string(),
×
159
                _ => unimplemented!(),
×
160
            },
161
            _ => unimplemented!(),
×
162
        }
163
    }
4✔
164

165
    fn resolve_schema(&'a self, ref_or: &'a RefOr<Schema>) -> &Schema {
4✔
166
        match ref_or {
4✔
167
            RefOr::Ref(reference) => {
×
168
                const SCHEMA_REF_PREFIX_LEN: usize = "#/components/schemas/".len();
×
169
                throw_if_invalid_ref(reference, self.components);
×
170
                let schema_name = &reference.ref_location[SCHEMA_REF_PREFIX_LEN..];
×
171
                self.resolve_schema(
×
172
                    self.components
×
173
                        .schemas
×
174
                        .get(schema_name)
×
175
                        .expect("checked before"),
×
176
                )
×
177
            }
178
            RefOr::T(concrete) => concrete,
4✔
179
        }
180
    }
4✔
181

182
    fn insert_parameters(&self, parameters: &Vec<Parameter>) -> TestRequest {
2✔
183
        let mut req = TestRequest::default();
2✔
184
        let mut uri = self.uri.to_string();
2✔
185
        let mut query_params = HashMap::new();
2✔
186
        let mut cookies = HashMap::new();
2✔
187

188
        for parameter in parameters {
6✔
189
            let schema = self.resolve_schema(
4✔
190
                parameter
4✔
191
                    .schema
4✔
192
                    .as_ref()
4✔
193
                    .expect("utoipa adds schema everytime"),
4✔
194
            );
4✔
195
            let value = Self::get_default_parameter_value(schema);
4✔
196

4✔
197
            match parameter.parameter_in {
4✔
198
                ParameterIn::Query => {
2✔
199
                    query_params.insert(parameter.name.as_str(), value);
2✔
200
                }
2✔
201
                ParameterIn::Path => {
2✔
202
                    uri = uri.replace(&format!("{{{}}}", parameter.name), value.as_str());
2✔
203
                }
2✔
204
                ParameterIn::Header => {
×
205
                    req = req.append_header((parameter.name.as_str(), value));
×
206
                }
×
207
                ParameterIn::Cookie => {
×
208
                    cookies.insert(parameter.name.as_str(), value);
×
209
                }
×
210
            }
211
        }
212
        if !cookies.is_empty() {
2✔
213
            req = req.append_header((
×
214
                header::COOKIE,
×
215
                serde_urlencoded::to_string(cookies)
×
216
                    .unwrap()
×
217
                    .replace('&', "; "),
×
218
            ));
×
219
        }
2✔
220
        if !query_params.is_empty() {
2✔
221
            uri = format!(
2✔
222
                "{}?{}",
2✔
223
                uri,
2✔
224
                serde_urlencoded::to_string(query_params).unwrap()
2✔
225
            );
2✔
226
        }
2✔
227
        req.uri(uri.as_str())
2✔
228
    }
2✔
229

230
    fn build_request(&self) -> TestRequest {
2✔
231
        let http_method = self.get_actix_http_method();
2✔
232
        let mut req;
233

234
        if let Some(parameters) = self.parameters {
2✔
235
            req = self.insert_parameters(parameters);
2✔
236
        } else {
2✔
237
            req = TestRequest::default().uri(self.uri);
×
238
        }
×
239
        req = req.method(http_method);
2✔
240

2✔
241
        if self.with_auth {
2✔
242
            req = req.append_header((
×
243
                header::AUTHORIZATION,
×
244
                Bearer::new(self.session_id.to_string()),
×
245
            ));
×
246
        }
2✔
247
        req.append_header((header::CONTENT_TYPE, "application/json"))
2✔
248
            .set_json(&self.body)
2✔
249
    }
2✔
250

251
    async fn run(self) -> ServiceResponse {
2✔
252
        let req = self.build_request();
2✔
253
        (self.send_test_request)(req, self.ctx).await
2✔
254
    }
2✔
255

256
    /// # Panics
257
    /// Will panic if an example cannot be run due to incomplete or
258
    /// outdated OpenAPI documentation.
259
    pub(crate) async fn check_for_bad_documentation(self) {
2✔
260
        let res = self.run().await;
2✔
261

262
        if res.status() == 400 {
2✔
263
            let method = res.request().head().method.to_string();
1✔
264
            let path = res.request().path().to_string();
1✔
265
            let body: ErrorResponse = actix_web::test::read_body_json(res).await;
1✔
266

267
            match body.error.as_str() {
1✔
268
                "NotFound" | "MethodNotAllowed" => panic!(
1✔
269
                    "The handler of the example at {method} {path} wasn't reachable. \
×
270
                    Check if the http method and path parameters are correctly set in the documentation."
×
271
                ),
×
272
                "UnableToParseQueryString" => panic!(
1✔
273
                    "The example at {method} {path} threw an UnableToParseQueryString error. \
×
274
                    Check if the query parameters are correctly set in the documentation."
×
275
                ),
×
276
                "BodyDeserializeError" => panic!(
1✔
277
                    "The example at {method} {path} threw an BodyDeserializeError. \
1✔
278
                    Check if there were schema changes and update the request body accordingly."
1✔
279
                ),
1✔
280
                _ => {}
×
281
            }
282
        }
1✔
283
    }
1✔
284
}
285

286
/// Runs all example requests against the provided test server to check for bad documentation,
287
/// for example due to incompatible schema changes between the time of writing the request body
288
/// and now. It can also detect if the query parameters are not documented correctly or the
289
/// request path changed.
290
///
291
/// # Panics
292
///
293
/// panics if a Ref occurs in an example, as this case is not yet supported.
294
pub async fn can_run_examples<F, Fut>(
2✔
295
    app_ctx: PostgresContext<NoTls>,
2✔
296
    api: OpenApi,
2✔
297
    send_test_request: F,
2✔
298
) where
2✔
299
    F: Fn(TestRequest, PostgresContext<NoTls>) -> Fut
2✔
300
        + Send
2✔
301
        + std::panic::UnwindSafe
2✔
302
        + 'static
2✔
303
        + Clone,
2✔
304
    Fut: Future<Output = ServiceResponse>,
2✔
305
{
2✔
306
    let components = api.components.expect("api has at least one component");
2✔
307

308
    for (uri, path_item) in api.paths.paths {
3✔
309
        for (http_method, operation) in path_item.operations {
3✔
310
            if let Some(request_body) = operation.request_body {
2✔
311
                let with_auth = operation.security.is_some();
2✔
312

313
                for content in request_body.content.into_values() {
2✔
314
                    if let Some(example) = content.example {
2✔
315
                        RunnableExample {
316
                            components: &components,
2✔
317
                            http_method: &http_method,
2✔
318
                            uri: uri.as_str(),
2✔
319
                            parameters: &operation.parameters,
2✔
320
                            body: example,
2✔
321
                            with_auth,
2✔
322
                            session_id: app_ctx.default_session_id().await,
2✔
323
                            ctx: app_ctx.clone(),
2✔
324
                            send_test_request: &send_test_request,
2✔
325
                        }
2✔
326
                        .check_for_bad_documentation()
2✔
327
                        .await;
×
328
                    } else {
329
                        for example in content.examples.into_values() {
×
330
                            match example {
×
331
                                RefOr::Ref(_reference) => {
×
332
                                    // This never happened during testing.
×
333
                                    // It is undocumented how the references would look like.
×
334
                                    panic!(
×
335
                                        "checking examples with references is not yet implemented"
×
336
                                    )
×
337
                                }
338
                                RefOr::T(concrete) => {
×
339
                                    if let Some(body) = concrete.value {
×
340
                                        RunnableExample {
341
                                            components: &components,
×
342
                                            http_method: &http_method,
×
343
                                            uri: uri.as_str(),
×
344
                                            parameters: &operation.parameters,
×
345
                                            body,
×
346
                                            with_auth,
×
347
                                            session_id: app_ctx.default_session_id().await,
×
348
                                            ctx: app_ctx.clone(),
×
349
                                            send_test_request: &send_test_request,
×
350
                                        }
×
351
                                        .check_for_bad_documentation()
×
352
                                        .await;
×
353
                                    } else {
×
354
                                        //skip external examples
×
355
                                    }
×
356
                                }
357
                            }
358
                        }
359
                    }
360
                }
361
            }
×
362
        }
363
    }
364
}
1✔
365

366
#[cfg(test)]
367
mod tests {
368
    use super::*;
369
    use crate::contexts::SimpleApplicationContext;
370
    use crate::datasets::upload::Volume;
371
    use crate::util::server::{configure_extractors, render_404, render_405};
372
    use crate::util::tests::with_temp_context;
373
    use actix_web::{http, middleware, post, web, App, HttpResponse, Responder};
374

375
    use serde::Deserialize;
376

377
    use serde_json::json;
378
    use utoipa::openapi::path::{OperationBuilder, ParameterBuilder, PathItemBuilder};
379
    use utoipa::openapi::request_body::RequestBodyBuilder;
380
    use utoipa::openapi::{
381
        AllOfBuilder, ArrayBuilder, ComponentsBuilder, ContentBuilder, Object, ObjectBuilder,
382
        OneOfBuilder, OpenApiBuilder, PathItemType, PathsBuilder, ResponseBuilder,
383
    };
384
    use utoipa::ToSchema;
385

386
    #[test]
1✔
387
    #[should_panic(expected = "MissingSchema")]
388
    fn throws_because_of_invalid_ref() {
1✔
389
        throw_if_invalid_ref(&Ref::from_schema_name("MissingSchema"), &Components::new());
1✔
390
    }
1✔
391

392
    #[test]
1✔
393
    fn finds_ref() {
1✔
394
        throw_if_invalid_ref(
1✔
395
            &Ref::from_schema_name("ExistingSchema"),
1✔
396
            &ComponentsBuilder::new()
1✔
397
                .schema("ExistingSchema", RefOr::T(Schema::default()))
1✔
398
                .into(),
1✔
399
        );
1✔
400
    }
1✔
401

402
    #[test]
1✔
403
    #[should_panic(expected = "MissingSchema")]
404
    fn detects_missing_array_ref() {
1✔
405
        can_resolve_schema(
1✔
406
            RefOr::T(
1✔
407
                ArrayBuilder::new()
1✔
408
                    .items(Ref::from_schema_name("MissingSchema"))
1✔
409
                    .into(),
1✔
410
            ),
1✔
411
            &Components::new(),
1✔
412
        );
1✔
413
    }
1✔
414

415
    #[test]
1✔
416
    #[should_panic(expected = "MissingSchema")]
417
    fn detects_missing_object_ref() {
1✔
418
        can_resolve_schema(
1✔
419
            RefOr::T(
1✔
420
                ObjectBuilder::new()
1✔
421
                    .property("Prop", Ref::from_schema_name("MissingSchema"))
1✔
422
                    .into(),
1✔
423
            ),
1✔
424
            &Components::new(),
1✔
425
        );
1✔
426
    }
1✔
427

428
    #[test]
1✔
429
    #[should_panic(expected = "MissingSchema")]
430
    fn detects_missing_oneof_ref() {
1✔
431
        can_resolve_schema(
1✔
432
            RefOr::T(
1✔
433
                OneOfBuilder::new()
1✔
434
                    .item(Ref::from_schema_name("MissingSchema"))
1✔
435
                    .into(),
1✔
436
            ),
1✔
437
            &Components::new(),
1✔
438
        );
1✔
439
    }
1✔
440

441
    #[test]
1✔
442
    #[should_panic(expected = "MissingSchema")]
443
    fn detects_missing_allof_ref() {
1✔
444
        can_resolve_schema(
1✔
445
            RefOr::T(
1✔
446
                AllOfBuilder::new()
1✔
447
                    .item(Ref::from_schema_name("MissingSchema"))
1✔
448
                    .into(),
1✔
449
            ),
1✔
450
            &Components::new(),
1✔
451
        );
1✔
452
    }
1✔
453

454
    #[test]
1✔
455
    fn successfull_api_validation() {
1✔
456
        let api: OpenApi = OpenApiBuilder::new()
1✔
457
            .paths(
1✔
458
                PathsBuilder::new().path(
1✔
459
                    "asdf",
1✔
460
                    PathItemBuilder::new()
1✔
461
                        .operation(
1✔
462
                            PathItemType::Post,
1✔
463
                            OperationBuilder::new()
1✔
464
                                .parameter(
1✔
465
                                    ParameterBuilder::new()
1✔
466
                                        .schema(Some(Ref::from_schema_name("Schema1"))),
1✔
467
                                )
1✔
468
                                .request_body(Some(
1✔
469
                                    RequestBodyBuilder::new()
1✔
470
                                        .content(
1✔
471
                                            "application/json",
1✔
472
                                            ContentBuilder::new()
1✔
473
                                                .schema(Ref::from_schema_name("Schema2"))
1✔
474
                                                .into(),
1✔
475
                                        )
1✔
476
                                        .into(),
1✔
477
                                ))
1✔
478
                                .response(
1✔
479
                                    "200",
1✔
480
                                    ResponseBuilder::new().content(
1✔
481
                                        "application/json",
1✔
482
                                        ContentBuilder::new()
1✔
483
                                            .schema(Ref::from_schema_name("Schema3"))
1✔
484
                                            .into(),
1✔
485
                                    ),
1✔
486
                                ),
1✔
487
                        )
1✔
488
                        .into(),
1✔
489
                ),
1✔
490
            )
1✔
491
            .components(Some(
1✔
492
                ComponentsBuilder::new()
1✔
493
                    .schemas_from_iter([
1✔
494
                        ("Schema1", Schema::default()),
1✔
495
                        ("Schema2", Schema::default()),
1✔
496
                        ("Schema3", Schema::default()),
1✔
497
                    ])
1✔
498
                    .into(),
1✔
499
            ))
1✔
500
            .into();
1✔
501
        can_resolve_api(api);
1✔
502
    }
1✔
503

504
    #[test]
1✔
505
    #[should_panic(expected = "MissingSchema")]
506
    fn detect_unresolvable_request_body() {
1✔
507
        let api: OpenApi = OpenApiBuilder::new()
1✔
508
            .paths(
1✔
509
                PathsBuilder::new().path(
1✔
510
                    "asdf",
1✔
511
                    PathItemBuilder::new()
1✔
512
                        .operation(
1✔
513
                            PathItemType::Post,
1✔
514
                            OperationBuilder::new().request_body(Some(
1✔
515
                                RequestBodyBuilder::new()
1✔
516
                                    .content(
1✔
517
                                        "application/json",
1✔
518
                                        ContentBuilder::new()
1✔
519
                                            .schema(Ref::from_schema_name("MissingSchema"))
1✔
520
                                            .into(),
1✔
521
                                    )
1✔
522
                                    .into(),
1✔
523
                            )),
1✔
524
                        )
1✔
525
                        .into(),
1✔
526
                ),
1✔
527
            )
1✔
528
            .components(Some(
1✔
529
                ComponentsBuilder::new()
1✔
530
                    .schemas_from_iter([
1✔
531
                        ("Schema1", Schema::default()),
1✔
532
                        ("Schema2", Schema::default()),
1✔
533
                        ("Schema3", Schema::default()),
1✔
534
                    ])
1✔
535
                    .into(),
1✔
536
            ))
1✔
537
            .into();
1✔
538
        can_resolve_api(api);
1✔
539
    }
1✔
540

541
    #[test]
1✔
542
    #[should_panic(expected = "MissingSchema")]
543
    fn detect_unresolvable_parameter() {
1✔
544
        let api: OpenApi = OpenApiBuilder::new()
1✔
545
            .paths(
1✔
546
                PathsBuilder::new().path(
1✔
547
                    "asdf",
1✔
548
                    PathItemBuilder::new()
1✔
549
                        .operation(
1✔
550
                            PathItemType::Post,
1✔
551
                            OperationBuilder::new().parameter(
1✔
552
                                ParameterBuilder::new()
1✔
553
                                    .schema(Some(Ref::from_schema_name("MissingSchema"))),
1✔
554
                            ),
1✔
555
                        )
1✔
556
                        .into(),
1✔
557
                ),
1✔
558
            )
1✔
559
            .components(Some(
1✔
560
                ComponentsBuilder::new()
1✔
561
                    .schemas_from_iter([
1✔
562
                        ("Schema1", Schema::default()),
1✔
563
                        ("Schema2", Schema::default()),
1✔
564
                        ("Schema3", Schema::default()),
1✔
565
                    ])
1✔
566
                    .into(),
1✔
567
            ))
1✔
568
            .into();
1✔
569
        can_resolve_api(api);
1✔
570
    }
1✔
571

572
    #[test]
1✔
573
    #[should_panic(expected = "MissingSchema")]
574
    fn detect_unresolvable_response() {
1✔
575
        let api: OpenApi = OpenApiBuilder::new()
1✔
576
            .paths(
1✔
577
                PathsBuilder::new().path(
1✔
578
                    "asdf",
1✔
579
                    PathItemBuilder::new()
1✔
580
                        .operation(
1✔
581
                            PathItemType::Post,
1✔
582
                            OperationBuilder::new().response(
1✔
583
                                "200",
1✔
584
                                ResponseBuilder::new().content(
1✔
585
                                    "application/json",
1✔
586
                                    ContentBuilder::new()
1✔
587
                                        .schema(Ref::from_schema_name("MissingSchema"))
1✔
588
                                        .into(),
1✔
589
                                ),
1✔
590
                            ),
1✔
591
                        )
1✔
592
                        .into(),
1✔
593
                ),
1✔
594
            )
1✔
595
            .components(Some(
1✔
596
                ComponentsBuilder::new()
1✔
597
                    .schemas_from_iter([
1✔
598
                        ("Schema1", Schema::default()),
1✔
599
                        ("Schema2", Schema::default()),
1✔
600
                        ("Schema3", Schema::default()),
1✔
601
                    ])
1✔
602
                    .into(),
1✔
603
            ))
1✔
604
            .into();
1✔
605
        can_resolve_api(api);
1✔
606
    }
1✔
607

608
    #[derive(Deserialize)]
6✔
609
    struct DummyQueryParams {
610
        #[serde(rename = "x")]
611
        _x: String,
612
    }
613

614
    // adding path and query parameter to ensure parameter insertion works
615
    #[post("/test/{id}")]
2✔
616
    #[allow(clippy::unused_async)] // the function signature of request handlers requires it
617
    async fn dummy_handler(
1✔
618
        _id: web::Path<u32>,
1✔
619
        _params: web::Query<DummyQueryParams>,
1✔
620
        _body: web::Json<Volume>,
1✔
621
    ) -> impl Responder {
1✔
622
        HttpResponse::Ok()
1✔
623
    }
1✔
624

625
    async fn dummy_send_test_request<C: SimpleApplicationContext>(
2✔
626
        req: TestRequest,
2✔
627
        ctx: C,
2✔
628
    ) -> ServiceResponse {
2✔
629
        let app = actix_web::test::init_service(
2✔
630
            App::new()
2✔
631
                .app_data(web::Data::new(ctx))
2✔
632
                .wrap(
2✔
633
                    middleware::ErrorHandlers::default()
2✔
634
                        .handler(http::StatusCode::NOT_FOUND, render_404)
2✔
635
                        .handler(http::StatusCode::METHOD_NOT_ALLOWED, render_405),
2✔
636
                )
2✔
637
                .configure(configure_extractors)
2✔
638
                .service(dummy_handler),
2✔
639
        )
2✔
640
        .await;
×
641
        actix_web::test::call_service(&app, req.to_request())
2✔
642
            .await
×
643
            .map_into_boxed_body()
2✔
644
    }
2✔
645

646
    async fn run_dummy_example(example: serde_json::Value) {
2✔
647
        with_temp_context(|app_ctx, _| async move {
2✔
648
            can_run_examples(
2✔
649
                app_ctx,
2✔
650
                OpenApiBuilder::new()
2✔
651
                    .paths(
2✔
652
                        PathsBuilder::new().path(
2✔
653
                            "/test/{id}",
2✔
654
                            PathItemBuilder::new()
2✔
655
                                .operation(
2✔
656
                                    PathItemType::Post,
2✔
657
                                    OperationBuilder::new()
2✔
658
                                        .parameter(
2✔
659
                                            ParameterBuilder::new()
2✔
660
                                                .name("id")
2✔
661
                                                .parameter_in(ParameterIn::Path)
2✔
662
                                                .schema(Some(RefOr::T(
2✔
663
                                                    ObjectBuilder::new()
2✔
664
                                                        .schema_type(SchemaType::Integer)
2✔
665
                                                        .format(Some(SchemaFormat::KnownFormat(
2✔
666
                                                            KnownFormat::Int32,
2✔
667
                                                        )))
2✔
668
                                                        .into(),
2✔
669
                                                ))),
2✔
670
                                        )
2✔
671
                                        .parameter(
2✔
672
                                            ParameterBuilder::new()
2✔
673
                                                .name("x")
2✔
674
                                                .parameter_in(ParameterIn::Query)
2✔
675
                                                .schema(Some(RefOr::T(
2✔
676
                                                    Object::with_type(SchemaType::String).into(),
2✔
677
                                                ))),
2✔
678
                                        )
2✔
679
                                        .request_body(Some(
2✔
680
                                            RequestBodyBuilder::new()
2✔
681
                                                .content(
2✔
682
                                                    "application/json",
2✔
683
                                                    ContentBuilder::new()
2✔
684
                                                        .schema(Volume::schema().1)
2✔
685
                                                        .example(Some(example))
2✔
686
                                                        .into(),
2✔
687
                                                )
2✔
688
                                                .into(),
2✔
689
                                        )),
2✔
690
                                )
2✔
691
                                .into(),
2✔
692
                        ),
2✔
693
                    )
2✔
694
                    .components(Some(
2✔
695
                        ComponentsBuilder::new()
2✔
696
                            .schemas_from_iter([
2✔
697
                                ("Schema1", Schema::default()),
2✔
698
                                ("Schema2", Schema::default()),
2✔
699
                                ("Schema3", Schema::default()),
2✔
700
                            ])
2✔
701
                            .into(),
2✔
702
                    ))
2✔
703
                    .into(),
2✔
704
                dummy_send_test_request,
2✔
705
            )
2✔
706
            .await;
×
707
        })
2✔
708
        .await;
19✔
709
    }
1✔
710

711
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
712
    #[should_panic(expected = "BodyDeserializeError")]
713
    async fn detects_bodydeserializeerror() {
1✔
714
        run_dummy_example(json!({"name": "note-path_field_missing"})).await;
11✔
715
    }
716

717
    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1✔
718
    async fn successfull_example_run() {
1✔
719
        run_dummy_example(json!({"name": "Files", "path": "/path/to/files"})).await;
8✔
720
    }
721
}
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