Repository: CS-SI/eodag
Pull request #909 (2023-11-01 08:21:38)

feat: server mode collections search
Implements server-mode free text search of collections https://github.com/stac-api-extensions/collection-search#free-text-search.

Uses updated [guess_product_type](https://eodag.readthedocs.io/en/latest/api_reference/core.html#eodag.api.core.EODataAccessGateway.guess_product_type) method that now accepts `free_text_filter` and `intersect` parameters.
See updated examples on [how to guess a product type](https://eodag.readthedocs.io/en/latest/notebooks/api_user_guide/4_search.html#Guess-a-product-type).


github-actions[bot]: ## Test Results
    1 files   -        3      1 suites   - 3   1m 13s [:stopwatch:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v2.11.0/README.md#the-symbols "duration of all tests") - 4m 30s
431 tests +       1  427 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v2.11.0/README.md#the-symbols "passed tests") ±       0  3 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v2.11.0/README.md#the-symbols "skipped / disabled tests") ±  0  1 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v2.11.0/README.md#the-symbols "failed tests") +1 
431 runs   - 1 289  427 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v2.11.0/README.md#the-symbols "passed tests")  - 1 217  3 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v2.11.0/README.md#the-symbols "skipped / disabled tests")  - 73  1 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v2.11.0/README.md#the-symbols "failed tests") +1 

For more details on these failures, see [this check](https://github.com/CS-SI/eodag/runs/18255176878).

Results for commit 3171d910. ± Comparison against base commit 19d7d6b2.

[test-results]:data:application/gzip;base64,H4sIAFsLQmUC/1WMyw6DIBQFf8Ww7gJ6UbA/Y5RHQqrS8FiZ/nsv2opdzpyc2Yh1s4nk0bBbQ2J26QSdw5icXxEFIOOSysaB/WiIWami7qKqp3uhOh+DHd38TR7ChOADGoom5LU2C/wnD1OLO1+CO197yi+LSwgEmGC6Z7QHLtTErZCcsklR3mmrWtsZkC0H2ZH3BzdujYQEAQAA

github-actions[bot]: <strong>Code Coverage (Ubuntu)</strong>

| File | Coverage |   |
| - | :-: | :-: |
| **All files** | `82%` | :white_check_mark: |

_Minimum allowed coverage is `70%`_

<p align="right">Generated by :monkey: cobertura-action against 3171d9109347cb4f78401bc046dfc5f6e3854386 </p>
github-actions[bot]: <strong>Code Coverage (Windows)</strong>

| File | Coverage |   |
| - | :-: | :-: |
| **All files** | `24%` | :x: |

_Minimum allowed coverage is `70%`_

<p align="right">Generated by :monkey: cobertura-action against 3171d9109347cb4f78401bc046dfc5f6e3854386 </p>
alambare-csgroup: @dalpasso the following STAC extension should be added to the server conformance classes:

https://api.stacspec.org/v1.0.0-rc.1/collection-search#free-text
dalpasso: Feature reimplemented in `EODataAccessGateway.list_product_types()` using `whoosh`
alambare-csgroup: @dalpasso the new parameter is missing from the OpenAPI definition.

It should be added at https://github.com/CS-SI/eodag/blob/852-server-mode-collections-search/eodag/resources/stac_api.yml#L165

You can check how it is done for */search*: https://github.com/CS-SI/eodag/blob/852-server-mode-collections-search/eodag/resources/stac_api.yml#L305
github-actions[bot]: ![badge](https://raw.githubusercontent.com/CS-SI/eodag/_xml_coverage_reports/data/852-server-mode-collections-search/./badge.svg)


## Code Coverage (Ubuntu)

<details>


```
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                     11       0  100.00%
cli.py                                         300      47  84.33%   61, 647-686, 788-839, 843
config.py                                      312      27  91.35%   80-82, 91, 99, 103-105, 178, 190, 385-387, 451-454, 501-502, 511-512, 591, 660-665, 667
crunch.py                                        6       6  0.00%    18-24
api/__init__.py                                  1       0  100.00%
api/core.py                                    760      78  89.74%   102-110, 383, 598, 657, 702-705, 743, 846, 850-855, 881, 951, 1102-1107, 1193-1205, 1245, 1247, 1251, 1272-1274, 1278-1289, 1302-1308, 1398-1401, 1430-1450, 1498, 1504-1507, 1516, 1863, 1896-1902, 2165, 2169-2172, 2186-2188, 2253, 2263, 2295, 2298
api/search_result.py                            44       6  86.36%   33-35, 70, 79, 86, 100
api/product/__init__.py                          5       0  100.00%
api/product/_assets.py                          39       3  92.31%   27, 77, 124
api/product/_product.py                        200      30  85.00%   46-51, 55-57, 161-168, 252-253, 343, 379, 440, 454-457, 470, 490-493, 534-540
api/product/metadata_mapping.py                641      84  86.90%   64-66, 127-129, 230, 262-263, 315-327, 329, 340, 344-360, 399-402, 439, 460-463, 477, 556-557, 581-582, 588-591, 606-607, 771, 871-872, 915-920, 1044, 1058-1078, 1098, 1103, 1213, 1235, 1249, 1262-1281, 1320, 1372, 1410-1414, 1433
api/product/drivers/__init__.py                  6       0  100.00%
api/product/drivers/base.py                      8       2  75.00%   23, 41
plugins/__init__.py                              1       0  100.00%
plugins/base.py                                 23       3  86.96%   25, 48, 55
plugins/manager.py                             130      11  91.54%   49-51, 93-98, 142, 271-272
plugins/apis/__init__.py                         1       0  100.00%
plugins/apis/base.py                            31       8  74.19%   26-29, 93, 97, 162, 207
plugins/apis/cds.py                            204      46  77.45%   72-75, 123-126, 173-185, 241, 306, 314-319, 332-339, 357-359, 383-385, 395-397, 411-412, 457, 460, 470, 482, 515, 517
plugins/apis/ecmwf.py                           94      12  87.23%   48-51, 152-154, 199-200, 226-228
plugins/apis/usgs.py                           165      34  79.39%   59-61, 122, 195, 229, 264-266, 271, 297-298, 303, 331-338, 347-352, 375-381, 383-389, 412
plugins/authentication/__init__.py               7       1  85.71%   31
plugins/authentication/aws_auth.py              20       2  90.00%   25-27
plugins/authentication/base.py                  19       2  89.47%   26, 47
plugins/authentication/generic.py               16       3  81.25%   28, 40, 50
plugins/authentication/header.py                17       1  94.12%   27
plugins/authentication/keycloak.py              82      17  79.27%   32-34, 149-150, 180-201, 227-232
plugins/authentication/oauth.py                 15       8  46.67%   25, 32-34, 38-41
plugins/authentication/openid_connect.py        99      60  39.39%   34-36, 133-145, 149-167, 175-209, 215-224, 233-273, 278-286, 291-292
plugins/authentication/qsauth.py                35       2  94.29%   32, 80
plugins/authentication/sas_auth.py              47       2  95.74%   32, 71
plugins/authentication/token.py                 75       8  89.33%   35-37, 102, 127, 154-157
plugins/crunch/__init__.py                       1       0  100.00%
plugins/crunch/base.py                          10       2  80.00%   25, 38
plugins/crunch/filter_date.py                   62      15  75.81%   30, 53-58, 72, 81, 90, 93, 105-107, 116-118, 125
plugins/crunch/filter_latest_intersect.py       50      10  80.00%   32-34, 51-52, 71, 80-83, 85, 92-95
plugins/crunch/filter_latest_tpl_name.py        33       2  93.94%   28, 86
plugins/crunch/filter_overlap.py                68      17  75.00%   28-30, 33, 82-85, 91, 99, 110-126
plugins/crunch/filter_property.py               33       8  75.76%   29, 60-65, 68-69, 85-89
plugins/download/__init__.py                     1       0  100.00%
plugins/download/aws.py                        478     160  66.53%   75-80, 277, 344-347, 361-365, 408-410, 414, 442-443, 449-453, 483, 551-559, 563, 601-609, 616-618, 659-733, 751-812, 823-828, 844-857, 886, 901-903, 906, 916-924, 932-945, 955-974, 981-993, 1034, 1060, 1105-1107, 1327
plugins/download/base.py                       259      55  78.76%   58-61, 142, 177, 314-315, 335-341, 372-376, 382-383, 427, 430-444, 456, 460, 533-537, 567-568, 576-593, 600-608, 610-614, 661, 684, 706, 714
plugins/download/creodias_s3.py                 17       9  47.06%   44-58
plugins/download/http.py                       402      92  77.11%   69-74, 108, 152, 172-179, 189-202, 235-240, 290, 322, 349-351, 361-369, 428-432, 489, 564-616, 629, 653-655, 685-686, 694, 697, 713, 737, 759-760, 767, 828-834, 884-885, 891, 901, 967, 985-1001
plugins/download/s3rest.py                     111      24  78.38%   53-54, 158, 185, 215-222, 225-227, 231, 244-250, 258-259, 262-266, 289, 309-312
plugins/search/__init__.py                       1       0  100.00%
plugins/search/base.py                          91      11  87.91%   43-44, 91, 95, 121-125, 252, 272
plugins/search/build_search_result.py           56       6  89.29%   67, 112-113, 121, 132, 176
plugins/search/creodias_s3.py                   55       3  94.55%   56, 74, 107
plugins/search/csw.py                          107      83  22.43%   43-45, 57-58, 62-63, 74-122, 128-141, 149-181, 199-240
plugins/search/data_request_search.py          190      59  68.95%   52, 87-90, 106, 118, 137, 143, 149-150, 163-166, 220-221, 225, 235-241, 246, 270, 280-290, 301, 303, 310-311, 313-314, 330-334, 367, 374, 385, 398, 404-416, 421
plugins/search/qssearch.py                     514      54  89.49%   73, 340-341, 435-441, 468-471, 543-544, 603, 618, 671, 692, 695-696, 705, 716, 725, 736-737, 748, 808-813, 817-818, 846, 909, 928-945, 980, 1114-1115, 1136, 1178-1179, 1189-1195, 1232, 1245, 1265, 1353
plugins/search/static_stac_search.py            46       3  93.48%   39-40, 81
rest/__init__.py                                 1       0  100.00%
rest/core.py                                   168      13  92.26%   238, 506-507, 510, 512, 515, 518-520, 572, 579-583
rest/server.py                                 306      54  82.35%   81-82, 106, 129-130, 241-243, 259, 299-300, 312-328, 410-415, 445, 607-614, 645, 688-689, 782-784, 801-806, 836, 838, 842-843, 847-848
rest/stac.py                                   428      93  78.27%   59-61, 229-231, 273, 286-295, 314-320, 365, 402-404, 427, 462-463, 549-594, 639, 824, 889-891, 1110, 1120-1132, 1145-1167, 1181-1226, 1385-1386
rest/types/__init__.py                           1       0  100.00%
rest/types/eodag_search.py                     185       9  95.14%   52-55, 231-235, 288, 291, 359
rest/types/stac_queryables.py                   38       7  81.58%   28, 120-126, 129
rest/types/stac_search.py                      122      10  91.80%   48-51, 167, 182-184, 192, 196
rest/utils/__init__.py                         116      14  87.93%   53, 108-109, 128-130, 180, 190-204, 236
rest/utils/cql_evaluate.py                      48       5  89.58%   69, 76, 90, 97, 105
rest/utils/rfc3339.py                           31       3  90.32%   78, 90, 92
types/__init__.py                               76       6  92.11%   53, 87, 152, 172, 177, 185
types/bbox.py                                   43      19  55.81%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/queryables.py                             81       0  100.00%
types/search_args.py                            70      18  74.29%   60-64, 71-88, 104
utils/__init__.py                              483      43  91.10%   80-82, 103-105, 183-184, 193-220, 223, 237, 319-323, 399-403, 424-426, 508, 513, 523, 561-562, 958-961, 969-970, 1011-1012, 1092, 1176, 1194, 1367
utils/constraints.py                           120      40  66.67%   81-90, 131, 136, 140, 151, 174, 185-193, 202, 216-232, 241-252
utils/exceptions.py                             36       2  94.44%   23, 89
utils/import_system.py                          30      20  33.33%   27, 67-81, 93-103
utils/logging.py                                29       1  96.55%   123
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/stac_reader.py                            89      27  69.66%   54-55, 62-83, 90-92, 96, 135, 149-152
TOTAL                                         8544    1523  82.17%
```

</details>


### Diff against develop

```
Filename       Stmts    Miss  Cover
-----------  -------  ------  -------
api/core.py      +20      +1  +0.15%
TOTAL            +20      +1  +0.03%
```


Results for commit: 6e9e38fb06f533166ec359d640b3349d081139ef


_Minimum allowed coverage is `70%`_


:recycle: This comment has been updated with latest results


<!-- Sticky Pull Request Commentartifacts/unit-test-results-python3.12-ubuntu-latest/coverage.xml -->
github-actions[bot]: ![badge](https://raw.githubusercontent.com/CS-SI/eodag/_xml_coverage_reports_win/data/852-server-mode-collections-search/./badge.svg)


## Code Coverage (Windows)

<details>


```
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                     11       0  100.00%
cli.py                                         300      47  84.33%   61, 647-686, 788-839, 843
config.py                                      312      28  91.03%   80-82, 91, 99, 103-105, 178, 190, 385-387, 451-454, 501-502, 511-512, 591, 625, 660-665, 667
crunch.py                                        6       6  0.00%    18-24
api/__init__.py                                  1       0  100.00%
api/core.py                                    760      82  89.21%   102-110, 383, 598, 657, 702-705, 743, 846, 850-855, 881, 951, 1102-1107, 1193-1205, 1245, 1247, 1251, 1272-1274, 1278-1289, 1302-1308, 1398-1401, 1430-1450, 1498, 1504-1507, 1516, 1863, 1896-1902, 2165, 2169-2172, 2186-2188, 2230-2237, 2253, 2263, 2295, 2298
api/search_result.py                            44       6  86.36%   33-35, 70, 79, 86, 100
api/product/__init__.py                          5       0  100.00%
api/product/_assets.py                          39       3  92.31%   27, 77, 124
api/product/_product.py                        200      30  85.00%   46-51, 55-57, 161-168, 252-253, 343, 379, 440, 454-457, 470, 490-493, 534-540
api/product/metadata_mapping.py                641      85  86.74%   64-66, 127-129, 230, 262-263, 315-327, 329, 340, 344-360, 399-402, 439, 460-463, 477, 556-557, 581-582, 588-591, 606-607, 771, 871-872, 915-920, 1044, 1058-1078, 1098, 1103, 1213, 1235, 1249, 1262-1281, 1320, 1372, 1395, 1410-1414, 1433
api/product/drivers/__init__.py                  6       0  100.00%
api/product/drivers/base.py                      8       2  75.00%   23, 41
plugins/__init__.py                              1       0  100.00%
plugins/base.py                                 23       4  82.61%   25, 48, 55, 68
plugins/manager.py                             130      11  91.54%   49-51, 93-98, 142, 271-272
plugins/apis/__init__.py                         1       0  100.00%
plugins/apis/base.py                            31       9  70.97%   26-29, 93, 97, 110, 162, 207
plugins/apis/cds.py                            204      53  74.02%   72-75, 123-126, 173-185, 241, 306, 314-319, 332-339, 357-359, 383-385, 395-397, 411-412, 457, 460, 470, 482, 507-522
plugins/apis/ecmwf.py                           94      12  87.23%   48-51, 152-154, 199-200, 226-228
plugins/apis/usgs.py                           165      34  79.39%   59-61, 122, 195, 229, 264-266, 271, 297-298, 303, 331-338, 347-352, 375-381, 383-389, 412
plugins/authentication/__init__.py               7       1  85.71%   31
plugins/authentication/aws_auth.py              20       2  90.00%   25-27
plugins/authentication/base.py                  19       3  84.21%   26, 34, 47
plugins/authentication/generic.py               16       3  81.25%   28, 40, 50
plugins/authentication/header.py                17       1  94.12%   27
plugins/authentication/keycloak.py              82      17  79.27%   32-34, 149-150, 180-201, 227-232
plugins/authentication/oauth.py                 15       8  46.67%   25, 32-34, 38-41
plugins/authentication/openid_connect.py        99      60  39.39%   34-36, 133-145, 149-167, 175-209, 215-224, 233-273, 278-286, 291-292
plugins/authentication/qsauth.py                35       2  94.29%   32, 80
plugins/authentication/sas_auth.py              47       2  95.74%   32, 71
plugins/authentication/token.py                 75       8  89.33%   35-37, 102, 127, 154-157
plugins/crunch/__init__.py                       1       0  100.00%
plugins/crunch/base.py                          10       2  80.00%   25, 38
plugins/crunch/filter_date.py                   62      15  75.81%   30, 53-58, 72, 81, 90, 93, 105-107, 116-118, 125
plugins/crunch/filter_latest_intersect.py       50      35  30.00%   32-34, 48-53, 69-114
plugins/crunch/filter_latest_tpl_name.py        33       2  93.94%   28, 86
plugins/crunch/filter_overlap.py                68      17  75.00%   28-30, 33, 82-85, 91, 99, 110-126
plugins/crunch/filter_property.py               33       8  75.76%   29, 60-65, 68-69, 85-89
plugins/download/__init__.py                     1       0  100.00%
plugins/download/aws.py                        478     160  66.53%   75-80, 277, 344-347, 361-365, 408-410, 414, 442-443, 449-453, 483, 551-559, 563, 601-609, 616-618, 659-733, 751-812, 823-828, 844-857, 886, 901-903, 906, 916-924, 932-945, 955-974, 981-993, 1034, 1060, 1105-1107, 1327
plugins/download/base.py                       259      57  77.99%   58-61, 142, 177, 245-247, 314-315, 335-341, 372-376, 382-383, 427, 430-444, 456, 460, 533-537, 567-568, 576-593, 600-608, 610-614, 661, 684, 706, 714
plugins/download/creodias_s3.py                 17       9  47.06%   44-58
plugins/download/http.py                       402      92  77.11%   69-74, 108, 152, 172-179, 189-202, 235-240, 290, 322, 349-351, 361-369, 428-432, 489, 564-616, 629, 653-655, 685-686, 694, 697, 713, 737, 759-760, 767, 828-834, 884-885, 891, 901, 967, 985-1001
plugins/download/s3rest.py                     111      24  78.38%   53-54, 158, 185, 215-222, 225-227, 231, 244-250, 258-259, 262-266, 289, 309-312
plugins/search/__init__.py                       1       0  100.00%
plugins/search/base.py                          91      11  87.91%   43-44, 91, 95, 121-125, 252, 272
plugins/search/build_search_result.py           56       6  89.29%   67, 112-113, 121, 132, 176
plugins/search/creodias_s3.py                   55       3  94.55%   56, 74, 107
plugins/search/csw.py                          107      83  22.43%   43-45, 57-58, 62-63, 74-122, 128-141, 149-181, 199-240
plugins/search/data_request_search.py          190      59  68.95%   52, 87-90, 106, 118, 137, 143, 149-150, 163-166, 220-221, 225, 235-241, 246, 270, 280-290, 301, 303, 310-311, 313-314, 330-334, 367, 374, 385, 398, 404-416, 421
plugins/search/qssearch.py                     514      78  84.82%   73, 340-341, 435-441, 468-471, 543-544, 603, 618, 671, 692, 695-696, 705, 716, 725, 736-737, 748, 808-813, 817-818, 846, 909, 928-945, 980, 1114-1115, 1136, 1178-1179, 1189-1195, 1232, 1245, 1265, 1313-1382
plugins/search/static_stac_search.py            46       3  93.48%   39-40, 81
rest/__init__.py                                 1       0  100.00%
rest/core.py                                   168      60  64.29%   135, 137, 139, 145-146, 163-171, 178-184, 230-259, 413-440, 458, 505-535, 572, 579-583
rest/server.py                                 306     306  0.00%    18-861
rest/stac.py                                   428     147  65.65%   59-61, 214, 229-231, 273, 286-295, 314-320, 365, 402-404, 427, 462-463, 549-594, 639, 766, 824, 889-891, 908-910, 918-920, 933-935, 949-966, 976-997, 1007-1029, 1037-1054, 1077-1100, 1110, 1120-1132, 1145-1167, 1181-1226, 1379-1405
rest/types/__init__.py                           1       0  100.00%
rest/types/eodag_search.py                     185      18  90.27%   52-55, 231-235, 268-270, 288, 291, 297, 301, 359, 371-374
rest/types/stac_queryables.py                   38      13  65.79%   28, 53-58, 120-126, 129, 133-134
rest/types/stac_search.py                      122      12  90.16%   48-51, 167, 182-184, 192, 196, 240, 243
rest/utils/__init__.py                         116      31  73.28%   53, 79-85, 105, 108-109, 128-130, 147, 173-181, 188-209, 236
rest/utils/cql_evaluate.py                      48       5  89.58%   69, 76, 90, 97, 105
rest/utils/rfc3339.py                           31       5  83.87%   73-74, 78, 90, 92
types/__init__.py                               76      11  85.53%   53, 87, 125, 152, 162-164, 172, 177, 185, 195
types/bbox.py                                   43      19  55.81%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/queryables.py                             81       0  100.00%
types/search_args.py                            70      18  74.29%   60-64, 71-88, 104
utils/__init__.py                              483      43  91.10%   80-82, 103-105, 183-184, 193-220, 223, 237, 319-323, 399-403, 424-426, 508, 513, 523, 561-562, 958-961, 969-970, 1011-1012, 1092, 1176, 1194, 1367
utils/constraints.py                           120      40  66.67%   81-90, 131, 136, 140, 151, 174, 185-193, 202, 216-232, 241-252
utils/exceptions.py                             36       2  94.44%   23, 89
utils/import_system.py                          30      20  33.33%   27, 67-81, 93-103
utils/logging.py                                29       1  96.55%   123
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/stac_reader.py                            89      27  69.66%   54-55, 62-83, 90-92, 96, 135, 149-152
TOTAL                                         8544    1984  76.78%
```

</details>


### Diff against develop

```
Filename       Stmts    Miss  Cover
-----------  -------  ------  -------
api/core.py      +20      +1  +0.16%
TOTAL            +20      +1  +0.04%
```


Results for commit: 6e9e38fb06f533166ec359d640b3349d081139ef


_Minimum allowed coverage is `70%`_


:recycle: This comment has been updated with latest results


<!-- Sticky Pull Request Commentartifacts/unit-test-results-python3.12-windows-latest/coverage.xml -->

Patch:
diff --git a/docs/notebooks/api_user_guide/4_search.ipynb b/docs/notebooks/api_user_guide/4_search.ipynb
index ade3ccab..0337a757 100644
--- a/docs/notebooks/api_user_guide/4_search.ipynb
+++ b/docs/notebooks/api_user_guide/4_search.ipynb
@@ -2228,7 +2228,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "In the previous request we made use of the whoosh query language which can be used to do complex text search. It supports the boolean operators AND, OR and NOT to combine the search terms. If a space is given between two words as in the example above, this corresponds to the operator AND. Brackets '()' can also be used. The example above also shows the use of the wildcard operator '*' which can represent any numer of characters. The wildcard operator '?' always represents only one character. It is also possible to match a range of terms by using square brackets '[]' and TO, e.g. [A TO D] will match all words in the lexical range between A and D. Below you can find some examples for the different operators."
+    "In the previous request we made use of the [whoosh query language](https://whoosh.readthedocs.io/en/latest/querylang.html#the-default-query-language) which can be used to do complex text search. It supports the boolean operators `AND`, `OR` and `NOT` to combine the search terms. If a space is given between two words as in the example above, this corresponds to the operator AND. Brackets `()` can also be used. The example above also shows the use of the wildcard operator `*` which can represent any numer of characters. The wildcard operator `?` always represents only one character. It is also possible to match a range of terms by using square brackets `[]` and TO, e.g. `[A TO D]` will match all words in the lexical range between A and D. Below you can find some examples for the different operators."
    ]
   },
   {
@@ -2274,9 +2274,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "returns all product types where the platform is either LANDSAT or SENTINEL1:\n",
-    "\n",
-    "['L57_REFLECTANCE', 'LANDSAT_C2L1', 'LANDSAT_C2L2', 'LANDSAT_C2L2ALB_BT', 'LANDSAT_C2L2ALB_SR', 'LANDSAT_C2L2ALB_ST', 'LANDSAT_C2L2ALB_TA', 'LANDSAT_C2L2_SR', 'LANDSAT_C2L2_ST', 'LANDSAT_ETM_C1', 'LANDSAT_ETM_C2L1', 'LANDSAT_ETM_C2L2', 'LANDSAT_TM_C1', 'LANDSAT_TM_C2L1', 'LANDSAT_TM_C2L2', 'S1_SAR_GRD', 'S1_SAR_OCN', 'S1_SAR_RAW', 'S1_SAR_SLC']"
+    "returns all product types where the platform is either LANDSAT or SENTINEL1."
    ]
   },
   {
@@ -2319,9 +2317,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "returns all product types which contain either the keywords LANDSAT and collection2 or the keyword SAR:\n",
-    "\n",
-    "['LANDSAT_C2L1', 'LANDSAT_C2L2', 'LANDSAT_C2L2ALB_BT', 'LANDSAT_C2L2ALB_SR', 'LANDSAT_C2L2ALB_ST', 'LANDSAT_C2L2ALB_TA', 'LANDSAT_C2L2_SR', 'LANDSAT_C2L2_ST', 'LANDSAT_ETM_C2L1', 'LANDSAT_ETM_C2L2', 'LANDSAT_TM_C2L1', 'LANDSAT_TM_C2L2', 'S1_SAR_GRD', 'S1_SAR_OCN', 'S1_SAR_RAW', 'S1_SAR_SLC']"
+    "returns all product types which contain either the keywords LANDSAT and collection2 or the keyword SAR."
    ]
   },
   {
@@ -2366,9 +2362,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "returns all product types where the platformSerialIdentifier is composed of 'L' and one other character:\n",
-    "\n",
-    "['L57_REFLECTANCE', 'L8_OLI_TIRS_C1L1', 'L8_REFLECTANCE', 'LANDSAT_C2L1', 'LANDSAT_C2L2', 'LANDSAT_C2L2ALB_BT', 'LANDSAT_C2L2ALB_SR', 'LANDSAT_C2L2ALB_ST', 'LANDSAT_C2L2ALB_TA', 'LANDSAT_C2L2_SR', 'LANDSAT_C2L2_ST', 'LANDSAT_ETM_C1', 'LANDSAT_ETM_C2L1', 'LANDSAT_ETM_C2L2', 'LANDSAT_TM_C1', 'LANDSAT_TM_C2L1', 'LANDSAT_TM_C2L2']"
+    "returns all product types where the platformSerialIdentifier is composed of 'L' and one other character."
    ]
   },
   {
@@ -2439,9 +2433,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "returns all product types where the platform is SENTINEL1, SENTINEL2 or SENTINEL3:\n",
-    "\n",
-    "['S1_SAR_GRD', 'S1_SAR_OCN', 'S1_SAR_RAW', 'S1_SAR_SLC', 'S2_MSI_L1C', 'S2_MSI_L2A', 'S2_MSI_L2A_COG', 'S2_MSI_L2A_MAJA', 'S2_MSI_L2B_MAJA_SNOW', 'S2_MSI_L2B_MAJA_WATER', 'S2_MSI_L3A_WASP', 'S3_EFR', 'S3_ERR', 'S3_LAN', 'S3_OLCI_L2LFR', 'S3_OLCI_L2LRR', 'S3_OLCI_L2WFR', 'S3_OLCI_L2WRR', 'S3_RAC', 'S3_SLSTR_L1RBT', 'S3_SLSTR_L2AOD', 'S3_SLSTR_L2FRP', 'S3_SLSTR_L2LST', 'S3_SLSTR_L2WST', 'S3_SRA', 'S3_SRA_A', 'S3_SRA_BS', 'S3_SY_AOD', 'S3_SY_SYN', 'S3_SY_V10', 'S3_SY_VG1', 'S3_SY_VGP', 'S3_WAT']"
+    "returns all product types where the platform is SENTINEL1, SENTINEL2 or SENTINEL3."
    ]
   },
   {
@@ -2454,7 +2446,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 74,
+   "execution_count": 4,
    "metadata": {},
    "outputs": [
     {
@@ -2477,7 +2469,7 @@
        " 'LANDSAT_TM_C2L2']"
       ]
      },
-     "execution_count": 74,
+     "execution_count": 4,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -2491,29 +2483,68 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "['LANDSAT_C2L1', \n",
-    "'L57_REFLECTANCE', \n",
-    "'LANDSAT_C2L2', \n",
-    "'LANDSAT_C2L2ALB_BT', \n",
-    "'LANDSAT_C2L2ALB_SR', \n",
-    "'LANDSAT_C2L2ALB_ST', \n",
-    "'LANDSAT_C2L2ALB_TA', \n",
-    "'LANDSAT_C2L2_SR', \n",
-    "'LANDSAT_C2L2_ST', \n",
-    "'LANDSAT_ETM_C1', \n",
-    "'LANDSAT_ETM_C2L1', \n",
-    "'LANDSAT_ETM_C2L2', \n",
-    "'LANDSAT_TM_C1', \n",
-    "'LANDSAT_TM_C2L1', \n",
-    "'LANDSAT_TM_C2L2']"
+    "The product types in the result are ordered by how well they match the criteria. In the example above only the first product type (LANDSAT_C2L1) matches the second parameter (platformSerialIdentifier=\"L1\"), all other product types only match the first criterion. Therefore, it is usually best to use the first product type in the list as it will be the one that fits best."
    ]
   },
   {
-   "attachments": {},
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "The product types in the result are ordered by how well they match the criteria. In the example above only the first product type (LANDSAT_C2L1) matches the second parameter (platformSerialIdentifier=\"L1\"), all other product types only match the first criterion. Therefore, it is usually best to use the first product type in the list as it will be the one that fits best."
+    "Per paramater guesses are joined using a `UNION` by default (`intersect=False`). This can also be changed to an intersection:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "['LANDSAT_C2L1']"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "dag.guess_product_type(platform=\"LANDSAT\", platformSerialIdentifier=\"L1\", intersect=True)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "[Whoosh query language](https://whoosh.readthedocs.io/en/latest/querylang.html#the-default-query-language) *free text search* can also be passed to the method, it will be used to search in `title`, `abstract` and `keywords` fields:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "['ERA5_SL_MONTHLY',\n",
+       " 'ERA5_PL_MONTHLY',\n",
+       " 'ERA5_LAND_MONTHLY',\n",
+       " 'ERA5_SL',\n",
+       " 'ERA5_PL',\n",
+       " 'GLOFAS_SEASONAL_REFORECAST',\n",
+       " 'SEASONAL_MONTHLY_PL',\n",
+       " 'SEASONAL_MONTHLY_SL']"
+      ]
+     },
+     "execution_count": 6,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "dag.guess_product_type(\"ECMWF AND MONTHLY\")"
    ]
   },
   {
diff --git a/eodag/api/core.py b/eodag/api/core.py
index 2407732d..92716831 100644
--- a/eodag/api/core.py
+++ b/eodag/api/core.py
@@ -289,7 +289,7 @@ class EODataAccessGateway:
             product_types_schema = Schema(
                 ID=fields.STORED,
                 alias=fields.ID,
-                abstract=fields.STORED,
+                abstract=fields.TEXT,
                 instrument=fields.IDLIST,
                 platform=fields.ID,
                 platformSerialIdentifier=fields.IDLIST,
@@ -297,7 +297,7 @@ class EODataAccessGateway:
                 sensorType=fields.ID,
                 md5=fields.ID,
                 license=fields.ID,
-                title=fields.ID,
+                title=fields.TEXT,
                 missionStartDate=fields.ID,
                 missionEndDate=fields.ID,
                 keywords=fields.KEYWORD(analyzer=kw_analyzer),
@@ -914,9 +914,22 @@ class EODataAccessGateway:
 
         return self.product_types_config[product_type].get("alias", product_type)
 
-    def guess_product_type(self, **kwargs: Any) -> List[str]:
-        """Find eodag product types codes that best match a set of search params
+    def guess_product_type(
+        self,
+        free_text_filter: Optional[str] = None,
+        intersect: bool = False,
+        **kwargs: Any,
+    ) -> List[str]:
+        """Find eodag product types ids that best match a set of search params
+
+        See https://whoosh.readthedocs.io/en/latest/querylang.html#the-default-query-language
+        for syntax.
 
+        :param free_text_filter: whoosh compatible free text search filter used to search
+                                 `title`, `abstract` and `keywords`
+        :type free_text_filter: Optional[str]
+        :param intersect: join results for each parameter using INTERSECT instead of UNION
+        :type intersect: bool
         :param kwargs: A set of search parameters as keywords arguments
         :returns: The best match for the given parameters
         :rtype: list[str]
@@ -924,6 +937,9 @@ class EODataAccessGateway:
         """
         if kwargs.get("productType", None):
             return [kwargs["productType"]]
+        free_text_search_params = (
+            ["title", "abstract", "keywords"] if free_text_filter else []
+        )
         supported_params = {
             param
             for param in (
@@ -934,6 +950,8 @@ class EODataAccessGateway:
                 "sensorType",
                 "keywords",
                 "md5",
+                "abstract",
+                "title",
             )
             if kwargs.get(param, None) is not None
         }
@@ -941,19 +959,35 @@ class EODataAccessGateway:
             raise EodagError("Missing product types index")
         with self._product_types_index.searcher() as searcher:
             results = None
-            # For each search key, do a guess and then upgrade the result (i.e. when
-            # merging results, if a hit appears in both results, its position is raised
-            # to the top. This way, the top most result will be the hit that best
+            # Using `upgrade_and_extend`, for each search key, do a guess and
+            # then upgrade the result (i.e. when merging results,
+            # if a hit appears in both results, its position is raised
+            # to the top). This way, the top most result will be the hit that best
             # matches the given queries. Put another way, this best guess is the one
             # that crosses the highest number of search params from the given queries
+
+            # Always use UNION to join free_text_search results
+            for search_key in free_text_search_params:
+                query = QueryParser(search_key, self._product_types_index.schema).parse(
+                    free_text_filter
+                )
+                if results is None:
+                    results = searcher.search(query, limit=None)
+                else:
+                    results.upgrade_and_extend(searcher.search(query, limit=None))
+
+            # join results from kwargs using UNION or INTERSECT
             for search_key in supported_params:
                 query = QueryParser(search_key, self._product_types_index.schema).parse(
                     kwargs[search_key]
                 )
                 if results is None:
                     results = searcher.search(query, limit=None)
+                elif intersect:
+                    results.filter(searcher.search(query, limit=None))
                 else:
                     results.upgrade_and_extend(searcher.search(query, limit=None))
+
             guesses: List[str] = [r["ID"] for r in results or []]
         if guesses:
             return guesses
diff --git a/eodag/resources/stac.yml b/eodag/resources/stac.yml
index 1c408e43..bf9765e9 100644
--- a/eodag/resources/stac.yml
+++ b/eodag/resources/stac.yml
@@ -62,6 +62,7 @@ conformance:
     - https://api.stacspec.org/v1.0.0/ogcapi-features#query
     - https://api.stacspec.org/v1.0.0/ogcapi-features#sort
     - https://api.stacspec.org/v1.0.0/collections
+    - https://api.stacspec.org/v1.0.0/collection-search#free-text
     - http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core
     - http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30
     - http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson
diff --git a/eodag/resources/stac_api.yml b/eodag/resources/stac_api.yml
index 112ad479..5b37bd90 100644
--- a/eodag/resources/stac_api.yml
+++ b/eodag/resources/stac_api.yml
@@ -174,9 +174,12 @@ paths:
       operationId: getCollections
       parameters:
         - $ref: '#/components/parameters/provider'
+        - $ref: '#/components/parameters/q'
       responses:
         '200':
           $ref: '#/components/responses/Collections'
+        '202':
+          $ref: '#/components/responses/Accepted'
         '500':
           $ref: '#/components/responses/ServerError'
   /collections/{collectionId}:
@@ -1913,6 +1916,12 @@ components:
         text/html:
           schema:
             type: string
+    Accepted:
+      description: The request has been accepted, but the data is not yet ready. Please wait a few minutes before trying again.
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/exception'
     Collections:
       description: >-
         The feature collections shared by this API.
diff --git a/eodag/rest/stac.py b/eodag/rest/stac.py
index de6d76c1..fef6fb09 100644
--- a/eodag/rest/stac.py
+++ b/eodag/rest/stac.py
@@ -637,10 +637,29 @@ class StacCollection(StacCommon):
         """
         if filters is None:
             filters = {}
+        free_text_filter = filters.pop("q", None)
+
+        # product types matching filters
         try:
-            guessed_product_types = self.eodag_api.guess_product_type(**filters)
+            guessed_product_types = (
+                self.eodag_api.guess_product_type(**filters) if filters else []
+            )
         except NoMatchingProductType:
             guessed_product_types = []
+
+        # product types matching free text filter
+        if free_text_filter and not guessed_product_types:
+            whooshable_filter = " OR ".join(
+                [f"({x})" for x in free_text_filter.split(",")]
+            )
+            try:
+                guessed_product_types = self.eodag_api.guess_product_type(
+                    whooshable_filter
+                )
+            except NoMatchingProductType:
+                guessed_product_types = []
+
+        # list product types with all metadata using guessed ids
         if guessed_product_types:
             product_types = [
                 pt


Test patch:
diff --git a/tests/resources/ext_product_types_free_text_search.json b/tests/resources/ext_product_types_free_text_search.json
new file mode 100644
index 00000000..498bf442
--- /dev/null
+++ b/tests/resources/ext_product_types_free_text_search.json
@@ -0,0 +1,59 @@
+{
+  "astraea_eod": {
+    "providers_config": {
+      "foo": {
+        "productType": "foo",
+        "metadata_mapping": {
+          "cloudCover": "$.null"
+        }
+      },
+      "bar": {
+        "productType": "bar",
+        "metadata_mapping": {
+          "cloudCover": "$.null"
+        }
+      },
+      "foobar": {
+        "productType": "foobar",
+        "metadata_mapping": {
+          "cloudCover": "$.null"
+        }
+      }
+    },
+    "product_types_config": {
+      "foo": {
+        "abstract": "abstractFOO - This is FOO. FooAndBar",
+        "instrument": "Not Available",
+        "platform": "Not Available",
+        "platformSerialIdentifier": "Not Available",
+        "processingLevel": "Not Available",
+        "keywords": "suspendisse",
+        "license": "WTFPL",
+        "title": "titleFOO - Lorem FOO collection",
+        "missionStartDate": "2012-12-12T00:00:00.000Z"
+      },
+      "bar": {
+        "abstract": "abstractBAR - This is BAR",
+        "instrument": "Not Available",
+        "platform": "Not Available",
+        "platformSerialIdentifier": "Not Available",
+        "processingLevel": "Not Available",
+        "keywords": "lectus,lectus_bar_key",
+        "license": "WTFPL",
+        "title": "titleBAR - Lorem BAR collection (FooAndBar)",
+        "missionStartDate": "2012-12-12T00:00:00.000Z"
+      },
+      "foobar": {
+        "abstract": "abstract FOOBAR - This is FOOBAR",
+        "instrument": "Not Available",
+        "platform": "Not Available",
+        "platformSerialIdentifier": "Not Available",
+        "processingLevel": "Not Available",
+        "keywords": "tortor",
+        "license": "WTFPL",
+        "title": "titleFOOBAR - Lorem FOOBAR collection",
+        "missionStartDate": "2012-12-12T00:00:00.000Z"
+      }
+    }
+  }
+}
diff --git a/tests/units/test_core.py b/tests/units/test_core.py
index 29ab7b81..b6eaa26f 100644
--- a/tests/units/test_core.py
+++ b/tests/units/test_core.py
@@ -517,6 +517,53 @@ class TestCore(TestCoreBase):
         self.dag.list_product_types(provider="peps", fetch_providers=True)
         mock_fetch_product_types_list.assert_called_once_with(self.dag, provider="peps")
 
+    def test_guess_product_type_with_filter(self):
+        """Testing the search terms"""
+
+        with open(
+            os.path.join(TEST_RESOURCES_PATH, "ext_product_types_free_text_search.json")
+        ) as f:
+            ext_product_types_conf = json.load(f)
+        self.dag.update_product_types_list(ext_product_types_conf)
+
+        # Free text search: match in the abstract
+        filter = "ABSTRACTFOO"
+        product_types_ids = self.dag.guess_product_type(filter)
+        self.assertListEqual(product_types_ids, ["foo"])
+        filter = "(ABSTRACTFOO)"
+        product_types_ids = self.dag.guess_product_type(filter)
+        self.assertListEqual(product_types_ids, ["foo"])
+        filter = " FOO  THIS  IS "
+        product_types_ids = self.dag.guess_product_type(filter)
+        self.assertListEqual(product_types_ids, ["foo"])
+
+        # Free text search: match in the keywords
+        filter = "LECTUS_BAR_KEY"
+        product_types_ids = self.dag.guess_product_type(filter)
+        self.assertListEqual(product_types_ids, ["bar"])
+
+        # Free text search: match in the title
+        filter = "COLLECTION FOOBAR"
+        product_types_ids = self.dag.guess_product_type(filter)
+        self.assertListEqual(product_types_ids, ["foobar"])
+
+        # Free text search: multiple terms
+        filter = "(This is FOOBAR) OR (This is BAR)"
+        product_types_ids = self.dag.guess_product_type(filter)
+        self.assertListEqual(sorted(product_types_ids), ["bar", "foobar"])
+
+        # Free text search: multiple terms joined with param search (UNION)
+        filter = "(This is FOOBAR) OR (This is BAR)"
+        product_types_ids = self.dag.guess_product_type(filter, title="FOO*")
+        self.assertListEqual(sorted(product_types_ids), ["bar", "foo", "foobar"])
+
+        # Free text search: multiple terms joined with param search (INTERSECT)
+        filter = "(This is FOOBAR) OR (This is BAR)"
+        product_types_ids = self.dag.guess_product_type(
+            filter, intersect=True, title="titleFOO*"
+        )
+        self.assertListEqual(sorted(product_types_ids), ["foobar"])
+
     def test_update_product_types_list(self):
         """Core api.update_product_types_list must update eodag product types list"""
         with open(os.path.join(TEST_RESOURCES_PATH, "ext_product_types.json")) as f:
diff --git a/tests/units/test_http_server.py b/tests/units/test_http_server.py
index db6d8f6d..82b33732 100644
--- a/tests/units/test_http_server.py
+++ b/tests/units/test_http_server.py
@@ -964,7 +964,6 @@ class RequestTestCase(unittest.TestCase):
         """A simple request for product types with(out) a provider must succeed"""
         for url in ("/collections",):
             r = self.app.get(url)
-            self.assertTrue(guess_pt.called)
             self.assertTrue(list_pt.called)
             self.assertEqual(200, r.status_code)
             self.assertListEqual(
@@ -1377,3 +1376,14 @@ class RequestTestCase(unittest.TestCase):
                 }
             },
         )
+
+    @mock.patch("eodag.rest.core.eodag_api.list_product_types", autospec=True)
+    @mock.patch("eodag.rest.core.eodag_api.guess_product_type", autospec=True)
+    def test_collection_free_text_search(self, guess_pt: Mock, list_pt: Mock):
+        """Test STAC Collection free-text search"""
+
+        url = "/collections?q=TERM1,TERM2"
+        r = self.app.get(url)
+        list_pt.assert_called_once_with(provider=None)
+        guess_pt.assert_called_once_with("(TERM1) OR (TERM2)")
+        self.assertEqual(200, r.status_code)