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

geo-engine / geoengine / 13809415963

12 Mar 2025 10:42AM UTC coverage: 90.026% (-0.05%) from 90.076%
13809415963

Pull #1013

github

web-flow
Merge b51e2554c into c96026921
Pull Request #1013: Update-utoipa

787 of 935 new or added lines in 41 files covered. (84.17%)

28 existing lines in 10 files now uncovered.

125995 of 139954 relevant lines covered (90.03%)

57510.86 hits per line

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

90.87
/services/src/api/handlers/layers.rs
1
use crate::api::model::datatypes::{DataProviderId, LayerId};
2
use crate::api::model::responses::IdResponse;
3
use crate::config::get_config_element;
4
use crate::contexts::ApplicationContext;
5
use crate::datasets::{schedule_raster_dataset_from_workflow_task, RasterDatasetFromWorkflow};
6
use crate::error::Error::NotImplemented;
7
use crate::error::{Error, Result};
8
use crate::layers::layer::{
9
    AddLayer, AddLayerCollection, CollectionItem, Layer, LayerCollection, LayerCollectionListing,
10
    ProviderLayerCollectionId, UpdateLayer, UpdateLayerCollection,
11
};
12
use crate::layers::listing::{
13
    LayerCollectionId, LayerCollectionProvider, ProviderCapabilities, SearchParameters,
14
};
15
use crate::layers::storage::{LayerDb, LayerProviderDb, LayerProviderListingOptions};
16
use crate::util::extractors::{ValidatedJson, ValidatedQuery};
17
use crate::util::workflows::validate_workflow;
18
use crate::workflows::registry::WorkflowRegistry;
19
use crate::workflows::workflow::WorkflowId;
20
use crate::{contexts::SessionContext, layers::layer::LayerCollectionListOptions};
21
use actix_web::{web, FromRequest, HttpResponse, Responder};
22
use geoengine_datatypes::primitives::{BandSelection, QueryRectangle};
23
use geoengine_operators::engine::WorkflowOperatorPath;
24
use serde::{Deserialize, Serialize};
25
use std::sync::Arc;
26
use utoipa::IntoParams;
27

28
use super::tasks::TaskResponse;
29

30
pub const ROOT_PROVIDER_ID: DataProviderId =
31
    DataProviderId::from_u128(0x1c3b_8042_300b_485c_95b5_0147_d9dc_068d);
32

33
pub const ROOT_COLLECTION_ID: DataProviderId =
34
    DataProviderId::from_u128(0xf242_4474_ef24_4c18_ab84_6859_2e12_ce48);
35

36
pub(crate) fn init_layer_routes<C>(cfg: &mut web::ServiceConfig)
342✔
37
where
342✔
38
    C: ApplicationContext,
342✔
39
    C::Session: FromRequest,
342✔
40
{
342✔
41
    cfg.service(
342✔
42
        web::scope("/layers")
342✔
43
            .service(
342✔
44
                web::scope("/collections")
342✔
45
                    .service(
342✔
46
                        web::scope("/search")
342✔
47
                            .route(
342✔
48
                                "/autocomplete/{provider}/{collection}",
342✔
49
                                web::get().to(autocomplete_handler::<C>),
342✔
50
                            )
342✔
51
                            .route(
342✔
52
                                "/{provider}/{collection}",
342✔
53
                                web::get().to(search_handler::<C>),
342✔
54
                            ),
342✔
55
                    )
342✔
56
                    .route("", web::get().to(list_root_collections_handler::<C>))
342✔
57
                    .route(
342✔
58
                        "/{provider}/{collection}",
342✔
59
                        web::get().to(list_collection_handler::<C>),
342✔
60
                    ),
342✔
61
            )
342✔
62
            .service(
342✔
63
                web::scope("/{provider}")
342✔
64
                    .route(
342✔
65
                        "/capabilities",
342✔
66
                        web::get().to(provider_capabilities_handler::<C>),
342✔
67
                    )
342✔
68
                    .service(
342✔
69
                        web::scope("/{layer}")
342✔
70
                            .route(
342✔
71
                                "/workflowId",
342✔
72
                                web::post().to(layer_to_workflow_id_handler::<C>),
342✔
73
                            )
342✔
74
                            .route("/dataset", web::post().to(layer_to_dataset::<C>))
342✔
75
                            .route("", web::get().to(layer_handler::<C>)),
342✔
76
                    ),
342✔
77
            ),
342✔
78
    )
342✔
79
    .service(
342✔
80
        web::scope("/layerDb")
342✔
81
            .service(
342✔
82
                web::scope("/layers/{layer}")
342✔
83
                    .route("", web::put().to(update_layer::<C>))
342✔
84
                    .route("", web::delete().to(remove_layer::<C>)),
342✔
85
            )
342✔
86
            .service(
342✔
87
                web::scope("/collections/{collection}")
342✔
88
                    .service(
342✔
89
                        web::scope("/layers")
342✔
90
                            .route("", web::post().to(add_layer::<C>))
342✔
91
                            .service(
342✔
92
                                web::resource("/{layer}")
342✔
93
                                    .route(web::post().to(add_existing_layer_to_collection::<C>))
342✔
94
                                    .route(web::delete().to(remove_layer_from_collection::<C>)),
342✔
95
                            ),
342✔
96
                    )
342✔
97
                    .service(
342✔
98
                        web::scope("/collections")
342✔
99
                            .route("", web::post().to(add_collection::<C>))
342✔
100
                            .service(
342✔
101
                                web::resource("/{sub_collection}")
342✔
102
                                    .route(
342✔
103
                                        web::post().to(add_existing_collection_to_collection::<C>),
342✔
104
                                    )
342✔
105
                                    .route(
342✔
106
                                        web::delete().to(remove_collection_from_collection::<C>),
342✔
107
                                    ),
342✔
108
                            ),
342✔
109
                    )
342✔
110
                    .route("", web::put().to(update_collection::<C>))
342✔
111
                    .route("", web::delete().to(remove_collection::<C>)),
342✔
112
            ),
342✔
113
    );
342✔
114
}
342✔
115

116
/// List all layer collections
117
#[utoipa::path(
16✔
118
    tag = "Layers",
16✔
119
    get,
16✔
120
    path = "/layers/collections",
16✔
121
    responses(
16✔
122
        (status = 200, description = "OK", body = LayerCollection,
16✔
123
            example = json!({
16✔
124
                "id": {
16✔
125
                  "providerId": "1c3b8042-300b-485c-95b5-0147d9dc068d",
16✔
126
                  "collectionId": "f2424474-ef24-4c18-ab84-68592e12ce48"
16✔
127
                },
16✔
128
                "name": "Layer Providers",
16✔
129
                "description": "All available Geo Engine layer providers",
16✔
130
                "items": [
16✔
131
                  {
16✔
132
                    "type": "collection",
16✔
133
                    "id": {
16✔
134
                      "providerId": "ac50ed0d-c9a0-41f8-9ce8-35fc9e38299b",
16✔
135
                      "collectionId": "546073b6-d535-4205-b601-99675c9f6dd7"
16✔
136
                    },
16✔
137
                    "name": "Datasets",
16✔
138
                    "description": "Basic Layers for all Datasets"
16✔
139
                  },
16✔
140
                  {
16✔
141
                    "type": "collection",
16✔
142
                    "id": {
16✔
143
                      "providerId": "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74",
16✔
144
                      "collectionId": "05102bb3-a855-4a37-8a8a-30026a91fef1"
16✔
145
                    },
16✔
146
                    "name": "Layers",
16✔
147
                    "description": "All available Geo Engine layers"
16✔
148
                  }
16✔
149
                ],
16✔
150
                "entryLabel": null,
16✔
151
                "properties": []
16✔
152
              })
16✔
153
        )
16✔
154
    ),
16✔
155
    params(
16✔
156
        LayerCollectionListOptions
16✔
157
    ),
16✔
158
    security(
16✔
159
        ("session_token" = [])
16✔
160
    )
16✔
161
)]
16✔
162
async fn list_root_collections_handler<C: ApplicationContext>(
×
163
    session: C::Session,
×
164
    app_ctx: web::Data<C>,
×
165
    options: ValidatedQuery<LayerCollectionListOptions>,
×
166
) -> Result<impl Responder> {
×
167
    let root_collection = get_layer_providers(session, options, app_ctx).await?;
×
168

169
    Ok(web::Json(root_collection))
×
170
}
×
171

172
async fn get_layer_providers<C: ApplicationContext>(
×
173
    session: C::Session,
×
174
    mut options: ValidatedQuery<LayerCollectionListOptions>,
×
175
    app_ctx: web::Data<C>,
×
176
) -> Result<LayerCollection> {
×
177
    let mut providers = vec![];
×
178
    if options.offset == 0 && options.limit > 0 {
×
179
        providers.push(CollectionItem::Collection(LayerCollectionListing {
×
NEW
180
            r#type: Default::default(),
×
181
            id: ProviderLayerCollectionId {
×
182
                provider_id: crate::layers::storage::INTERNAL_PROVIDER_ID,
×
183
                collection_id: LayerCollectionId(
×
184
                    crate::layers::storage::INTERNAL_LAYER_DB_ROOT_COLLECTION_ID.to_string(),
×
185
                ),
×
186
            },
×
187
            name: "Data Catalog".to_string(),
×
188
            description: "Catalog of data and workflows".to_string(),
×
189
            properties: Default::default(),
×
190
        }));
×
191

×
192
        options.limit -= 1;
×
193
    }
×
194

195
    let external = app_ctx.session_context(session).db();
×
196
    for provider_listing in external
×
197
        .list_layer_providers(LayerProviderListingOptions {
×
198
            offset: options.offset,
×
199
            limit: options.limit,
×
200
        })
×
201
        .await?
×
202
    {
203
        if provider_listing.priority <= -1000 {
×
204
            continue; // skip providers that are disabled
×
205
        };
×
206

207
        // TODO: resolve providers in parallel
208
        let provider = match external.load_layer_provider(provider_listing.id).await {
×
209
            Ok(provider) => provider,
×
210
            Err(err) => {
×
211
                log::error!("Error loading provider: {err:?}");
×
212
                continue;
×
213
            }
214
        };
215

216
        if !provider.capabilities().listing {
×
217
            continue; // skip providers that do not support listing
×
218
        }
×
219

220
        let collection_id = match provider.get_root_layer_collection_id().await {
×
221
            Ok(root) => root,
×
222
            Err(err) => {
×
223
                log::error!(
×
224
                    "Error loading provider {}, could not get root collection id: {err:?}",
×
225
                    provider_listing.id,
226
                );
227
                continue;
×
228
            }
229
        };
230

231
        providers.push(CollectionItem::Collection(LayerCollectionListing {
×
NEW
232
            r#type: Default::default(),
×
233
            id: ProviderLayerCollectionId {
×
234
                provider_id: provider_listing.id,
×
235
                collection_id,
×
236
            },
×
237
            name: provider.name().to_owned(),
×
238
            description: provider.description().to_owned(),
×
239
            properties: Default::default(),
×
240
        }));
×
241
    }
242
    let root_collection = LayerCollection {
×
243
        id: ProviderLayerCollectionId {
×
244
            provider_id: ROOT_PROVIDER_ID.into(),
×
245
            collection_id: LayerCollectionId(ROOT_COLLECTION_ID.to_string()),
×
246
        },
×
247
        name: "Layer Providers".to_string(),
×
248
        description: "All available Geo Engine layer providers".to_string(),
×
249
        items: providers,
×
250
        entry_label: None,
×
251
        properties: vec![],
×
252
    };
×
253
    Ok(root_collection)
×
254
}
×
255

256
/// List the contents of the collection of the given provider
257
#[utoipa::path(
16✔
258
    tag = "Layers",
16✔
259
    get,
16✔
260
    path = "/layers/collections/{provider}/{collection}",
16✔
261
    responses(
16✔
262
        (status = 200, description = "OK", body = LayerCollection,
16✔
263
            example = json!({
16✔
264
                "id": {
16✔
265
                  "providerId": "ac50ed0d-c9a0-41f8-9ce8-35fc9e38299b",
16✔
266
                  "collectionId": "546073b6-d535-4205-b601-99675c9f6dd7"
16✔
267
                },
16✔
268
                "name": "Datasets",
16✔
269
                "description": "Basic Layers for all Datasets",
16✔
270
                "items": [
16✔
271
                  {
16✔
272
                    "type": "layer",
16✔
273
                    "id": {
16✔
274
                      "providerId": "ac50ed0d-c9a0-41f8-9ce8-35fc9e38299b",
16✔
275
                      "layerId": "9ee3619e-d0f9-4ced-9c44-3d407c3aed69"
16✔
276
                    },
16✔
277
                    "name": "Land Cover",
16✔
278
                    "description": "Land Cover derived from MODIS/Terra+Aqua Land Cover"
16✔
279
                  },
16✔
280
                  {
16✔
281
                    "type": "layer",
16✔
282
                    "id": {
16✔
283
                      "providerId": "ac50ed0d-c9a0-41f8-9ce8-35fc9e38299b",
16✔
284
                      "layerId": "36574dc3-560a-4b09-9d22-d5945f2b8093"
16✔
285
                    },
16✔
286
                    "name": "NDVI",
16✔
287
                    "description": "NDVI data from MODIS"
16✔
288
                  }
16✔
289
                ],
16✔
290
                "entryLabel": null,
16✔
291
                "properties": []
16✔
292
              })
16✔
293
        )
16✔
294
    ),
16✔
295
    params(
16✔
296
        ("provider" = DataProviderId, description = "Data provider id"),
16✔
297
        ("collection" = LayerCollectionId, description = "Layer collection id"),
16✔
298
        LayerCollectionListOptions
16✔
299
    ),
16✔
300
    security(
16✔
301
        ("session_token" = [])
16✔
302
    )
16✔
303
)]
16✔
304
async fn list_collection_handler<C: ApplicationContext>(
×
305
    app_ctx: web::Data<C>,
×
306
    path: web::Path<(DataProviderId, LayerCollectionId)>,
×
307
    options: ValidatedQuery<LayerCollectionListOptions>,
×
308
    session: C::Session,
×
309
) -> Result<impl Responder> {
×
310
    let (provider, item) = path.into_inner();
×
311

×
312
    if provider == ROOT_PROVIDER_ID && item == LayerCollectionId(ROOT_COLLECTION_ID.to_string()) {
×
313
        let collection = get_layer_providers(session.clone(), options, app_ctx).await?;
×
314
        return Ok(web::Json(collection));
×
315
    }
×
316

×
317
    let db = app_ctx.session_context(session).db();
×
318

×
319
    if provider == crate::layers::storage::INTERNAL_PROVIDER_ID.into() {
×
320
        let collection = db
×
321
            .load_layer_collection(&item, options.into_inner())
×
322
            .await?;
×
323

324
        return Ok(web::Json(collection));
×
325
    }
×
326

327
    let collection = db
×
328
        .load_layer_provider(provider.into())
×
329
        .await?
×
330
        .load_layer_collection(&item, options.into_inner())
×
331
        .await?;
×
332

333
    Ok(web::Json(collection))
×
334
}
×
335

336
// Returns the capabilities of the given provider
337
#[utoipa::path(
16✔
338
    tag = "Layers",
16✔
339
    get,
16✔
340
    path = "/layers/{provider}/capabilities",
16✔
341
    responses(
16✔
342
        (status = 200, description = "OK", body = ProviderCapabilities,
16✔
343
            example = json!({
16✔
344
                "listing": true,
16✔
345
                "search": {
16✔
346
                    "search_types": {
16✔
347
                        "fulltext": true,
16✔
348
                        "prefix": true
16✔
349
                    },
16✔
350
                    "autocomplete": true,
16✔
351
                    "filters": [],
16✔
352
                }
16✔
353
            })
16✔
354
        )
16✔
355
    ),
16✔
356
    params(
16✔
357
        ("provider" = DataProviderId, description = "Data provider id")
16✔
358
    ),
16✔
359
    security(
16✔
360
        ("session_token" = [])
16✔
361
    )
16✔
362
)]
16✔
363
async fn provider_capabilities_handler<C: ApplicationContext>(
1✔
364
    app_ctx: web::Data<C>,
1✔
365
    path: web::Path<DataProviderId>,
1✔
366
    session: C::Session,
1✔
367
) -> Result<web::Json<ProviderCapabilities>> {
1✔
368
    let provider = path.into_inner();
1✔
369

1✔
370
    if provider == ROOT_PROVIDER_ID {
1✔
371
        return Err(NotImplemented {
×
372
            message: "Global search is not supported".to_string(),
×
373
        });
×
374
    }
1✔
375

1✔
376
    let db = app_ctx.session_context(session).db();
1✔
377

378
    let capabilities = match provider.into() {
1✔
379
        crate::layers::storage::INTERNAL_PROVIDER_ID => LayerCollectionProvider::capabilities(&db),
1✔
380
        provider => db.load_layer_provider(provider).await?.capabilities(),
×
381
    };
382

383
    Ok(web::Json(capabilities))
1✔
384
}
1✔
385

386
/// Searches the contents of the collection of the given provider
387
#[utoipa::path(
16✔
388
    tag = "Layers",
16✔
389
    get,
16✔
390
    path = "/layers/collections/search/{provider}/{collection}",
16✔
391
    responses(
16✔
392
        (status = 200, description = "OK", body = LayerCollection,
16✔
393
            example = json!({
16✔
394
                "id": {
16✔
395
                    "providerId": "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74",
16✔
396
                    "collectionId": "05102bb3-a855-4a37-8a8a-30026a91fef1"
16✔
397
                },
16✔
398
                "name": "Layers",
16✔
399
                "description": "All available Geo Engine layers",
16✔
400
                "items": [
16✔
401
                    {
16✔
402
                        "type": "collection",
16✔
403
                        "id": {
16✔
404
                            "providerId": "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74",
16✔
405
                            "collectionId": "a29f77cc-51ce-466b-86ef-d0ab2170bc0a"
16✔
406
                        },
16✔
407
                        "name": "An empty collection",
16✔
408
                        "description": "There is nothing here",
16✔
409
                        "properties": []
16✔
410
                    },
16✔
411
                    {
16✔
412
                        "type": "layer",
16✔
413
                        "id": {
16✔
414
                            "providerId": "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74",
16✔
415
                            "layerId": "b75db46e-2b9a-4a86-b33f-bc06a73cd711"
16✔
416
                        },
16✔
417
                        "name": "Ports in Germany",
16✔
418
                        "description": "Natural Earth Ports point filtered with Germany polygon",
16✔
419
                        "properties": []
16✔
420
                    }
16✔
421
                ],
16✔
422
                "entryLabel": null,
16✔
423
                "properties": []
16✔
424
            })
16✔
425
        )
16✔
426
    ),
16✔
427
    params(
16✔
428
        ("provider" = DataProviderId, description = "Data provider id", example = "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74"),
16✔
429
        ("collection" = LayerCollectionId, description = "Layer collection id", example = "05102bb3-a855-4a37-8a8a-30026a91fef1"),
16✔
430
        SearchParameters
16✔
431
    ),
16✔
432
    security(
16✔
433
        ("session_token" = [])
16✔
434
    )
16✔
435
)]
16✔
436
async fn search_handler<C: ApplicationContext>(
1✔
437
    session: C::Session,
1✔
438
    app_ctx: web::Data<C>,
1✔
439
    path: web::Path<(DataProviderId, LayerCollectionId)>,
1✔
440
    options: ValidatedQuery<SearchParameters>,
1✔
441
) -> Result<web::Json<LayerCollection>> {
1✔
442
    let (provider, collection) = path.into_inner();
1✔
443

1✔
444
    if provider == ROOT_PROVIDER_ID {
1✔
445
        return Err(NotImplemented {
×
446
            message: "Global search is not supported".to_string(),
×
447
        });
×
448
    }
1✔
449

1✔
450
    let db = app_ctx.session_context(session).db();
1✔
451

452
    let collection = match provider.into() {
1✔
453
        crate::layers::storage::INTERNAL_PROVIDER_ID => {
454
            LayerCollectionProvider::search(&db, &collection, options.into_inner()).await?
1✔
455
        }
456
        provider => {
×
457
            db.load_layer_provider(provider)
×
458
                .await?
×
459
                .search(&collection, options.into_inner())
×
460
                .await?
×
461
        }
462
    };
463

464
    Ok(web::Json(collection))
1✔
465
}
1✔
466

467
/// Autocompletes the search on the contents of the collection of the given provider
468
#[utoipa::path(
16✔
469
    tag = "Layers",
16✔
470
    get,
16✔
471
    path = "/layers/collections/search/autocomplete/{provider}/{collection}",
16✔
472
    responses(
16✔
473
        (status = 200, description = "OK", body = Vec<String>,
16✔
474
            example = json!(["An empty collection", "Ports in Germany"])
16✔
475
        )
16✔
476
    ),
16✔
477
    params(
16✔
478
        ("provider" = DataProviderId, description = "Data provider id", example = "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74"),
16✔
479
        ("collection" = LayerCollectionId, description = "Layer collection id", example = "05102bb3-a855-4a37-8a8a-30026a91fef1"),
16✔
480
        SearchParameters
16✔
481
    ),
16✔
482
    security(
16✔
483
        ("session_token" = [])
16✔
484
    )
16✔
485
)]
16✔
486
async fn autocomplete_handler<C: ApplicationContext>(
1✔
487
    session: C::Session,
1✔
488
    app_ctx: web::Data<C>,
1✔
489
    path: web::Path<(DataProviderId, LayerCollectionId)>,
1✔
490
    options: ValidatedQuery<SearchParameters>,
1✔
491
) -> Result<web::Json<Vec<String>>> {
1✔
492
    let (provider, collection) = path.into_inner();
1✔
493

1✔
494
    if provider == ROOT_PROVIDER_ID {
1✔
495
        return Err(NotImplemented {
×
496
            message: "Global search is not supported".to_string(),
×
497
        });
×
498
    }
1✔
499

1✔
500
    let db = app_ctx.session_context(session).db();
1✔
501

502
    let suggestions = match provider.into() {
1✔
503
        crate::layers::storage::INTERNAL_PROVIDER_ID => {
504
            LayerCollectionProvider::autocomplete_search(&db, &collection, options.into_inner())
1✔
505
                .await?
1✔
506
        }
507
        provider => {
×
508
            db.load_layer_provider(provider)
×
509
                .await?
×
510
                .autocomplete_search(&collection, options.into_inner())
×
511
                .await?
×
512
        }
513
    };
514

515
    Ok(web::Json(suggestions))
1✔
516
}
1✔
517

518
/// Retrieves the layer of the given provider
519
#[utoipa::path(
16✔
520
    tag = "Layers",
16✔
521
    get,
16✔
522
    path = "/layers/{provider}/{layer}",
16✔
523
    responses(
16✔
524
        (status = 200, description = "OK", body = Layer,
16✔
525
            example = json!({
16✔
526
                "id": {
16✔
527
                  "providerId": "ac50ed0d-c9a0-41f8-9ce8-35fc9e38299b",
16✔
528
                  "layerId": "9ee3619e-d0f9-4ced-9c44-3d407c3aed69"
16✔
529
                },
16✔
530
                "name": "Land Cover",
16✔
531
                "description": "Land Cover derived from MODIS/Terra+Aqua Land Cover",
16✔
532
                "workflow": {
16✔
533
                  "type": "Raster",
16✔
534
                  "operator": {
16✔
535
                    "type": "GdalSource",
16✔
536
                    "params": {
16✔
537
                      "data": {
16✔
538
                        "type": "internal",
16✔
539
                        "datasetId": "9ee3619e-d0f9-4ced-9c44-3d407c3aed69"
16✔
540
                      }
16✔
541
                    }
16✔
542
                  }
16✔
543
                },
16✔
544
                "symbology": {
16✔
545
                  "type": "raster",
16✔
546
                  "opacity": 1,
16✔
547
                  "colorizer": {
16✔
548
                    "type": "palette",
16✔
549
                    "colors": {
16✔
550
                      "0": [
16✔
551
                        134,
16✔
552
                        201,
16✔
553
                        227,
16✔
554
                        255
16✔
555
                      ],
16✔
556
                      "1": [
16✔
557
                        30,
16✔
558
                        129,
16✔
559
                        62,
16✔
560
                        255
16✔
561
                      ],
16✔
562
                      "2": [
16✔
563
                        59,
16✔
564
                        194,
16✔
565
                        212,
16✔
566
                        255
16✔
567
                      ],
16✔
568
                      "3": [
16✔
569
                        157,
16✔
570
                        194,
16✔
571
                        63,
16✔
572
                        255
16✔
573
                      ],
16✔
574
                      "4": [
16✔
575
                        159,
16✔
576
                        225,
16✔
577
                        127,
16✔
578
                        255
16✔
579
                      ],
16✔
580
                      "5": [
16✔
581
                        125,
16✔
582
                        194,
16✔
583
                        127,
16✔
584
                        255
16✔
585
                      ],
16✔
586
                      "6": [
16✔
587
                        195,
16✔
588
                        127,
16✔
589
                        126,
16✔
590
                        255
16✔
591
                      ],
16✔
592
                      "7": [
16✔
593
                        188,
16✔
594
                        221,
16✔
595
                        190,
16✔
596
                        255
16✔
597
                      ],
16✔
598
                      "8": [
16✔
599
                        224,
16✔
600
                        223,
16✔
601
                        133,
16✔
602
                        255
16✔
603
                      ],
16✔
604
                      "9": [
16✔
605
                        226,
16✔
606
                        221,
16✔
607
                        7,
16✔
608
                        255
16✔
609
                      ],
16✔
610
                      "10": [
16✔
611
                        223,
16✔
612
                        192,
16✔
613
                        125,
16✔
614
                        255
16✔
615
                      ],
16✔
616
                      "11": [
16✔
617
                        66,
16✔
618
                        128,
16✔
619
                        189,
16✔
620
                        255
16✔
621
                      ],
16✔
622
                      "12": [
16✔
623
                        225,
16✔
624
                        222,
16✔
625
                        127,
16✔
626
                        255
16✔
627
                      ],
16✔
628
                      "13": [
16✔
629
                        253,
16✔
630
                        2,
16✔
631
                        0,
16✔
632
                        255
16✔
633
                      ],
16✔
634
                      "14": [
16✔
635
                        162,
16✔
636
                        159,
16✔
637
                        66,
16✔
638
                        255
16✔
639
                      ],
16✔
640
                      "15": [
16✔
641
                        255,
16✔
642
                        255,
16✔
643
                        255,
16✔
644
                        255
16✔
645
                      ],
16✔
646
                      "16": [
16✔
647
                        192,
16✔
648
                        192,
16✔
649
                        192,
16✔
650
                        255
16✔
651
                      ]
16✔
652
                    },
16✔
653
                    "noDataColor": [
16✔
654
                      0,
16✔
655
                      0,
16✔
656
                      0,
16✔
657
                      0
16✔
658
                    ],
16✔
659
                    "defaultColor": [
16✔
660
                      0,
16✔
661
                      0,
16✔
662
                      0,
16✔
663
                      0
16✔
664
                    ]
16✔
665
                  }
16✔
666
                },
16✔
667
                "properties": [],
16✔
668
                "metadata": {}
16✔
669
              })
16✔
670
        )
16✔
671
    ),
16✔
672
    params(
16✔
673
        ("provider" = DataProviderId, description = "Data provider id"),
16✔
674
        ("layer" = LayerCollectionId, description = "Layer id"),
16✔
675
    ),
16✔
676
    security(
16✔
677
        ("session_token" = [])
16✔
678
    )
16✔
679
)]
16✔
680
async fn layer_handler<C: ApplicationContext>(
×
681
    app_ctx: web::Data<C>,
×
682
    path: web::Path<(DataProviderId, LayerId)>,
×
683
    session: C::Session,
×
684
) -> Result<impl Responder> {
×
685
    let (provider, item) = path.into_inner();
×
686

×
687
    let db = app_ctx.session_context(session).db();
×
688

×
689
    if provider == crate::layers::storage::INTERNAL_PROVIDER_ID.into() {
×
690
        let collection = db.load_layer(&item.into()).await?;
×
691

692
        return Ok(web::Json(collection));
×
693
    }
×
694

695
    let collection = db
×
696
        .load_layer_provider(provider.into())
×
697
        .await?
×
698
        .load_layer(&item.into())
×
699
        .await?;
×
700

701
    Ok(web::Json(collection))
×
702
}
×
703

704
/// Registers a layer from a provider as a workflow and returns the workflow id
705
#[utoipa::path(
16✔
706
    tag = "Layers",
16✔
707
    post,
16✔
708
    path = "/layers/{provider}/{layer}/workflowId",
16✔
709
    responses(
16✔
710
        (status = 200, response = IdResponse::<WorkflowId>)
16✔
711
    ),
16✔
712
    params(
16✔
713
        ("provider" = DataProviderId, description = "Data provider id"),
16✔
714
        ("layer" = LayerCollectionId, description = "Layer id"),
16✔
715
    ),
16✔
716
    security(
16✔
717
        ("session_token" = [])
16✔
718
    )
16✔
719
)]
16✔
720
async fn layer_to_workflow_id_handler<C: ApplicationContext>(
×
721
    app_ctx: web::Data<C>,
×
722
    path: web::Path<(DataProviderId, LayerId)>,
×
723
    session: C::Session,
×
724
) -> Result<web::Json<IdResponse<WorkflowId>>> {
×
725
    let (provider, item) = path.into_inner();
×
726

×
727
    let db = app_ctx.session_context(session.clone()).db();
×
728
    let layer = match provider.into() {
×
729
        crate::layers::storage::INTERNAL_PROVIDER_ID => db.load_layer(&item.into()).await?,
×
730
        _ => {
731
            db.load_layer_provider(provider.into())
×
732
                .await?
×
733
                .load_layer(&item.into())
×
734
                .await?
×
735
        }
736
    };
737

738
    let db = app_ctx.session_context(session).db();
×
739
    let workflow_id = db.register_workflow(layer.workflow).await?;
×
740

741
    Ok(web::Json(IdResponse::from(workflow_id)))
×
742
}
×
743

744
/// Persist a raster layer from a provider as a dataset.
745
#[utoipa::path(
16✔
746
    tag = "Layers",
16✔
747
    post,
16✔
748
    path = "/layers/{provider}/{layer}/dataset",
16✔
749
    responses(
16✔
750
        (status = 200, description = "Id of created task", body = TaskResponse,
16✔
751
            example = json!({
16✔
752
                "taskId": "7f8a4cfe-76ab-4972-b347-b197e5ef0f3c"
16✔
753
            })
16✔
754
        )
16✔
755
    ),
16✔
756
    params(
16✔
757
        ("provider" = DataProviderId, description = "Data provider id"),
16✔
758
        ("layer" = LayerCollectionId, description = "Layer id"),
16✔
759
    ),
16✔
760
    security(
16✔
761
        ("session_token" = [])
16✔
762
    )
16✔
763
)]
16✔
764
async fn layer_to_dataset<C: ApplicationContext>(
5✔
765
    session: C::Session,
5✔
766
    app_ctx: web::Data<C>,
5✔
767
    path: web::Path<(DataProviderId, LayerId)>,
5✔
768
) -> Result<impl Responder> {
5✔
769
    let ctx = Arc::new(app_ctx.into_inner().session_context(session));
5✔
770

5✔
771
    let (provider, item) = path.into_inner();
5✔
772
    let item: geoengine_datatypes::dataset::LayerId = item.into();
5✔
773

5✔
774
    let db = ctx.db();
5✔
775

776
    let layer = match provider.into() {
5✔
777
        crate::layers::storage::INTERNAL_PROVIDER_ID => db.load_layer(&item).await?,
5✔
778
        _ => {
779
            db.load_layer_provider(provider.into())
×
780
                .await?
×
781
                .load_layer(&item)
×
782
                .await?
×
783
        }
784
    };
785

786
    let workflow_id = db.register_workflow(layer.workflow.clone()).await?;
5✔
787

788
    let execution_context = ctx.execution_context()?;
5✔
789

790
    let workflow_operator_path_root = WorkflowOperatorPath::initialize_root();
5✔
791

792
    let raster_operator = layer
5✔
793
        .workflow
5✔
794
        .operator
5✔
795
        .clone()
5✔
796
        .get_raster()?
5✔
797
        .initialize(workflow_operator_path_root, &execution_context)
5✔
798
        .await?;
5✔
799

800
    let result_descriptor = raster_operator.result_descriptor();
5✔
801

802
    let qr = QueryRectangle {
2✔
803
        spatial_bounds: result_descriptor.bbox.ok_or(
5✔
804
            Error::LayerResultDescriptorMissingFields {
5✔
805
                field: "bbox".to_string(),
5✔
806
                cause: "is None".to_string(),
5✔
807
            },
5✔
808
        )?,
5✔
809
        time_interval: result_descriptor
4✔
810
            .time
4✔
811
            .ok_or(Error::LayerResultDescriptorMissingFields {
4✔
812
                field: "time".to_string(),
4✔
813
                cause: "is None".to_string(),
4✔
814
            })?,
4✔
815
        spatial_resolution: result_descriptor.resolution.ok_or(
3✔
816
            Error::LayerResultDescriptorMissingFields {
3✔
817
                field: "spatial_resolution".to_string(),
3✔
818
                cause: "is None".to_string(),
3✔
819
            },
3✔
820
        )?,
3✔
821
        attributes: BandSelection::first(), // TODO: add to API
2✔
822
    };
2✔
823

2✔
824
    let from_workflow = RasterDatasetFromWorkflow {
2✔
825
        name: None,
2✔
826
        display_name: layer.name,
2✔
827
        description: Some(layer.description),
2✔
828
        query: qr.into(),
2✔
829
        as_cog: true,
2✔
830
    };
2✔
831

832
    let compression_num_threads =
2✔
833
        get_config_element::<crate::config::Gdal>()?.compression_num_threads;
2✔
834

835
    let task_id = schedule_raster_dataset_from_workflow_task(
2✔
836
        format!("layer {item}"),
2✔
837
        workflow_id,
2✔
838
        layer.workflow,
2✔
839
        ctx,
2✔
840
        from_workflow,
2✔
841
        compression_num_threads,
2✔
842
    )
2✔
843
    .await?;
2✔
844

845
    Ok(web::Json(TaskResponse::new(task_id)))
2✔
846
}
5✔
847

848
/// Add a new layer to a collection
849
#[utoipa::path(
16✔
850
    tag = "Layers",
16✔
851
    post,
16✔
852
    path = "/layerDb/collections/{collection}/layers",
16✔
853
    params(
16✔
854
        ("collection" = LayerCollectionId, description = "Layer collection id", example = "05102bb3-a855-4a37-8a8a-30026a91fef1"),
16✔
855
    ),
16✔
856
    request_body = AddLayer,
16✔
857
    responses(
16✔
858
        (status = 200, response = IdResponse::<LayerId>)
16✔
859
    ),
16✔
860
    security(
16✔
861
        ("session_token" = [])
16✔
862
    )
16✔
863
)]
16✔
864
async fn add_layer<C: ApplicationContext>(
2✔
865
    session: C::Session,
2✔
866
    app_ctx: web::Data<C>,
2✔
867
    collection: web::Path<LayerCollectionId>,
2✔
868
    request: web::Json<AddLayer>,
2✔
869
) -> Result<web::Json<IdResponse<LayerId>>> {
2✔
870
    let request = request.into_inner();
2✔
871
    let add_layer = request;
2✔
872

2✔
873
    let ctx = app_ctx.session_context(session);
2✔
874

2✔
875
    validate_workflow(&add_layer.workflow, &ctx.execution_context()?).await?;
2✔
876

877
    let id = ctx.db().add_layer(add_layer, &collection).await?.into();
1✔
878

1✔
879
    Ok(web::Json(IdResponse { id }))
1✔
880
}
2✔
881

882
/// Update a layer
883
#[utoipa::path(
16✔
884
    tag = "Layers",
16✔
885
    put,
16✔
886
    path = "/layerDb/layers/{layer}",
16✔
887
    params(
16✔
888
        ("layer" = LayerId, description = "Layer id", example = "05102bb3-a855-4a37-8a8a-30026a91fef1"),
16✔
889
    ),
16✔
890
    request_body = UpdateLayer,
16✔
891
    responses(
16✔
892
        (status = 200)
16✔
893
    ),
16✔
894
    security(
16✔
895
        ("session_token" = [])
16✔
896
    )
16✔
897
)]
16✔
898
async fn update_layer<C: ApplicationContext>(
2✔
899
    session: C::Session,
2✔
900
    app_ctx: web::Data<C>,
2✔
901
    layer: web::Path<LayerId>,
2✔
902
    request: ValidatedJson<UpdateLayer>,
2✔
903
) -> Result<HttpResponse> {
2✔
904
    let layer = layer.into_inner().into();
2✔
905
    let request = request.into_inner();
2✔
906

2✔
907
    let ctx = app_ctx.session_context(session);
2✔
908

2✔
909
    validate_workflow(&request.workflow, &ctx.execution_context()?).await?;
2✔
910

911
    ctx.db().update_layer(&layer, request).await?;
1✔
912

913
    Ok(HttpResponse::Ok().finish())
1✔
914
}
2✔
915

916
/// Remove a collection
917
#[utoipa::path(
16✔
918
    tag = "Layers",
16✔
919
    delete,
16✔
920
    path = "/layerDb/layers/{layer}",
16✔
921
    params(
16✔
922
        ("layer" = LayerId, description = "Layer id"),
16✔
923
    ),
16✔
924
    responses(
16✔
925
        (status = 200, description = "OK")
16✔
926
    ),
16✔
927
    security(
16✔
928
        ("session_token" = [])
16✔
929
    )
16✔
930
)]
16✔
931
async fn remove_layer<C: ApplicationContext>(
1✔
932
    session: C::Session,
1✔
933
    app_ctx: web::Data<C>,
1✔
934
    layer: web::Path<LayerId>,
1✔
935
) -> Result<HttpResponse> {
1✔
936
    let layer = layer.into_inner().into();
1✔
937

1✔
938
    app_ctx
1✔
939
        .session_context(session)
1✔
940
        .db()
1✔
941
        .remove_layer(&layer)
1✔
942
        .await?;
1✔
943

944
    Ok(HttpResponse::Ok().finish())
1✔
945
}
1✔
946

947
/// Add a new collection to an existing collection
948
#[utoipa::path(
16✔
949
    tag = "Layers",
16✔
950
    post,
16✔
951
    path = "/layerDb/collections/{collection}/collections",
16✔
952
    params(
16✔
953
        ("collection" = LayerCollectionId, description = "Layer collection id", example = "05102bb3-a855-4a37-8a8a-30026a91fef1"),
16✔
954
    ),
16✔
955
    request_body = AddLayerCollection,
16✔
956
    responses(
16✔
957
        (status = 200, response = IdResponse::<LayerCollectionId>)
16✔
958
    ),
16✔
959
    security(
16✔
960
        ("session_token" = [])
16✔
961
    )
16✔
962
)]
16✔
963
async fn add_collection<C: ApplicationContext>(
1✔
964
    session: C::Session,
1✔
965
    app_ctx: web::Data<C>,
1✔
966
    collection: web::Path<LayerCollectionId>,
1✔
967
    request: web::Json<AddLayerCollection>,
1✔
968
) -> Result<web::Json<IdResponse<LayerCollectionId>>> {
1✔
969
    let add_collection = request.into_inner();
1✔
970

971
    let id = app_ctx
1✔
972
        .into_inner()
1✔
973
        .session_context(session)
1✔
974
        .db()
1✔
975
        .add_layer_collection(add_collection, &collection)
1✔
976
        .await?;
1✔
977

978
    Ok(web::Json(IdResponse { id }))
1✔
979
}
1✔
980

981
/// Update a collection
982
#[utoipa::path(
16✔
983
    tag = "Layers",
16✔
984
    put,
16✔
985
    path = "/layerDb/collections/{collection}",
16✔
986
    params(
16✔
987
        ("collection" = LayerCollectionId, description = "Layer collection id", example = "05102bb3-a855-4a37-8a8a-30026a91fef1"),
16✔
988
    ),
16✔
989
    request_body = UpdateLayerCollection,
16✔
990
    responses(
16✔
991
        (status = 200)
16✔
992
    ),
16✔
993
    security(
16✔
994
        ("session_token" = [])
16✔
995
    )
16✔
996
)]
16✔
997
async fn update_collection<C: ApplicationContext>(
1✔
998
    session: C::Session,
1✔
999
    app_ctx: web::Data<C>,
1✔
1000
    collection: web::Path<LayerCollectionId>,
1✔
1001
    request: ValidatedJson<UpdateLayerCollection>,
1✔
1002
) -> Result<HttpResponse> {
1✔
1003
    let collection = collection.into_inner();
1✔
1004
    let update = request.into_inner();
1✔
1005

1✔
1006
    app_ctx
1✔
1007
        .into_inner()
1✔
1008
        .session_context(session)
1✔
1009
        .db()
1✔
1010
        .update_layer_collection(&collection, update)
1✔
1011
        .await?;
1✔
1012

1013
    Ok(HttpResponse::Ok().finish())
1✔
1014
}
1✔
1015

1016
/// Remove a collection
1017
#[utoipa::path(
16✔
1018
    tag = "Layers",
16✔
1019
    delete,
16✔
1020
    path = "/layerDb/collections/{collection}",
16✔
1021
    params(
16✔
1022
        ("collection" = LayerCollectionId, description = "Layer collection id"),
16✔
1023
    ),
16✔
1024
    responses(
16✔
1025
        (status = 200, description = "OK")
16✔
1026
    ),
16✔
1027
    security(
16✔
1028
        ("session_token" = [])
16✔
1029
    )
16✔
1030
)]
16✔
1031
async fn remove_collection<C: ApplicationContext>(
2✔
1032
    session: C::Session,
2✔
1033
    app_ctx: web::Data<C>,
2✔
1034
    collection: web::Path<LayerCollectionId>,
2✔
1035
) -> Result<HttpResponse> {
2✔
1036
    app_ctx
2✔
1037
        .session_context(session)
2✔
1038
        .db()
2✔
1039
        .remove_layer_collection(&collection)
2✔
1040
        .await?;
2✔
1041

1042
    Ok(HttpResponse::Ok().finish())
1✔
1043
}
2✔
1044

1045
// TODO: reflect in the API docs that these ids are usually UUIDs in the layer db
1046
#[derive(Debug, Serialize, Deserialize, IntoParams)]
×
1047
struct RemoveLayerFromCollectionParams {
1048
    collection: LayerCollectionId,
1049
    layer: LayerId,
1050
}
1051

1052
/// Remove a layer from a collection
1053
#[utoipa::path(
16✔
1054
    tag = "Layers",
16✔
1055
    delete,
16✔
1056
    path = "/layerDb/collections/{collection}/layers/{layer}",
16✔
1057
    params(
16✔
1058
        ("collection" = LayerCollectionId, description = "Layer collection id"),
16✔
1059
        ("layer" = LayerId, description = "Layer id"),
16✔
1060
    ),
16✔
1061
    responses(
16✔
1062
        (status = 200, description = "OK")
16✔
1063
    ),
16✔
1064
    security(
16✔
1065
        ("session_token" = [])
16✔
1066
    )
16✔
1067
)]
16✔
1068
async fn remove_layer_from_collection<C: ApplicationContext>(
1✔
1069
    session: C::Session,
1✔
1070
    app_ctx: web::Data<C>,
1✔
1071
    path: web::Path<RemoveLayerFromCollectionParams>,
1✔
1072
) -> Result<HttpResponse> {
1✔
1073
    app_ctx
1✔
1074
        .session_context(session)
1✔
1075
        .db()
1✔
1076
        .remove_layer_from_collection(&path.layer.clone().into(), &path.collection)
1✔
1077
        .await?;
1✔
1078

1079
    Ok(HttpResponse::Ok().finish())
1✔
1080
}
1✔
1081

1082
#[derive(Debug, Serialize, Deserialize, IntoParams)]
×
1083
struct AddExistingLayerToCollectionParams {
1084
    collection: LayerCollectionId,
1085
    layer: LayerId,
1086
}
1087

1088
/// Add an existing layer to a collection
1089
#[utoipa::path(
16✔
1090
    tag = "Layers",
16✔
1091
    post,
16✔
1092
    path = "/layerDb/collections/{collection}/layers/{layer}",
16✔
1093
    params(
16✔
1094
        ("collection" = LayerCollectionId, description = "Layer collection id"),
16✔
1095
        ("layer" = LayerId, description = "Layer id"),
16✔
1096
    ),
16✔
1097
    responses(
16✔
1098
        (status = 200, description = "OK")
16✔
1099
    ),
16✔
1100
    security(
16✔
1101
        ("session_token" = [])
16✔
1102
    )
16✔
1103
)]
16✔
1104
async fn add_existing_layer_to_collection<C: ApplicationContext>(
1✔
1105
    session: C::Session,
1✔
1106
    app_ctx: web::Data<C>,
1✔
1107
    path: web::Path<AddExistingLayerToCollectionParams>,
1✔
1108
) -> Result<HttpResponse> {
1✔
1109
    app_ctx
1✔
1110
        .session_context(session)
1✔
1111
        .db()
1✔
1112
        .add_layer_to_collection(&path.layer.clone().into(), &path.collection)
1✔
1113
        .await?;
1✔
1114

1115
    Ok(HttpResponse::Ok().finish())
1✔
1116
}
1✔
1117

1118
#[derive(Debug, Serialize, Deserialize, IntoParams)]
×
1119
struct CollectionAndSubCollectionParams {
1120
    collection: LayerCollectionId,
1121
    sub_collection: LayerCollectionId,
1122
}
1123

1124
/// Add an existing collection to a collection
1125
#[utoipa::path(
16✔
1126
    tag = "Layers",
16✔
1127
    post,
16✔
1128
    path = "/layerDb/collections/{parent}/collections/{collection}",
16✔
1129
    params(
16✔
1130
        ("parent" = LayerCollectionId, description = "Parent layer collection id", example = "05102bb3-a855-4a37-8a8a-30026a91fef1"),
16✔
1131
        ("collection" = LayerId, description = "Layer collection id"),
16✔
1132
    ),
16✔
1133
    responses(
16✔
1134
        (status = 200, description = "OK")
16✔
1135
    ),
16✔
1136
    security(
16✔
1137
        ("session_token" = [])
16✔
1138
    )
16✔
1139
)]
16✔
1140
async fn add_existing_collection_to_collection<C: ApplicationContext>(
1✔
1141
    session: C::Session,
1✔
1142
    app_ctx: web::Data<C>,
1✔
1143
    path: web::Path<CollectionAndSubCollectionParams>,
1✔
1144
) -> Result<HttpResponse> {
1✔
1145
    app_ctx
1✔
1146
        .session_context(session)
1✔
1147
        .db()
1✔
1148
        .add_collection_to_parent(&path.sub_collection, &path.collection)
1✔
1149
        .await?;
1✔
1150

1151
    Ok(HttpResponse::Ok().finish())
1✔
1152
}
1✔
1153

1154
/// Delete a collection from a collection
1155
#[utoipa::path(
16✔
1156
    tag = "Layers",
16✔
1157
    delete,
16✔
1158
    path = "/layerDb/collections/{parent}/collections/{collection}",
16✔
1159
    params(
16✔
1160
        ("parent" = LayerCollectionId, description = "Parent layer collection id", example = "05102bb3-a855-4a37-8a8a-30026a91fef1"),
16✔
1161
        ("collection" = LayerId, description = "Layer collection id"),
16✔
1162
    ),
16✔
1163
    responses(
16✔
1164
        (status = 200, description = "OK")
16✔
1165
    ),
16✔
1166
    security(
16✔
1167
        ("session_token" = [])
16✔
1168
    )
16✔
1169
)]
16✔
1170
async fn remove_collection_from_collection<C: ApplicationContext>(
1✔
1171
    session: C::Session,
1✔
1172
    app_ctx: web::Data<C>,
1✔
1173
    path: web::Path<CollectionAndSubCollectionParams>,
1✔
1174
) -> Result<HttpResponse> {
1✔
1175
    app_ctx
1✔
1176
        .session_context(session)
1✔
1177
        .db()
1✔
1178
        .remove_layer_collection_from_parent(&path.sub_collection, &path.collection)
1✔
1179
        .await?;
1✔
1180

1181
    Ok(HttpResponse::Ok().finish())
1✔
1182
}
1✔
1183

1184
#[cfg(test)]
1185
mod tests {
1186

1187
    use super::*;
1188
    use crate::api::model::responses::ErrorResponse;
1189
    use crate::config::get_config_element;
1190
    use crate::contexts::PostgresContext;
1191
    use crate::contexts::SessionId;
1192
    use crate::datasets::RasterDatasetFromWorkflowResult;
1193
    use crate::ge_context;
1194
    use crate::layers::layer::Layer;
1195
    use crate::layers::storage::INTERNAL_PROVIDER_ID;
1196
    use crate::tasks::util::test::wait_for_task_to_finish;
1197
    use crate::tasks::{TaskManager, TaskStatus};
1198
    use crate::users::{UserAuth, UserSession};
1199
    use crate::util::tests::admin_login;
1200
    use crate::util::tests::{
1201
        read_body_string, send_test_request, MockQueryContext, TestDataUploads,
1202
    };
1203
    use crate::{contexts::Session, workflows::workflow::Workflow};
1204
    use actix_web::dev::ServiceResponse;
1205
    use actix_web::{http::header, test};
1206
    use actix_web_httpauth::headers::authorization::Bearer;
1207
    use geoengine_datatypes::primitives::{CacheHint, Coordinate2D};
1208
    use geoengine_datatypes::primitives::{
1209
        RasterQueryRectangle, SpatialPartition2D, TimeGranularity, TimeInterval,
1210
    };
1211
    use geoengine_datatypes::raster::{
1212
        GeoTransform, Grid, GridShape, RasterDataType, RasterTile2D, TilingSpecification,
1213
    };
1214
    use geoengine_datatypes::spatial_reference::SpatialReference;
1215
    use geoengine_datatypes::util::test::TestDefault;
1216
    use geoengine_operators::engine::{
1217
        ExecutionContext, InitializedRasterOperator, RasterBandDescriptors, RasterOperator,
1218
        RasterResultDescriptor, SingleRasterOrVectorSource, TypedOperator,
1219
    };
1220
    use geoengine_operators::mock::{MockRasterSource, MockRasterSourceParams};
1221
    use geoengine_operators::processing::{TimeShift, TimeShiftParams};
1222
    use geoengine_operators::source::{GdalSource, GdalSourceParameters};
1223
    use geoengine_operators::util::raster_stream_to_geotiff::{
1224
        raster_stream_to_geotiff_bytes, GdalGeoTiffDatasetMetadata, GdalGeoTiffOptions,
1225
    };
1226
    use geoengine_operators::{
1227
        engine::VectorOperator,
1228
        mock::{MockPointSource, MockPointSourceParams},
1229
    };
1230
    use std::sync::Arc;
1231
    use tokio_postgres::NoTls;
1232

1233
    #[ge_context::test]
1✔
1234
    async fn test_add_layer_to_collection(app_ctx: PostgresContext<NoTls>) {
1✔
1235
        let session = admin_login(&app_ctx).await;
1✔
1236
        let ctx = app_ctx.session_context(session.clone());
1✔
1237

1✔
1238
        let session_id = session.id();
1✔
1239

1240
        let collection_id = ctx.db().get_root_layer_collection_id().await.unwrap();
1✔
1241

1✔
1242
        let req = test::TestRequest::post()
1✔
1243
            .uri(&format!("/layerDb/collections/{collection_id}/layers"))
1✔
1244
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())))
1✔
1245
            .set_json(serde_json::json!({
1✔
1246
                "name": "Foo",
1✔
1247
                "description": "Bar",
1✔
1248
                "workflow": {
1✔
1249
                  "type": "Vector",
1✔
1250
                  "operator": {
1✔
1251
                    "type": "MockPointSource",
1✔
1252
                    "params": {
1✔
1253
                      "points": [
1✔
1254
                        { "x": 0.0, "y": 0.1 },
1✔
1255
                        { "x": 1.0, "y": 1.1 }
1✔
1256
                      ]
1✔
1257
                    }
1✔
1258
                  }
1✔
1259
                },
1✔
1260
                "symbology": null,
1✔
1261
            }));
1✔
1262
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1263

1264
        assert!(response.status().is_success(), "{response:?}");
1✔
1265

1266
        let result: IdResponse<LayerId> = test::read_body_json(response).await;
1✔
1267

1268
        ctx.db()
1✔
1269
            .load_layer(&result.id.clone().into())
1✔
1270
            .await
1✔
1271
            .unwrap();
1✔
1272

1273
        let collection = ctx
1✔
1274
            .db()
1✔
1275
            .load_layer_collection(&collection_id, LayerCollectionListOptions::default())
1✔
1276
            .await
1✔
1277
            .unwrap();
1✔
1278

1✔
1279
        assert!(collection.items.iter().any(|item| match item {
2✔
1280
            CollectionItem::Layer(layer) => layer.id.layer_id == result.id.clone().into(),
1✔
1281
            CollectionItem::Collection(_) => false,
1✔
1282
        }));
2✔
1283
    }
1✔
1284

1285
    #[ge_context::test]
1✔
1286
    async fn test_add_existing_layer_to_collection(app_ctx: PostgresContext<NoTls>) {
1✔
1287
        let session = admin_login(&app_ctx).await;
1✔
1288
        let ctx = app_ctx.session_context(session.clone());
1✔
1289

1✔
1290
        let session_id = session.id();
1✔
1291

1292
        let root_collection_id = ctx.db().get_root_layer_collection_id().await.unwrap();
1✔
1293

1294
        let layer_id = ctx
1✔
1295
            .db()
1✔
1296
            .add_layer(
1✔
1297
                AddLayer {
1✔
1298
                    name: "Layer Name".to_string(),
1✔
1299
                    description: "Layer Description".to_string(),
1✔
1300
                    workflow: Workflow {
1✔
1301
                        operator: MockPointSource {
1✔
1302
                            params: MockPointSourceParams {
1✔
1303
                                points: vec![(0.0, 0.1).into(), (1.0, 1.1).into()],
1✔
1304
                            },
1✔
1305
                        }
1✔
1306
                        .boxed()
1✔
1307
                        .into(),
1✔
1308
                    },
1✔
1309
                    symbology: None,
1✔
1310
                    metadata: Default::default(),
1✔
1311
                    properties: Default::default(),
1✔
1312
                },
1✔
1313
                &root_collection_id,
1✔
1314
            )
1✔
1315
            .await
1✔
1316
            .unwrap();
1✔
1317

1318
        let collection_id = ctx
1✔
1319
            .db()
1✔
1320
            .add_layer_collection(
1✔
1321
                AddLayerCollection {
1✔
1322
                    name: "Foo".to_string(),
1✔
1323
                    description: "Bar".to_string(),
1✔
1324
                    properties: Default::default(),
1✔
1325
                },
1✔
1326
                &root_collection_id,
1✔
1327
            )
1✔
1328
            .await
1✔
1329
            .unwrap();
1✔
1330

1✔
1331
        let req = test::TestRequest::post()
1✔
1332
            .uri(&format!(
1✔
1333
                "/layerDb/collections/{collection_id}/layers/{layer_id}"
1✔
1334
            ))
1✔
1335
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())));
1✔
1336
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1337

1338
        assert!(response.status().is_success(), "{response:?}");
1✔
1339

1340
        let collection = ctx
1✔
1341
            .db()
1✔
1342
            .load_layer_collection(&collection_id, LayerCollectionListOptions::default())
1✔
1343
            .await
1✔
1344
            .unwrap();
1✔
1345
        assert_eq!(collection.items.len(), 1);
1✔
1346
    }
1✔
1347

1348
    #[ge_context::test]
1✔
1349
    async fn test_add_layer_collection(app_ctx: PostgresContext<NoTls>) {
1✔
1350
        let session = admin_login(&app_ctx).await;
1✔
1351
        let ctx = app_ctx.session_context(session.clone());
1✔
1352

1✔
1353
        let session_id = session.id();
1✔
1354

1355
        let collection_id = ctx.db().get_root_layer_collection_id().await.unwrap();
1✔
1356

1✔
1357
        let req = test::TestRequest::post()
1✔
1358
            .uri(&format!("/layerDb/collections/{collection_id}/collections"))
1✔
1359
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())))
1✔
1360
            .set_json(serde_json::json!({
1✔
1361
                "name": "Foo",
1✔
1362
                "description": "Bar",
1✔
1363
            }));
1✔
1364
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1365

1366
        assert!(response.status().is_success(), "{response:?}");
1✔
1367

1368
        let result: IdResponse<LayerCollectionId> = test::read_body_json(response).await;
1✔
1369

1370
        ctx.db()
1✔
1371
            .load_layer_collection(&result.id, LayerCollectionListOptions::default())
1✔
1372
            .await
1✔
1373
            .unwrap();
1✔
1374
    }
1✔
1375

1376
    #[ge_context::test]
1✔
1377
    async fn test_update_layer_collection(app_ctx: PostgresContext<NoTls>) {
1✔
1378
        let session = admin_login(&app_ctx).await;
1✔
1379
        let ctx = app_ctx.session_context(session.clone());
1✔
1380

1✔
1381
        let session_id = session.id();
1✔
1382

1383
        let collection_id = ctx
1✔
1384
            .db()
1✔
1385
            .add_layer_collection(
1✔
1386
                AddLayerCollection {
1✔
1387
                    name: "Foo".to_string(),
1✔
1388
                    description: "Bar".to_string(),
1✔
1389
                    properties: Default::default(),
1✔
1390
                },
1✔
1391
                &ctx.db().get_root_layer_collection_id().await.unwrap(),
1✔
1392
            )
1✔
1393
            .await
1✔
1394
            .unwrap();
1✔
1395

1✔
1396
        let req = test::TestRequest::put()
1✔
1397
            .uri(&format!("/layerDb/collections/{collection_id}"))
1✔
1398
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())))
1✔
1399
            .set_json(serde_json::json!({
1✔
1400
                "name": "Foo new",
1✔
1401
                "description": "Bar new",
1✔
1402
            }));
1✔
1403
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1404

1405
        assert!(response.status().is_success(), "{response:?}");
1✔
1406

1407
        let result = ctx
1✔
1408
            .db()
1✔
1409
            .load_layer_collection(&collection_id, LayerCollectionListOptions::default())
1✔
1410
            .await
1✔
1411
            .unwrap();
1✔
1412

1✔
1413
        assert_eq!(result.name, "Foo new");
1✔
1414
        assert_eq!(result.description, "Bar new");
1✔
1415
    }
1✔
1416

1417
    #[ge_context::test]
1✔
1418
    async fn test_update_layer(app_ctx: PostgresContext<NoTls>) {
1✔
1419
        let session = admin_login(&app_ctx).await;
1✔
1420
        let ctx = app_ctx.session_context(session.clone());
1✔
1421

1✔
1422
        let session_id = session.id();
1✔
1423

1✔
1424
        let add_layer = AddLayer {
1✔
1425
            name: "Foo".to_string(),
1✔
1426
            description: "Bar".to_string(),
1✔
1427
            properties: Default::default(),
1✔
1428
            workflow: Workflow {
1✔
1429
                operator: TypedOperator::Vector(
1✔
1430
                    MockPointSource {
1✔
1431
                        params: MockPointSourceParams {
1✔
1432
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1433
                        },
1✔
1434
                    }
1✔
1435
                    .boxed(),
1✔
1436
                ),
1✔
1437
            },
1✔
1438
            symbology: None,
1✔
1439
            metadata: Default::default(),
1✔
1440
        };
1✔
1441

1442
        let layer_id = ctx
1✔
1443
            .db()
1✔
1444
            .add_layer(
1✔
1445
                add_layer.clone(),
1✔
1446
                &ctx.db().get_root_layer_collection_id().await.unwrap(),
1✔
1447
            )
1✔
1448
            .await
1✔
1449
            .unwrap();
1✔
1450

1✔
1451
        let update_layer = UpdateLayer {
1✔
1452
            name: "Foo new".to_string(),
1✔
1453
            description: "Bar new".to_string(),
1✔
1454
            workflow: Workflow {
1✔
1455
                operator: TypedOperator::Vector(
1✔
1456
                    MockPointSource {
1✔
1457
                        params: MockPointSourceParams {
1✔
1458
                            points: vec![Coordinate2D::new(4., 5.); 3],
1✔
1459
                        },
1✔
1460
                    }
1✔
1461
                    .boxed(),
1✔
1462
                ),
1✔
1463
            },
1✔
1464
            symbology: None,
1✔
1465
            metadata: Default::default(),
1✔
1466
            properties: Default::default(),
1✔
1467
        };
1✔
1468

1✔
1469
        let req = test::TestRequest::put()
1✔
1470
            .uri(&format!("/layerDb/layers/{layer_id}"))
1✔
1471
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())))
1✔
1472
            .set_json(serde_json::json!(update_layer.clone()));
1✔
1473
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1474

1475
        assert!(response.status().is_success(), "{response:?}");
1✔
1476

1477
        let result = ctx.db().load_layer(&layer_id).await.unwrap();
1✔
1478

1✔
1479
        assert_eq!(result.name, update_layer.name);
1✔
1480
        assert_eq!(result.description, update_layer.description);
1✔
1481
        assert_eq!(result.workflow, update_layer.workflow);
1✔
1482
        assert_eq!(result.symbology, update_layer.symbology);
1✔
1483
        assert_eq!(result.metadata, update_layer.metadata);
1✔
1484
        assert_eq!(result.properties, update_layer.properties);
1✔
1485
    }
1✔
1486

1487
    #[ge_context::test]
1✔
1488
    async fn it_checks_for_workflow_validity(app_ctx: PostgresContext<NoTls>) {
1✔
1489
        let session = admin_login(&app_ctx).await;
1✔
1490
        let ctx = app_ctx.session_context(session.clone());
1✔
1491

1✔
1492
        let session_id = session.id();
1✔
1493

1494
        let collection_id = ctx.db().get_root_layer_collection_id().await.unwrap();
1✔
1495

1✔
1496
        let invalid_workflow_layer = serde_json::json!({
1✔
1497
            "name": "Foo",
1✔
1498
            "description": "Bar",
1✔
1499
            "workflow":{
1✔
1500
                "type": "Raster",
1✔
1501
                "operator": {
1✔
1502
                    "type": "GdalSource",
1✔
1503
                    "params": {
1✔
1504
                    "data": "example"
1✔
1505
                    }
1✔
1506
                }
1✔
1507
            }
1✔
1508
        });
1✔
1509
        let req = test::TestRequest::post()
1✔
1510
            .uri(&format!("/layerDb/collections/{collection_id}/layers"))
1✔
1511
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())))
1✔
1512
            .set_json(invalid_workflow_layer.clone());
1✔
1513

1514
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1515

1516
        ErrorResponse::assert(
1✔
1517
            response,
1✔
1518
            400,
1✔
1519
            "UnknownDatasetName",
1✔
1520
            "Dataset name 'example' does not exist",
1✔
1521
        )
1✔
1522
        .await;
1✔
1523

1524
        let add_layer = AddLayer {
1✔
1525
            name: "Foo".to_string(),
1✔
1526
            description: "Bar".to_string(),
1✔
1527
            properties: Default::default(),
1✔
1528
            workflow: Workflow {
1✔
1529
                operator: TypedOperator::Vector(
1✔
1530
                    MockPointSource {
1✔
1531
                        params: MockPointSourceParams {
1✔
1532
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1533
                        },
1✔
1534
                    }
1✔
1535
                    .boxed(),
1✔
1536
                ),
1✔
1537
            },
1✔
1538
            symbology: None,
1✔
1539
            metadata: Default::default(),
1✔
1540
        };
1✔
1541

1542
        let layer_id = ctx
1✔
1543
            .db()
1✔
1544
            .add_layer(
1✔
1545
                add_layer.clone(),
1✔
1546
                &ctx.db().get_root_layer_collection_id().await.unwrap(),
1✔
1547
            )
1✔
1548
            .await
1✔
1549
            .unwrap();
1✔
1550

1✔
1551
        let req = test::TestRequest::put()
1✔
1552
            .uri(&format!("/layerDb/layers/{layer_id}"))
1✔
1553
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())))
1✔
1554
            .set_json(invalid_workflow_layer);
1✔
1555
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1556

1557
        ErrorResponse::assert(
1✔
1558
            response,
1✔
1559
            400,
1✔
1560
            "UnknownDatasetName",
1✔
1561
            "Dataset name 'example' does not exist",
1✔
1562
        )
1✔
1563
        .await;
1✔
1564
    }
1✔
1565

1566
    #[ge_context::test]
1✔
1567
    async fn test_remove_layer(app_ctx: PostgresContext<NoTls>) {
1✔
1568
        let session = admin_login(&app_ctx).await;
1✔
1569
        let ctx = app_ctx.session_context(session.clone());
1✔
1570

1✔
1571
        let session_id = session.id();
1✔
1572

1✔
1573
        let add_layer = AddLayer {
1✔
1574
            name: "Foo".to_string(),
1✔
1575
            description: "Bar".to_string(),
1✔
1576
            properties: Default::default(),
1✔
1577
            workflow: Workflow {
1✔
1578
                operator: TypedOperator::Vector(
1✔
1579
                    MockPointSource {
1✔
1580
                        params: MockPointSourceParams {
1✔
1581
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1582
                        },
1✔
1583
                    }
1✔
1584
                    .boxed(),
1✔
1585
                ),
1✔
1586
            },
1✔
1587
            symbology: None,
1✔
1588
            metadata: Default::default(),
1✔
1589
        };
1✔
1590

1591
        let layer_id = ctx
1✔
1592
            .db()
1✔
1593
            .add_layer(
1✔
1594
                add_layer.clone(),
1✔
1595
                &ctx.db().get_root_layer_collection_id().await.unwrap(),
1✔
1596
            )
1✔
1597
            .await
1✔
1598
            .unwrap();
1✔
1599

1✔
1600
        let req = test::TestRequest::delete()
1✔
1601
            .uri(&format!("/layerDb/layers/{layer_id}"))
1✔
1602
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())));
1✔
1603

1604
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1605

1606
        assert!(response.status().is_success(), "{response:?}");
1✔
1607

1608
        let result = ctx.db().load_layer(&layer_id).await;
1✔
1609

1610
        assert!(result.is_err());
1✔
1611
    }
1✔
1612

1613
    #[ge_context::test]
1✔
1614
    async fn test_add_existing_collection_to_collection(app_ctx: PostgresContext<NoTls>) {
1✔
1615
        let session = admin_login(&app_ctx).await;
1✔
1616
        let ctx = app_ctx.session_context(session.clone());
1✔
1617

1✔
1618
        let session_id = session.id();
1✔
1619

1620
        let root_collection_id = ctx.db().get_root_layer_collection_id().await.unwrap();
1✔
1621

1622
        let collection_a_id = ctx
1✔
1623
            .db()
1✔
1624
            .add_layer_collection(
1✔
1625
                AddLayerCollection {
1✔
1626
                    name: "Foo".to_string(),
1✔
1627
                    description: "Foo".to_string(),
1✔
1628
                    properties: Default::default(),
1✔
1629
                },
1✔
1630
                &root_collection_id,
1✔
1631
            )
1✔
1632
            .await
1✔
1633
            .unwrap();
1✔
1634

1635
        let collection_b_id = ctx
1✔
1636
            .db()
1✔
1637
            .add_layer_collection(
1✔
1638
                AddLayerCollection {
1✔
1639
                    name: "Bar".to_string(),
1✔
1640
                    description: "Bar".to_string(),
1✔
1641
                    properties: Default::default(),
1✔
1642
                },
1✔
1643
                &root_collection_id,
1✔
1644
            )
1✔
1645
            .await
1✔
1646
            .unwrap();
1✔
1647

1✔
1648
        let req = test::TestRequest::post()
1✔
1649
            .uri(&format!(
1✔
1650
                "/layerDb/collections/{collection_a_id}/collections/{collection_b_id}"
1✔
1651
            ))
1✔
1652
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())));
1✔
1653
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1654

1655
        assert!(response.status().is_success(), "{response:?}");
1✔
1656

1657
        let collection_a = ctx
1✔
1658
            .db()
1✔
1659
            .load_layer_collection(&collection_a_id, LayerCollectionListOptions::default())
1✔
1660
            .await
1✔
1661
            .unwrap();
1✔
1662

1✔
1663
        assert_eq!(collection_a.items.len(), 1);
1✔
1664
    }
1✔
1665

1666
    #[ge_context::test]
1✔
1667
    async fn test_remove_layer_from_collection(app_ctx: PostgresContext<NoTls>) {
1✔
1668
        let session = admin_login(&app_ctx).await;
1✔
1669
        let ctx = app_ctx.session_context(session.clone());
1✔
1670

1✔
1671
        let session_id = session.id();
1✔
1672

1673
        let root_collection_id = ctx.db().get_root_layer_collection_id().await.unwrap();
1✔
1674

1675
        let collection_id = ctx
1✔
1676
            .db()
1✔
1677
            .add_layer_collection(
1✔
1678
                AddLayerCollection {
1✔
1679
                    name: "Foo".to_string(),
1✔
1680
                    description: "Bar".to_string(),
1✔
1681
                    properties: Default::default(),
1✔
1682
                },
1✔
1683
                &root_collection_id,
1✔
1684
            )
1✔
1685
            .await
1✔
1686
            .unwrap();
1✔
1687

1688
        let layer_id = ctx
1✔
1689
            .db()
1✔
1690
            .add_layer(
1✔
1691
                AddLayer {
1✔
1692
                    name: "Layer Name".to_string(),
1✔
1693
                    description: "Layer Description".to_string(),
1✔
1694
                    workflow: Workflow {
1✔
1695
                        operator: MockPointSource {
1✔
1696
                            params: MockPointSourceParams {
1✔
1697
                                points: vec![(0.0, 0.1).into(), (1.0, 1.1).into()],
1✔
1698
                            },
1✔
1699
                        }
1✔
1700
                        .boxed()
1✔
1701
                        .into(),
1✔
1702
                    },
1✔
1703
                    symbology: None,
1✔
1704
                    metadata: Default::default(),
1✔
1705
                    properties: Default::default(),
1✔
1706
                },
1✔
1707
                &collection_id,
1✔
1708
            )
1✔
1709
            .await
1✔
1710
            .unwrap();
1✔
1711

1✔
1712
        let req = test::TestRequest::delete()
1✔
1713
            .uri(&format!(
1✔
1714
                "/layerDb/collections/{collection_id}/layers/{layer_id}"
1✔
1715
            ))
1✔
1716
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())));
1✔
1717
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1718

1719
        assert!(
1✔
1720
            response.status().is_success(),
1✔
1721
            "{:?}: {:?}",
×
1722
            response.response().head(),
×
1723
            response.response().body()
×
1724
        );
1725

1726
        // layer should be gone
1727
        ctx.db().load_layer(&layer_id).await.unwrap_err();
1✔
1728
    }
1✔
1729

1730
    #[ge_context::test]
1✔
1731
    async fn test_remove_collection(app_ctx: PostgresContext<NoTls>) {
1✔
1732
        let session = admin_login(&app_ctx).await;
1✔
1733
        let ctx = app_ctx.session_context(session.clone());
1✔
1734

1✔
1735
        let session_id = session.id();
1✔
1736

1737
        let root_collection_id = ctx.db().get_root_layer_collection_id().await.unwrap();
1✔
1738

1739
        let collection_id = ctx
1✔
1740
            .db()
1✔
1741
            .add_layer_collection(
1✔
1742
                AddLayerCollection {
1✔
1743
                    name: "Foo".to_string(),
1✔
1744
                    description: "Bar".to_string(),
1✔
1745
                    properties: Default::default(),
1✔
1746
                },
1✔
1747
                &root_collection_id,
1✔
1748
            )
1✔
1749
            .await
1✔
1750
            .unwrap();
1✔
1751

1✔
1752
        let req = test::TestRequest::delete()
1✔
1753
            .uri(&format!("/layerDb/collections/{collection_id}"))
1✔
1754
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())));
1✔
1755
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1756

1757
        assert!(response.status().is_success(), "{response:?}");
1✔
1758

1759
        ctx.db()
1✔
1760
            .load_layer_collection(&collection_id, LayerCollectionListOptions::default())
1✔
1761
            .await
1✔
1762
            .unwrap_err();
1✔
1763

1✔
1764
        // try removing root collection id --> should fail
1✔
1765

1✔
1766
        let req = test::TestRequest::delete()
1✔
1767
            .uri(&format!("/layerDb/collections/{root_collection_id}"))
1✔
1768
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())));
1✔
1769
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1770

1771
        assert!(response.status().is_client_error(), "{response:?}");
1✔
1772
    }
1✔
1773

1774
    #[ge_context::test]
1✔
1775
    async fn test_remove_collection_from_collection(app_ctx: PostgresContext<NoTls>) {
1✔
1776
        let session = admin_login(&app_ctx).await;
1✔
1777
        let ctx = app_ctx.session_context(session.clone());
1✔
1778

1✔
1779
        let session_id = session.id();
1✔
1780

1781
        let root_collection_id = ctx.db().get_root_layer_collection_id().await.unwrap();
1✔
1782

1783
        let collection_id = ctx
1✔
1784
            .db()
1✔
1785
            .add_layer_collection(
1✔
1786
                AddLayerCollection {
1✔
1787
                    name: "Foo".to_string(),
1✔
1788
                    description: "Bar".to_string(),
1✔
1789
                    properties: Default::default(),
1✔
1790
                },
1✔
1791
                &root_collection_id,
1✔
1792
            )
1✔
1793
            .await
1✔
1794
            .unwrap();
1✔
1795

1✔
1796
        let req = test::TestRequest::delete()
1✔
1797
            .uri(&format!(
1✔
1798
                "/layerDb/collections/{root_collection_id}/collections/{collection_id}"
1✔
1799
            ))
1✔
1800
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())));
1✔
1801
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1802

1803
        assert!(response.status().is_success(), "{response:?}");
1✔
1804

1805
        let root_collection = ctx
1✔
1806
            .db()
1✔
1807
            .load_layer_collection(&root_collection_id, LayerCollectionListOptions::default())
1✔
1808
            .await
1✔
1809
            .unwrap();
1✔
1810

1✔
1811
        assert!(
1✔
1812
            !root_collection
1✔
1813
                .items
1✔
1814
                .iter()
1✔
1815
                .any(|item| item.name() == "Foo"),
1✔
1816
            "{root_collection:#?}"
×
1817
        );
1818
    }
1✔
1819

1820
    #[ge_context::test]
1✔
1821
    async fn test_search_capabilities(app_ctx: PostgresContext<NoTls>) {
1✔
1822
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
1823

1✔
1824
        let session_id = session.id();
1✔
1825

1✔
1826
        let req = test::TestRequest::get()
1✔
1827
            .uri(&format!("/layers/{INTERNAL_PROVIDER_ID}/capabilities",))
1✔
1828
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())));
1✔
1829
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1830

1831
        assert!(response.status().is_success(), "{response:?}");
1✔
1832
    }
1✔
1833

1834
    #[ge_context::test]
1✔
1835
    async fn test_search(app_ctx: PostgresContext<NoTls>) {
1✔
1836
        let session = admin_login(&app_ctx).await;
1✔
1837
        let ctx = app_ctx.session_context(session.clone());
1✔
1838

1✔
1839
        let session_id = session.id();
1✔
1840

1841
        let root_collection_id = ctx.db().get_root_layer_collection_id().await.unwrap();
1✔
1842

1✔
1843
        let req = test::TestRequest::get()
1✔
1844
            .uri(&format!(
1✔
1845
                "/layers/collections/search/{INTERNAL_PROVIDER_ID}/{root_collection_id}?limit=5&offset=0&searchType=fulltext&searchString=x"
1✔
1846
            ))
1✔
1847
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())));
1✔
1848
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1849

1850
        assert!(response.status().is_success(), "{response:?}");
1✔
1851
    }
1✔
1852

1853
    #[ge_context::test]
1✔
1854
    async fn test_search_autocomplete(app_ctx: PostgresContext<NoTls>) {
1✔
1855
        let session = admin_login(&app_ctx).await;
1✔
1856
        let ctx = app_ctx.session_context(session.clone());
1✔
1857

1✔
1858
        let session_id = session.id();
1✔
1859

1860
        let root_collection_id = ctx.db().get_root_layer_collection_id().await.unwrap();
1✔
1861

1✔
1862
        let req = test::TestRequest::get()
1✔
1863
            .uri(&format!(
1✔
1864
                "/layers/collections/search/autocomplete/{INTERNAL_PROVIDER_ID}/{root_collection_id}?limit=5&offset=0&searchType=fulltext&searchString=x"
1✔
1865
            ))
1✔
1866
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())));
1✔
1867
        let response = send_test_request(req, app_ctx.clone()).await;
1✔
1868

1869
        assert!(response.status().is_success(), "{response:?}");
1✔
1870
    }
1✔
1871

1872
    struct MockRasterWorkflowLayerDescription {
1873
        workflow: Workflow,
1874
        tiling_specification: TilingSpecification,
1875
        query_rectangle: RasterQueryRectangle,
1876
        collection_name: String,
1877
        collection_description: String,
1878
        layer_name: String,
1879
        layer_description: String,
1880
    }
1881

1882
    impl MockRasterWorkflowLayerDescription {
1883
        fn new(
10✔
1884
            has_time: bool,
10✔
1885
            has_bbox: bool,
10✔
1886
            has_resolution: bool,
10✔
1887
            time_shift_millis: i32,
10✔
1888
        ) -> Self {
10✔
1889
            let data: Vec<RasterTile2D<u8>> = vec![
10✔
1890
                RasterTile2D {
10✔
1891
                    time: TimeInterval::new_unchecked(1_671_868_800_000, 1_671_955_200_000),
10✔
1892
                    tile_position: [-1, 0].into(),
10✔
1893
                    band: 0,
10✔
1894
                    global_geo_transform: TestDefault::test_default(),
10✔
1895
                    grid_array: Grid::new([2, 2].into(), vec![1, 2, 3, 4]).unwrap().into(),
10✔
1896
                    properties: Default::default(),
10✔
1897
                    cache_hint: CacheHint::default(),
10✔
1898
                },
10✔
1899
                RasterTile2D {
10✔
1900
                    time: TimeInterval::new_unchecked(1_671_955_200_000, 1_672_041_600_000),
10✔
1901
                    tile_position: [-1, 0].into(),
10✔
1902
                    band: 0,
10✔
1903
                    global_geo_transform: TestDefault::test_default(),
10✔
1904
                    grid_array: Grid::new([2, 2].into(), vec![7, 8, 9, 10]).unwrap().into(),
10✔
1905
                    properties: Default::default(),
10✔
1906
                    cache_hint: CacheHint::default(),
10✔
1907
                },
10✔
1908
            ];
10✔
1909

1910
            let raster_source = MockRasterSource {
10✔
1911
                params: MockRasterSourceParams {
1912
                    data,
10✔
1913
                    result_descriptor: RasterResultDescriptor {
10✔
1914
                        data_type: RasterDataType::U8,
10✔
1915
                        spatial_reference: SpatialReference::epsg_4326().into(),
10✔
1916
                        time: if has_time {
10✔
1917
                            Some(TimeInterval::new_unchecked(
8✔
1918
                                1_671_868_800_000,
8✔
1919
                                1_672_041_600_000,
8✔
1920
                            ))
8✔
1921
                        } else {
1922
                            None
2✔
1923
                        },
1924
                        bbox: if has_bbox {
10✔
1925
                            Some(SpatialPartition2D::new_unchecked(
8✔
1926
                                (0., 2.).into(),
8✔
1927
                                (2., 0.).into(),
8✔
1928
                            ))
8✔
1929
                        } else {
1930
                            None
2✔
1931
                        },
1932
                        resolution: if has_resolution {
10✔
1933
                            Some(GeoTransform::test_default().spatial_resolution())
8✔
1934
                        } else {
1935
                            None
2✔
1936
                        },
1937
                        bands: RasterBandDescriptors::new_single_band(),
10✔
1938
                    },
10✔
1939
                },
10✔
1940
            }
10✔
1941
            .boxed();
10✔
1942

1943
            let workflow = if time_shift_millis == 0 {
10✔
1944
                Workflow {
8✔
1945
                    operator: raster_source.into(),
8✔
1946
                }
8✔
1947
            } else {
1948
                Workflow {
2✔
1949
                    operator: TypedOperator::Raster(Box::new(TimeShift {
2✔
1950
                        params: TimeShiftParams::Relative {
2✔
1951
                            granularity: TimeGranularity::Millis,
2✔
1952
                            value: time_shift_millis,
2✔
1953
                        },
2✔
1954
                        sources: SingleRasterOrVectorSource {
2✔
1955
                            source: raster_source.into(),
2✔
1956
                        },
2✔
1957
                    })),
2✔
1958
                }
2✔
1959
            };
1960

1961
            let tiling_specification = TilingSpecification {
10✔
1962
                origin_coordinate: (0., 0.).into(),
10✔
1963
                tile_size_in_pixels: GridShape::new([2, 2]),
10✔
1964
            };
10✔
1965

10✔
1966
            let query_rectangle = RasterQueryRectangle {
10✔
1967
                spatial_bounds: SpatialPartition2D::new((0., 2.).into(), (2., 0.).into()).unwrap(),
10✔
1968
                time_interval: TimeInterval::new_unchecked(
10✔
1969
                    1_671_868_800_000 + i64::from(time_shift_millis),
10✔
1970
                    1_672_041_600_000 + i64::from(time_shift_millis),
10✔
1971
                ),
10✔
1972
                spatial_resolution: GeoTransform::test_default().spatial_resolution(),
10✔
1973
                attributes: BandSelection::first(),
10✔
1974
            };
10✔
1975

10✔
1976
            MockRasterWorkflowLayerDescription {
10✔
1977
                workflow,
10✔
1978
                tiling_specification,
10✔
1979
                query_rectangle,
10✔
1980
                collection_name: "Test Collection Name".to_string(),
10✔
1981
                collection_description: "Test Collection Description".to_string(),
10✔
1982
                layer_name: "Test Layer Name".to_string(),
10✔
1983
                layer_description: "Test Layer Description".to_string(),
10✔
1984
            }
10✔
1985
        }
10✔
1986

1987
        async fn create_layer_in_context(&self, app_ctx: &PostgresContext<NoTls>) -> Layer {
5✔
1988
            let session = admin_login(app_ctx).await;
5✔
1989
            let ctx = app_ctx.session_context(session.clone());
5✔
1990

1991
            let root_collection_id = ctx.db().get_root_layer_collection_id().await.unwrap();
5✔
1992

1993
            let collection_id = ctx
5✔
1994
                .db()
5✔
1995
                .add_layer_collection(
5✔
1996
                    AddLayerCollection {
5✔
1997
                        name: self.collection_name.clone(),
5✔
1998
                        description: self.collection_description.clone(),
5✔
1999
                        properties: Default::default(),
5✔
2000
                    },
5✔
2001
                    &root_collection_id,
5✔
2002
                )
5✔
2003
                .await
5✔
2004
                .unwrap();
5✔
2005

2006
            let layer_id = ctx
5✔
2007
                .db()
5✔
2008
                .add_layer(
5✔
2009
                    AddLayer {
5✔
2010
                        name: self.layer_name.clone(),
5✔
2011
                        description: self.layer_description.clone(),
5✔
2012
                        workflow: self.workflow.clone(),
5✔
2013
                        symbology: None,
5✔
2014
                        metadata: Default::default(),
5✔
2015
                        properties: Default::default(),
5✔
2016
                    },
5✔
2017
                    &collection_id,
5✔
2018
                )
5✔
2019
                .await
5✔
2020
                .unwrap();
5✔
2021

5✔
2022
            ctx.db().load_layer(&layer_id).await.unwrap()
5✔
2023
        }
5✔
2024
    }
2025

2026
    async fn send_dataset_creation_test_request(
5✔
2027
        app_ctx: PostgresContext<NoTls>,
5✔
2028
        layer: Layer,
5✔
2029
        session_id: SessionId,
5✔
2030
    ) -> ServiceResponse {
5✔
2031
        let layer_id = layer.id.layer_id;
5✔
2032
        let provider_id = layer.id.provider_id;
5✔
2033

5✔
2034
        // create dataset from workflow
5✔
2035
        let req = test::TestRequest::post()
5✔
2036
            .uri(&format!("/layers/{provider_id}/{layer_id}/dataset"))
5✔
2037
            .append_header((header::AUTHORIZATION, Bearer::new(session_id.to_string())))
5✔
2038
            .append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
5✔
2039
        send_test_request(req, app_ctx.clone()).await
5✔
2040
    }
5✔
2041

2042
    async fn create_dataset_request_with_result_success(
2✔
2043
        ctx: PostgresContext<NoTls>,
2✔
2044
        layer: Layer,
2✔
2045
        session: UserSession,
2✔
2046
    ) -> RasterDatasetFromWorkflowResult {
2✔
2047
        let res = send_dataset_creation_test_request(ctx.clone(), layer, session.id()).await;
2✔
2048
        assert_eq!(res.status(), 200, "{:?}", res.response());
2✔
2049

2050
        let task_response =
2✔
2051
            serde_json::from_str::<TaskResponse>(&read_body_string(res).await).unwrap();
2✔
2052

2✔
2053
        let task_manager = Arc::new(ctx.session_context(session).tasks());
2✔
2054
        wait_for_task_to_finish(task_manager.clone(), task_response.task_id).await;
2✔
2055

2056
        let status = task_manager
2✔
2057
            .get_task_status(task_response.task_id)
2✔
2058
            .await
2✔
2059
            .unwrap();
2✔
2060

2061
        let response = if let TaskStatus::Completed { info, .. } = status {
2✔
2062
            info.as_any_arc()
2✔
2063
                .downcast::<RasterDatasetFromWorkflowResult>()
2✔
2064
                .unwrap()
2✔
2065
                .as_ref()
2✔
2066
                .clone()
2✔
2067
        } else {
2068
            panic!("Task must be completed");
×
2069
        };
2070

2071
        response
2✔
2072
    }
2✔
2073

2074
    async fn raster_operator_to_geotiff_bytes<C: SessionContext>(
4✔
2075
        ctx: &C,
4✔
2076
        operator: Box<dyn RasterOperator>,
4✔
2077
        query_rectangle: RasterQueryRectangle,
4✔
2078
    ) -> geoengine_operators::util::Result<Vec<Vec<u8>>> {
4✔
2079
        let exe_ctx = ctx.execution_context().unwrap();
4✔
2080
        let query_ctx = ctx.mock_query_context().unwrap();
4✔
2081

2082
        let initialized_operator = operator
4✔
2083
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
4✔
2084
            .await
4✔
2085
            .unwrap();
4✔
2086
        let query_processor = initialized_operator
4✔
2087
            .query_processor()
4✔
2088
            .unwrap()
4✔
2089
            .get_u8()
4✔
2090
            .unwrap();
4✔
2091

4✔
2092
        raster_stream_to_geotiff_bytes(
4✔
2093
            query_processor,
4✔
2094
            query_rectangle,
4✔
2095
            query_ctx,
4✔
2096
            GdalGeoTiffDatasetMetadata {
4✔
2097
                no_data_value: Some(0.),
4✔
2098
                spatial_reference: SpatialReference::epsg_4326(),
4✔
2099
            },
4✔
2100
            GdalGeoTiffOptions {
4✔
2101
                compression_num_threads: get_config_element::<crate::config::Gdal>()
4✔
2102
                    .unwrap()
4✔
2103
                    .compression_num_threads,
4✔
2104
                as_cog: true,
4✔
2105
                force_big_tiff: false,
4✔
2106
            },
4✔
2107
            None,
4✔
2108
            Box::pin(futures::future::pending()),
4✔
2109
            exe_ctx.tiling_specification(),
4✔
2110
        )
4✔
2111
        .await
4✔
2112
    }
4✔
2113

2114
    async fn raster_layer_to_dataset_success(
2✔
2115
        app_ctx: PostgresContext<NoTls>,
2✔
2116
        mock_source: MockRasterWorkflowLayerDescription,
2✔
2117
    ) {
2✔
2118
        let session = admin_login(&app_ctx).await;
2✔
2119
        let ctx = app_ctx.session_context(session.clone());
2✔
2120

2121
        let layer = mock_source.create_layer_in_context(&app_ctx).await;
2✔
2122
        let response =
2✔
2123
            create_dataset_request_with_result_success(app_ctx, layer, ctx.session().clone()).await;
2✔
2124

2125
        // automatically deletes uploads on drop
2126
        let _test_uploads = TestDataUploads {
2✔
2127
            uploads: vec![response.upload],
2✔
2128
        };
2✔
2129

2✔
2130
        // query the layer
2✔
2131
        let workflow_operator = mock_source.workflow.operator.get_raster().unwrap();
2✔
2132
        let workflow_result = raster_operator_to_geotiff_bytes(
2✔
2133
            &ctx,
2✔
2134
            workflow_operator,
2✔
2135
            mock_source.query_rectangle.clone(),
2✔
2136
        )
2✔
2137
        .await
2✔
2138
        .unwrap();
2✔
2139

2✔
2140
        // query the newly created dataset
2✔
2141
        let dataset_operator = GdalSource {
2✔
2142
            params: GdalSourceParameters {
2✔
2143
                data: response.dataset.into(),
2✔
2144
            },
2✔
2145
        }
2✔
2146
        .boxed();
2✔
2147
        let dataset_result = raster_operator_to_geotiff_bytes(
2✔
2148
            &ctx,
2✔
2149
            dataset_operator,
2✔
2150
            mock_source.query_rectangle.clone(),
2✔
2151
        )
2✔
2152
        .await
2✔
2153
        .unwrap();
2✔
2154

2✔
2155
        assert_eq!(workflow_result.as_slice(), dataset_result.as_slice());
2✔
2156
    }
2✔
2157

2158
    fn test_raster_layer_to_dataset_success_tiling_spec() -> TilingSpecification {
1✔
2159
        let mock_source = MockRasterWorkflowLayerDescription::new(true, true, true, 0);
1✔
2160
        mock_source.tiling_specification
1✔
2161
    }
1✔
2162

2163
    #[ge_context::test(tiling_spec = "test_raster_layer_to_dataset_success_tiling_spec")]
1✔
2164
    async fn test_raster_layer_to_dataset_success(app_ctx: PostgresContext<NoTls>) {
1✔
2165
        let mock_source = MockRasterWorkflowLayerDescription::new(true, true, true, 0);
1✔
2166
        raster_layer_to_dataset_success(app_ctx, mock_source).await;
1✔
2167
    }
1✔
2168

2169
    fn test_raster_layer_with_timeshift_to_dataset_success_tiling_spec() -> TilingSpecification {
1✔
2170
        let mock_source = MockRasterWorkflowLayerDescription::new(true, true, true, 1_000);
1✔
2171
        mock_source.tiling_specification
1✔
2172
    }
1✔
2173

2174
    #[ge_context::test(
1✔
2175
        tiling_spec = "test_raster_layer_with_timeshift_to_dataset_success_tiling_spec"
1✔
2176
    )]
1✔
2177
    async fn test_raster_layer_with_timeshift_to_dataset_success(app_ctx: PostgresContext<NoTls>) {
1✔
2178
        let mock_source = MockRasterWorkflowLayerDescription::new(true, true, true, 1_000);
1✔
2179
        raster_layer_to_dataset_success(app_ctx, mock_source).await;
1✔
2180
    }
1✔
2181

2182
    fn test_raster_layer_to_dataset_no_time_interval_tiling_spec() -> TilingSpecification {
1✔
2183
        let mock_source = MockRasterWorkflowLayerDescription::new(false, true, true, 0);
1✔
2184
        mock_source.tiling_specification
1✔
2185
    }
1✔
2186

2187
    #[ge_context::test(tiling_spec = "test_raster_layer_to_dataset_no_time_interval_tiling_spec")]
1✔
2188
    async fn test_raster_layer_to_dataset_no_time_interval(app_ctx: PostgresContext<NoTls>) {
1✔
2189
        let mock_source = MockRasterWorkflowLayerDescription::new(false, true, true, 0);
1✔
2190

2191
        let session = admin_login(&app_ctx).await;
1✔
2192

2193
        let session_id = session.id();
1✔
2194

2195
        let layer = mock_source.create_layer_in_context(&app_ctx).await;
1✔
2196

2197
        let res = send_dataset_creation_test_request(app_ctx, layer, session_id).await;
1✔
2198

2199
        ErrorResponse::assert(
1✔
2200
            res,
1✔
2201
            400,
1✔
2202
            "LayerResultDescriptorMissingFields",
1✔
2203
            "Result Descriptor field 'time' is None",
1✔
2204
        )
1✔
2205
        .await;
1✔
2206
    }
1✔
2207

2208
    fn test_raster_layer_to_dataset_no_bounding_box_tiling_spec() -> TilingSpecification {
1✔
2209
        let mock_source = MockRasterWorkflowLayerDescription::new(true, false, true, 0);
1✔
2210
        mock_source.tiling_specification
1✔
2211
    }
1✔
2212

2213
    #[ge_context::test(tiling_spec = "test_raster_layer_to_dataset_no_bounding_box_tiling_spec")]
1✔
2214
    async fn test_raster_layer_to_dataset_no_bounding_box(app_ctx: PostgresContext<NoTls>) {
1✔
2215
        let mock_source = MockRasterWorkflowLayerDescription::new(true, false, true, 0);
1✔
2216

2217
        let session = admin_login(&app_ctx).await;
1✔
2218

2219
        let session_id = session.id();
1✔
2220

2221
        let layer = mock_source.create_layer_in_context(&app_ctx).await;
1✔
2222

2223
        let res = send_dataset_creation_test_request(app_ctx, layer, session_id).await;
1✔
2224

2225
        ErrorResponse::assert(
1✔
2226
            res,
1✔
2227
            400,
1✔
2228
            "LayerResultDescriptorMissingFields",
1✔
2229
            "Result Descriptor field 'bbox' is None",
1✔
2230
        )
1✔
2231
        .await;
1✔
2232
    }
1✔
2233

2234
    fn test_raster_layer_to_dataset_no_spatial_resolution_tiling_spec() -> TilingSpecification {
1✔
2235
        let mock_source = MockRasterWorkflowLayerDescription::new(true, true, false, 0);
1✔
2236
        mock_source.tiling_specification
1✔
2237
    }
1✔
2238

2239
    #[ge_context::test(
1✔
2240
        tiling_spec = "test_raster_layer_to_dataset_no_spatial_resolution_tiling_spec"
1✔
2241
    )]
1✔
2242
    async fn test_raster_layer_to_dataset_no_spatial_resolution(app_ctx: PostgresContext<NoTls>) {
1✔
2243
        let mock_source = MockRasterWorkflowLayerDescription::new(true, true, false, 0);
1✔
2244

2245
        let session = admin_login(&app_ctx).await;
1✔
2246

2247
        let session_id = session.id();
1✔
2248

2249
        let layer = mock_source.create_layer_in_context(&app_ctx).await;
1✔
2250

2251
        let res = send_dataset_creation_test_request(app_ctx, layer, session_id).await;
1✔
2252

2253
        ErrorResponse::assert(
1✔
2254
            res,
1✔
2255
            400,
1✔
2256
            "LayerResultDescriptorMissingFields",
1✔
2257
            "Result Descriptor field 'spatial_resolution' is None",
1✔
2258
        )
1✔
2259
        .await;
1✔
2260
    }
1✔
2261
}
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

© 2025 Coveralls, Inc