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

geo-engine / geoengine / 6709775090

31 Oct 2023 03:43PM UTC coverage: 89.483% (-0.02%) from 89.498%
6709775090

push

github

web-flow
Merge pull request #882 from geo-engine/API-docs

Api docs

493 of 493 new or added lines in 15 files covered. (100.0%)

109476 of 122343 relevant lines covered (89.48%)

59359.13 hits per line

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

85.69
/services/src/util/openapi_examples.rs
1
use crate::api::model::responses::ErrorResponse;
2
use crate::contexts::{PostgresContext, SessionId, SimpleApplicationContext};
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, Response, Schema, SchemaFormat,
14
    SchemaType,
15
};
16
use uuid::Uuid;
17

18
/// Recursively checks that schemas referenced in the given schema object exist in the provided map.
19
fn can_resolve_schema(schema: &RefOr<Schema>, components: &Components) {
12,705✔
20
    match schema {
12,705✔
21
        RefOr::Ref(reference) => {
4,103✔
22
            can_resolve_reference(reference, components);
4,103✔
23
        }
4,103✔
24
        RefOr::T(concrete) => match concrete {
8,602✔
25
            Schema::Array(arr) => {
1,634✔
26
                can_resolve_schema(arr.items.as_ref(), components);
1,634✔
27
            }
1,634✔
28
            Schema::Object(obj) => {
5,973✔
29
                for property in obj.properties.values() {
5,973✔
30
                    can_resolve_schema(property, components);
4,485✔
31
                }
4,485✔
32
                if let Some(additional_properties) = &obj.additional_properties {
5,972✔
33
                    if let AdditionalProperties::RefOr(properties_schema) =
127✔
34
                        additional_properties.as_ref()
127✔
35
                    {
127✔
36
                        can_resolve_schema(properties_schema, components);
127✔
37
                    }
127✔
38
                }
5,845✔
39
            }
40
            Schema::OneOf(oo) => {
490✔
41
                for item in &oo.items {
1,732✔
42
                    can_resolve_schema(item, components);
1,242✔
43
                }
1,242✔
44
            }
45
            Schema::AllOf(ao) => {
505✔
46
                for item in &ao.items {
1,399✔
47
                    can_resolve_schema(item, components);
894✔
48
                }
894✔
49
            }
50
            _ => panic!("Unknown schema type"),
×
51
        },
52
    }
53
}
12,702✔
54

55
/// Recursively checks that schemas referenced in the given response object exist in the provided map.
56
fn can_resolve_response(response: &RefOr<Response>, components: &Components) {
23✔
57
    match response {
23✔
58
        RefOr::Ref(reference) => {
×
59
            can_resolve_reference(reference, components);
×
60
        }
×
61
        RefOr::T(concrete) => {
23✔
62
            for content in concrete.content.values() {
23✔
63
                can_resolve_schema(&content.schema, components);
23✔
64
            }
23✔
65
        }
66
    }
67
}
23✔
68

69
/// Checks that the given reference can be resolved using the provided map.
70
fn can_resolve_reference(reference: &Ref, components: &Components) {
4,127✔
71
    const SCHEMA_REF_PREFIX: &str = "#/components/schemas/";
4,127✔
72
    const RESPONSE_REF_PREFIX: &str = "#/components/responses/";
4,127✔
73

4,127✔
74
    if reference.ref_location.starts_with(SCHEMA_REF_PREFIX) {
4,127✔
75
        let schema_name = &reference.ref_location[SCHEMA_REF_PREFIX.len()..];
4,104✔
76

4,104✔
77
        match components.schemas.get(schema_name) {
4,104✔
78
            None => assert!(
8✔
79
                components.schemas.contains_key(schema_name),
8✔
80
                "Referenced the unknown schema `{schema_name}`"
8✔
81
            ),
82
            Some(resolved) => can_resolve_schema(resolved, components),
4,096✔
83
        }
84
    } else if reference.ref_location.starts_with(RESPONSE_REF_PREFIX) {
23✔
85
        let response_name = &reference.ref_location[RESPONSE_REF_PREFIX.len()..];
23✔
86

23✔
87
        match components.responses.get(response_name) {
23✔
88
            None => assert!(
×
89
                components.responses.contains_key(response_name),
×
90
                "Referenced the unknown response `{response_name}`"
×
91
            ),
92
            Some(resolved) => can_resolve_response(resolved, components),
23✔
93
        }
94
    } else {
95
        panic!("Invalid reference type");
×
96
    }
97
}
4,119✔
98

99
/// Loops through all registered HTTP handlers and ensures that the referenced schemas
100
/// (inside of request bodies, parameters or responses) exist and can be resolved.
101
///
102
/// # Panics
103
///
104
/// Panics if a referenced schema cannot be resolved.
105
///
106
pub fn can_resolve_api(api: OpenApi) {
5✔
107
    let components = api.components.expect("api has at least one component");
5✔
108

109
    for path_item in api.paths.paths.into_values() {
61✔
110
        for operation in path_item.operations.into_values() {
69✔
111
            if let Some(request_body) = operation.request_body {
69✔
112
                for content in request_body.content.into_values() {
17✔
113
                    can_resolve_schema(&content.schema, &components);
17✔
114
                }
17✔
115
            }
52✔
116

117
            if let Some(parameters) = operation.parameters {
68✔
118
                for parameter in parameters {
189✔
119
                    if let Some(schema) = parameter.schema {
141✔
120
                        can_resolve_schema(&schema, &components);
140✔
121
                    }
140✔
122
                }
123
            }
19✔
124

125
            for response in operation.responses.responses.into_values() {
85✔
126
                match response {
85✔
127
                    RefOr::Ref(reference) => {
23✔
128
                        can_resolve_reference(&reference, &components);
23✔
129
                    }
23✔
130
                    RefOr::T(concrete) => {
62✔
131
                        for content in concrete.content.into_values() {
62✔
132
                            can_resolve_schema(&content.schema, &components);
43✔
133
                        }
43✔
134
                    }
135
                }
136
            }
137
        }
138
    }
139
}
2✔
140

141
pub struct RunnableExample<'a, C, F, Fut>
142
where
143
    F: Fn(TestRequest, C) -> Fut,
144
    Fut: Future<Output = ServiceResponse>,
145
{
146
    pub(crate) components: &'a Components,
147
    pub(crate) http_method: &'a PathItemType,
148
    pub(crate) uri: &'a str,
149
    pub(crate) parameters: &'a Option<Vec<Parameter>>,
150
    pub(crate) body: serde_json::Value,
151
    pub(crate) with_auth: bool,
152
    pub(crate) ctx: C,
153
    pub(crate) session_id: SessionId,
154
    pub(crate) send_test_request: &'a F,
155
}
156

157
impl<'a, C, F, Fut> RunnableExample<'a, C, F, Fut>
158
where
159
    F: Fn(TestRequest, C) -> Fut,
160
    Fut: Future<Output = ServiceResponse>,
161
{
162
    fn get_actix_http_method(&self) -> Method {
8✔
163
        match self.http_method {
8✔
164
            PathItemType::Get => Method::GET,
×
165
            PathItemType::Post => Method::POST,
6✔
166
            PathItemType::Put => Method::PUT,
1✔
167
            PathItemType::Delete => Method::DELETE,
1✔
168
            PathItemType::Options => Method::OPTIONS,
×
169
            PathItemType::Head => Method::HEAD,
×
170
            PathItemType::Patch => Method::PATCH,
×
171
            PathItemType::Trace => Method::TRACE,
×
172
            PathItemType::Connect => Method::CONNECT,
×
173
        }
174
    }
8✔
175

176
    #[allow(clippy::unimplemented)]
177
    fn get_default_parameter_value(schema: &Schema) -> String {
4✔
178
        match schema {
4✔
179
            Schema::Object(obj) => match obj.schema_type {
4✔
180
                SchemaType::String => match &obj.format {
×
181
                    Some(SchemaFormat::KnownFormat(format)) => match format {
×
182
                        KnownFormat::Uuid => Uuid::new_v4().to_string(),
×
183
                        _ => unimplemented!(),
×
184
                    },
185
                    None => "asdf".to_string(),
2✔
186
                    _ => unimplemented!(),
×
187
                },
188
                SchemaType::Integer | SchemaType::Number => "42".to_string(),
2✔
189
                SchemaType::Boolean => "false".to_string(),
×
190
                _ => unimplemented!(),
×
191
            },
192
            _ => unimplemented!(),
×
193
        }
194
    }
4✔
195

196
    fn resolve_schema(&'a self, ref_or: &'a RefOr<Schema>) -> &Schema {
4✔
197
        match ref_or {
4✔
198
            RefOr::Ref(reference) => {
×
199
                const SCHEMA_REF_PREFIX_LEN: usize = "#/components/schemas/".len();
×
200
                can_resolve_reference(reference, self.components);
×
201
                let schema_name = &reference.ref_location[SCHEMA_REF_PREFIX_LEN..];
×
202
                self.resolve_schema(
×
203
                    self.components
×
204
                        .schemas
×
205
                        .get(schema_name)
×
206
                        .expect("checked before"),
×
207
                )
×
208
            }
209
            RefOr::T(concrete) => concrete,
4✔
210
        }
211
    }
4✔
212

213
    fn insert_parameters(&self, parameters: &Vec<Parameter>) -> TestRequest {
2✔
214
        let mut req = TestRequest::default();
2✔
215
        let mut uri = self.uri.to_string();
2✔
216
        let mut query_params = HashMap::new();
2✔
217
        let mut cookies = HashMap::new();
2✔
218

219
        for parameter in parameters {
6✔
220
            let schema = self.resolve_schema(
4✔
221
                parameter
4✔
222
                    .schema
4✔
223
                    .as_ref()
4✔
224
                    .expect("utoipa adds schema everytime"),
4✔
225
            );
4✔
226
            let value = Self::get_default_parameter_value(schema);
4✔
227

4✔
228
            match parameter.parameter_in {
4✔
229
                ParameterIn::Query => {
2✔
230
                    query_params.insert(parameter.name.as_str(), value);
2✔
231
                }
2✔
232
                ParameterIn::Path => {
2✔
233
                    uri = uri.replace(&format!("{{{}}}", parameter.name), value.as_str());
2✔
234
                }
2✔
235
                ParameterIn::Header => {
×
236
                    req = req.append_header((parameter.name.as_str(), value));
×
237
                }
×
238
                ParameterIn::Cookie => {
×
239
                    cookies.insert(parameter.name.as_str(), value);
×
240
                }
×
241
            }
242
        }
243
        if !cookies.is_empty() {
2✔
244
            req = req.append_header((
×
245
                header::COOKIE,
×
246
                serde_urlencoded::to_string(cookies)
×
247
                    .unwrap()
×
248
                    .replace('&', "; "),
×
249
            ));
×
250
        }
2✔
251
        if !query_params.is_empty() {
2✔
252
            uri = format!(
2✔
253
                "{}?{}",
2✔
254
                uri,
2✔
255
                serde_urlencoded::to_string(query_params).unwrap()
2✔
256
            );
2✔
257
        }
2✔
258
        req.uri(uri.as_str())
2✔
259
    }
2✔
260

261
    fn build_request(&self) -> TestRequest {
8✔
262
        let http_method = self.get_actix_http_method();
8✔
263
        let mut req;
264

265
        if let Some(parameters) = self.parameters {
8✔
266
            req = self.insert_parameters(parameters);
2✔
267
        } else {
6✔
268
            req = TestRequest::default().uri(self.uri);
6✔
269
        }
6✔
270
        req = req.method(http_method);
8✔
271

8✔
272
        if self.with_auth {
8✔
273
            req = req.append_header((
6✔
274
                header::AUTHORIZATION,
6✔
275
                Bearer::new(self.session_id.to_string()),
6✔
276
            ));
6✔
277
        }
6✔
278
        req.append_header((header::CONTENT_TYPE, "application/json"))
8✔
279
            .set_json(&self.body)
8✔
280
    }
8✔
281

282
    async fn run(self) -> ServiceResponse {
8✔
283
        let req = self.build_request();
8✔
284
        (self.send_test_request)(req, self.ctx).await
76✔
285
    }
8✔
286

287
    /// # Panics
288
    /// Will panic if an example cannot be run due to incomplete or
289
    /// outdated OpenAPI documentation.
290
    pub(crate) async fn check_for_bad_documentation(self) {
8✔
291
        let res = self.run().await;
76✔
292

293
        if res.status() != 200 {
8✔
294
            let method = res.request().head().method.to_string();
6✔
295
            let path = res.request().path().to_string();
6✔
296
            let body: ErrorResponse = actix_web::test::read_body_json(res).await;
6✔
297

298
            match body.error.as_str() {
6✔
299
                "NotFound" | "MethodNotAllowed" => panic!(
6✔
300
                    "The handler of the example at {method} {path} wasn't reachable. \
×
301
                    Check if the http method and path parameters are correctly set in the documentation."
×
302
                ),
×
303
                "UnableToParseQueryString" => panic!(
6✔
304
                    "The example at {method} {path} threw an UnableToParseQueryString error. \
×
305
                    Check if the query parameters are correctly set in the documentation."
×
306
                ),
×
307
                "BodyDeserializeError" => panic!(
6✔
308
                    "The example at {method} {path} threw an BodyDeserializeError. \
1✔
309
                    Check if there were schema changes and update the request body accordingly."
1✔
310
                ),
1✔
311
                _ => {}
5✔
312
            }
313
        }
2✔
314
    }
7✔
315
}
316

317
/// Runs all example requests against the provided test server to check for bad documentation,
318
/// for example due to incompatible schema changes between the time of writing the request body
319
/// and now. It can also detect if the query parameters are not documented correctly or the
320
/// request path changed.
321
///
322
/// # Panics
323
///
324
/// panics if a Ref occurs in an example, as this case is not yet supported.
325
pub async fn can_run_examples<F, Fut>(
2✔
326
    app_ctx: PostgresContext<NoTls>,
2✔
327
    api: OpenApi,
2✔
328
    send_test_request: F,
2✔
329
) where
2✔
330
    F: Fn(TestRequest, PostgresContext<NoTls>) -> Fut
2✔
331
        + std::panic::UnwindSafe
2✔
332
        + std::marker::Send
2✔
333
        + 'static,
2✔
334
    Fut: Future<Output = ServiceResponse>,
2✔
335
{
2✔
336
    let components = api.components.expect("api has at least one component");
2✔
337

338
    let session_id = app_ctx.default_session_id().await;
2✔
339
    for (uri, path_item) in api.paths.paths {
3✔
340
        for (http_method, operation) in path_item.operations {
3✔
341
            if let Some(request_body) = operation.request_body {
2✔
342
                let with_auth = operation.security.is_some();
2✔
343

344
                for content in request_body.content.into_values() {
2✔
345
                    if let Some(example) = content.example {
2✔
346
                        RunnableExample {
2✔
347
                            components: &components,
2✔
348
                            http_method: &http_method,
2✔
349
                            uri: uri.as_str(),
2✔
350
                            parameters: &operation.parameters,
2✔
351
                            body: example,
2✔
352
                            with_auth,
2✔
353
                            ctx: app_ctx.clone(),
2✔
354
                            session_id,
2✔
355
                            send_test_request: &send_test_request,
2✔
356
                        }
2✔
357
                        .check_for_bad_documentation()
2✔
358
                        .await;
×
359
                    } else {
360
                        for example in content.examples.into_values() {
×
361
                            match example {
×
362
                                RefOr::Ref(_reference) => {
×
363
                                    // This never happened during testing.
×
364
                                    // It is undocumented how the references would look like.
×
365
                                    panic!(
×
366
                                        "checking examples with references is not yet implemented"
×
367
                                    )
×
368
                                }
369
                                RefOr::T(concrete) => {
×
370
                                    if let Some(body) = concrete.value {
×
371
                                        RunnableExample {
×
372
                                            components: &components,
×
373
                                            http_method: &http_method,
×
374
                                            uri: uri.as_str(),
×
375
                                            parameters: &operation.parameters,
×
376
                                            body,
×
377
                                            with_auth,
×
378
                                            ctx: app_ctx.clone(),
×
379
                                            session_id,
×
380
                                            send_test_request: &send_test_request,
×
381
                                        }
×
382
                                        .check_for_bad_documentation()
×
383
                                        .await;
×
384
                                    } else {
×
385
                                        //skip external examples
×
386
                                    }
×
387
                                }
388
                            }
389
                        }
390
                    }
391
                }
392
            }
×
393
        }
394
    }
395
}
1✔
396

397
#[cfg(test)]
398
mod tests {
399
    use super::*;
400
    use crate::contexts::SimpleApplicationContext;
401
    use crate::datasets::upload::Volume;
402
    use crate::ge_context;
403
    use crate::util::server::{configure_extractors, render_404, render_405};
404
    use actix_web::{http, middleware, post, web, App, HttpResponse, Responder};
405
    use serde::Deserialize;
406
    use serde_json::json;
407
    use utoipa::openapi::path::{OperationBuilder, ParameterBuilder, PathItemBuilder};
408
    use utoipa::openapi::request_body::RequestBodyBuilder;
409
    use utoipa::openapi::{
410
        AllOfBuilder, ArrayBuilder, ComponentsBuilder, ContentBuilder, Object, ObjectBuilder,
411
        OneOfBuilder, OpenApiBuilder, PathItemType, PathsBuilder, ResponseBuilder,
412
    };
413
    use utoipa::ToSchema;
414

415
    #[test]
1✔
416
    #[should_panic(expected = "MissingSchema")]
417
    fn detects_missing_array_ref() {
1✔
418
        can_resolve_schema(
1✔
419
            &RefOr::T(
1✔
420
                ArrayBuilder::new()
1✔
421
                    .items(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_object_ref() {
1✔
431
        can_resolve_schema(
1✔
432
            &RefOr::T(
1✔
433
                ObjectBuilder::new()
1✔
434
                    .property("Prop", 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_oneof_ref() {
1✔
444
        can_resolve_schema(
1✔
445
            &RefOr::T(
1✔
446
                OneOfBuilder::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
    #[should_panic(expected = "MissingSchema")]
456
    fn detects_missing_allof_ref() {
1✔
457
        can_resolve_schema(
1✔
458
            &RefOr::T(
1✔
459
                AllOfBuilder::new()
1✔
460
                    .item(Ref::from_schema_name("MissingSchema"))
1✔
461
                    .into(),
1✔
462
            ),
1✔
463
            &Components::new(),
1✔
464
        );
1✔
465
    }
1✔
466

467
    #[test]
1✔
468
    #[should_panic(expected = "Inner")]
469
    fn detects_missing_nested_schema() {
1✔
470
        can_resolve_reference(
1✔
471
            &Ref::from_schema_name("Outer"),
1✔
472
            &ComponentsBuilder::new()
1✔
473
                .schema("Outer", RefOr::Ref(Ref::from_schema_name("Inner")))
1✔
474
                .into(),
1✔
475
        );
1✔
476
    }
1✔
477

478
    #[test]
1✔
479
    fn successfull_api_validation() {
1✔
480
        let api: OpenApi = OpenApiBuilder::new()
1✔
481
            .paths(
1✔
482
                PathsBuilder::new().path(
1✔
483
                    "asdf",
1✔
484
                    PathItemBuilder::new()
1✔
485
                        .operation(
1✔
486
                            PathItemType::Post,
1✔
487
                            OperationBuilder::new()
1✔
488
                                .parameter(
1✔
489
                                    ParameterBuilder::new()
1✔
490
                                        .schema(Some(Ref::from_schema_name("Schema1"))),
1✔
491
                                )
1✔
492
                                .request_body(Some(
1✔
493
                                    RequestBodyBuilder::new()
1✔
494
                                        .content(
1✔
495
                                            "application/json",
1✔
496
                                            ContentBuilder::new()
1✔
497
                                                .schema(Ref::from_schema_name("Schema2"))
1✔
498
                                                .into(),
1✔
499
                                        )
1✔
500
                                        .into(),
1✔
501
                                ))
1✔
502
                                .response(
1✔
503
                                    "200",
1✔
504
                                    ResponseBuilder::new().content(
1✔
505
                                        "application/json",
1✔
506
                                        ContentBuilder::new()
1✔
507
                                            .schema(Ref::from_schema_name("Schema3"))
1✔
508
                                            .into(),
1✔
509
                                    ),
1✔
510
                                ),
1✔
511
                        )
1✔
512
                        .into(),
1✔
513
                ),
1✔
514
            )
1✔
515
            .components(Some(
1✔
516
                ComponentsBuilder::new()
1✔
517
                    .schemas_from_iter([
1✔
518
                        ("Schema1", Schema::default()),
1✔
519
                        ("Schema2", Schema::default()),
1✔
520
                        ("Schema3", Schema::default()),
1✔
521
                    ])
1✔
522
                    .into(),
1✔
523
            ))
1✔
524
            .into();
1✔
525
        can_resolve_api(api);
1✔
526
    }
1✔
527

528
    #[test]
1✔
529
    #[should_panic(expected = "MissingSchema")]
530
    fn detect_unresolvable_request_body() {
1✔
531
        let api: OpenApi = OpenApiBuilder::new()
1✔
532
            .paths(
1✔
533
                PathsBuilder::new().path(
1✔
534
                    "asdf",
1✔
535
                    PathItemBuilder::new()
1✔
536
                        .operation(
1✔
537
                            PathItemType::Post,
1✔
538
                            OperationBuilder::new().request_body(Some(
1✔
539
                                RequestBodyBuilder::new()
1✔
540
                                    .content(
1✔
541
                                        "application/json",
1✔
542
                                        ContentBuilder::new()
1✔
543
                                            .schema(Ref::from_schema_name("MissingSchema"))
1✔
544
                                            .into(),
1✔
545
                                    )
1✔
546
                                    .into(),
1✔
547
                            )),
1✔
548
                        )
1✔
549
                        .into(),
1✔
550
                ),
1✔
551
            )
1✔
552
            .components(Some(
1✔
553
                ComponentsBuilder::new()
1✔
554
                    .schemas_from_iter([
1✔
555
                        ("Schema1", Schema::default()),
1✔
556
                        ("Schema2", Schema::default()),
1✔
557
                        ("Schema3", Schema::default()),
1✔
558
                    ])
1✔
559
                    .into(),
1✔
560
            ))
1✔
561
            .into();
1✔
562
        can_resolve_api(api);
1✔
563
    }
1✔
564

565
    #[test]
1✔
566
    #[should_panic(expected = "MissingSchema")]
567
    fn detect_unresolvable_parameter() {
1✔
568
        let api: OpenApi = OpenApiBuilder::new()
1✔
569
            .paths(
1✔
570
                PathsBuilder::new().path(
1✔
571
                    "asdf",
1✔
572
                    PathItemBuilder::new()
1✔
573
                        .operation(
1✔
574
                            PathItemType::Post,
1✔
575
                            OperationBuilder::new().parameter(
1✔
576
                                ParameterBuilder::new()
1✔
577
                                    .schema(Some(Ref::from_schema_name("MissingSchema"))),
1✔
578
                            ),
1✔
579
                        )
1✔
580
                        .into(),
1✔
581
                ),
1✔
582
            )
1✔
583
            .components(Some(
1✔
584
                ComponentsBuilder::new()
1✔
585
                    .schemas_from_iter([
1✔
586
                        ("Schema1", Schema::default()),
1✔
587
                        ("Schema2", Schema::default()),
1✔
588
                        ("Schema3", Schema::default()),
1✔
589
                    ])
1✔
590
                    .into(),
1✔
591
            ))
1✔
592
            .into();
1✔
593
        can_resolve_api(api);
1✔
594
    }
1✔
595

596
    #[test]
1✔
597
    #[should_panic(expected = "MissingSchema")]
598
    fn detect_unresolvable_response() {
1✔
599
        let api: OpenApi = OpenApiBuilder::new()
1✔
600
            .paths(
1✔
601
                PathsBuilder::new().path(
1✔
602
                    "asdf",
1✔
603
                    PathItemBuilder::new()
1✔
604
                        .operation(
1✔
605
                            PathItemType::Post,
1✔
606
                            OperationBuilder::new().response(
1✔
607
                                "200",
1✔
608
                                ResponseBuilder::new().content(
1✔
609
                                    "application/json",
1✔
610
                                    ContentBuilder::new()
1✔
611
                                        .schema(Ref::from_schema_name("MissingSchema"))
1✔
612
                                        .into(),
1✔
613
                                ),
1✔
614
                            ),
1✔
615
                        )
1✔
616
                        .into(),
1✔
617
                ),
1✔
618
            )
1✔
619
            .components(Some(
1✔
620
                ComponentsBuilder::new()
1✔
621
                    .schemas_from_iter([
1✔
622
                        ("Schema1", Schema::default()),
1✔
623
                        ("Schema2", Schema::default()),
1✔
624
                        ("Schema3", Schema::default()),
1✔
625
                    ])
1✔
626
                    .into(),
1✔
627
            ))
1✔
628
            .into();
1✔
629
        can_resolve_api(api);
1✔
630
    }
1✔
631

632
    #[derive(Deserialize)]
6✔
633
    struct DummyQueryParams {
634
        #[serde(rename = "x")]
635
        _x: String,
636
    }
637

638
    // adding path and query parameter to ensure parameter insertion works
639
    #[post("/test/{id}")]
2✔
640
    #[allow(clippy::unused_async)] // the function signature of request handlers requires it
641
    async fn dummy_handler(
1✔
642
        _id: web::Path<u32>,
1✔
643
        _params: web::Query<DummyQueryParams>,
1✔
644
        _body: web::Json<Volume>,
1✔
645
    ) -> impl Responder {
1✔
646
        HttpResponse::Ok()
1✔
647
    }
1✔
648

649
    async fn dummy_send_test_request<C: SimpleApplicationContext>(
2✔
650
        req: TestRequest,
2✔
651
        ctx: C,
2✔
652
    ) -> ServiceResponse {
2✔
653
        let app = actix_web::test::init_service(
2✔
654
            App::new()
2✔
655
                .app_data(web::Data::new(ctx))
2✔
656
                .wrap(
2✔
657
                    middleware::ErrorHandlers::default()
2✔
658
                        .handler(http::StatusCode::NOT_FOUND, render_404)
2✔
659
                        .handler(http::StatusCode::METHOD_NOT_ALLOWED, render_405),
2✔
660
                )
2✔
661
                .configure(configure_extractors)
2✔
662
                .service(dummy_handler),
2✔
663
        )
2✔
664
        .await;
×
665
        actix_web::test::call_service(&app, req.to_request())
2✔
666
            .await
×
667
            .map_into_boxed_body()
2✔
668
    }
2✔
669

670
    async fn run_dummy_example(app_ctx: PostgresContext<NoTls>, example: serde_json::Value) {
2✔
671
        can_run_examples(
2✔
672
            app_ctx,
2✔
673
            OpenApiBuilder::new()
2✔
674
                .paths(
2✔
675
                    PathsBuilder::new().path(
2✔
676
                        "/test/{id}",
2✔
677
                        PathItemBuilder::new()
2✔
678
                            .operation(
2✔
679
                                PathItemType::Post,
2✔
680
                                OperationBuilder::new()
2✔
681
                                    .parameter(
2✔
682
                                        ParameterBuilder::new()
2✔
683
                                            .name("id")
2✔
684
                                            .parameter_in(ParameterIn::Path)
2✔
685
                                            .schema(Some(RefOr::T(
2✔
686
                                                ObjectBuilder::new()
2✔
687
                                                    .schema_type(SchemaType::Integer)
2✔
688
                                                    .format(Some(SchemaFormat::KnownFormat(
2✔
689
                                                        KnownFormat::Int32,
2✔
690
                                                    )))
2✔
691
                                                    .into(),
2✔
692
                                            ))),
2✔
693
                                    )
2✔
694
                                    .parameter(
2✔
695
                                        ParameterBuilder::new()
2✔
696
                                            .name("x")
2✔
697
                                            .parameter_in(ParameterIn::Query)
2✔
698
                                            .schema(Some(RefOr::T(
2✔
699
                                                Object::with_type(SchemaType::String).into(),
2✔
700
                                            ))),
2✔
701
                                    )
2✔
702
                                    .request_body(Some(
2✔
703
                                        RequestBodyBuilder::new()
2✔
704
                                            .content(
2✔
705
                                                "application/json",
2✔
706
                                                ContentBuilder::new()
2✔
707
                                                    .schema(Volume::schema().1)
2✔
708
                                                    .example(Some(example))
2✔
709
                                                    .into(),
2✔
710
                                            )
2✔
711
                                            .into(),
2✔
712
                                    )),
2✔
713
                            )
2✔
714
                            .into(),
2✔
715
                    ),
2✔
716
                )
2✔
717
                .components(Some(
2✔
718
                    ComponentsBuilder::new()
2✔
719
                        .schemas_from_iter([
2✔
720
                            ("Schema1", Schema::default()),
2✔
721
                            ("Schema2", Schema::default()),
2✔
722
                            ("Schema3", Schema::default()),
2✔
723
                        ])
2✔
724
                        .into(),
2✔
725
                ))
2✔
726
                .into(),
2✔
727
            dummy_send_test_request,
2✔
728
        )
2✔
729
        .await;
×
730
    }
1✔
731

732
    #[ge_context::test(expect_panic = "BodyDeserializeError")]
2✔
733
    async fn detects_bodydeserializeerror(app_ctx: PostgresContext<NoTls>) {
1✔
734
        run_dummy_example(app_ctx, json!({"name": "note-path_field_missing"})).await;
1✔
735
    }
×
736

737
    #[ge_context::test]
2✔
738
    async fn successfull_example_run(app_ctx: PostgresContext<NoTls>) {
1✔
739
        run_dummy_example(app_ctx, json!({"name": "Files", "path": "/path/to/files"})).await;
1✔
740
    }
1✔
741
}
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