mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-12-24 05:30:16 +01:00
Merge #3339
3339: Continued deserr integration r=irevoire a=loiclec
Fix https://github.com/meilisearch/meilisearch/issues/3337
Fix https://github.com/meilisearch/meilisearch/issues/3338
1. Add new error codes that should have been implemented earlier:
- `MissingApiKeyActions`
- `MissingApiKeyExpiresAt`
- `MissingApiKeyIndexes`
- `MissingSwapIndexes`
2. Fix a bug where it was possible to create an API key without specifying the value of `expiresAt`
3. Improve the error messages generated by deserr. Have specific error messages for JSON and QueryParam deserialisation errors.
4. Improve error tests by passing query params as arguments to `GET` routes directly instead of using an intermediary JSON object
5. [Use invalid_index_uid error code in more places](e225608337
)
Co-authored-by: Loïc Lecrenier <loic.lecrenier@me.com>
This commit is contained in:
commit
41a970247e
329
Cargo.lock
generated
329
Cargo.lock
generated
@ -77,8 +77,8 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6"
|
||||
dependencies = [
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -211,9 +211,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa9362663c8643d67b2d5eafba49e4cb2c8a053a29ed00a0bea121f17c76b13"
|
||||
dependencies = [
|
||||
"actix-router",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -324,9 +324,9 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -335,9 +335,9 @@ version = "0.1.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -423,21 +423,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@ -541,9 +526,9 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fe233b960f12f8007e3db2d136e3cb1c291bfd7396e384ee76025fc1a3932b4"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -702,9 +687,9 @@ checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -715,9 +700,9 @@ checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -744,9 +729,9 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1df715824eb382e34b7afb7463b0247bf41538aeba731fba05241ecdb5dc3747"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -961,10 +946,10 @@ checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 1.0.107",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -974,8 +959,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -994,9 +979,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1006,7 +991,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn 1.0.107",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1016,17 +1001,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||
dependencies = [
|
||||
"convert_case 0.4.0",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn 1.0.107",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deserr"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86290491a2b5c21a1a5083da8dae831006761258fabd5617309c3eebc5f89468"
|
||||
checksum = "5d3c6417f0bf7561774690e3d47f9659b0cbc3614c7af7bfda404fda7a2c11d3"
|
||||
dependencies = [
|
||||
"deserr-internal",
|
||||
"serde-cs",
|
||||
@ -1035,14 +1020,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deserr-internal"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7131de1c27581bc376a22166c9f570be91b76cb096be2f6aecf224c27bf7c49a"
|
||||
checksum = "196415cbd3b782cddecbdd69da18cd9b19e1bb0bdbb649e87b5afd83fa8d322b"
|
||||
dependencies = [
|
||||
"convert_case 0.5.0",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1220,9 +1205,9 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1285,9 +1270,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35c9bb4a2c13ffb3a93a39902aaf4e7190a1706a4779b6db0449aee433d26c4a"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"uuid 0.8.2",
|
||||
]
|
||||
|
||||
@ -1315,8 +1300,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "filter-parser"
|
||||
version = "0.39.0"
|
||||
source = "git+https://github.com/meilisearch/milli.git?tag=v0.39.0#e6bea999740b153871f665abce869ffbb5aa94c5"
|
||||
version = "0.39.1"
|
||||
source = "git+https://github.com/meilisearch/milli.git?tag=v0.39.1#0c7d1f761e5db6d086f27d3f0f47a97c7f4a5f08"
|
||||
dependencies = [
|
||||
"nom",
|
||||
"nom_locate",
|
||||
@ -1334,8 +1319,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flatten-serde-json"
|
||||
version = "0.39.0"
|
||||
source = "git+https://github.com/meilisearch/milli.git?tag=v0.39.0#e6bea999740b153871f665abce869ffbb5aa94c5"
|
||||
version = "0.39.1"
|
||||
source = "git+https://github.com/meilisearch/milli.git?tag=v0.39.1#0c7d1f761e5db6d086f27d3f0f47a97c7f4a5f08"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
@ -1415,9 +1400,9 @@ version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1493,9 +1478,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1899,8 +1884,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "json-depth-checker"
|
||||
version = "0.39.0"
|
||||
source = "git+https://github.com/meilisearch/milli.git?tag=v0.39.0#e6bea999740b153871f665abce869ffbb5aa94c5"
|
||||
version = "0.39.1"
|
||||
source = "git+https://github.com/meilisearch/milli.git?tag=v0.39.1#0c7d1f761e5db6d086f27d3f0f47a97c7f4a5f08"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
@ -2238,9 +2223,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a9062912d7952c5588cc474795e0b9ee008e7e6781127945b85413d4b99d81"
|
||||
dependencies = [
|
||||
"log",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2260,9 +2245,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f08150cf2bab1fc47c2196f4f41173a27fcd0f684165e5458c0046b53a472e2f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2348,7 +2333,6 @@ dependencies = [
|
||||
"rustls-pemfile",
|
||||
"segment",
|
||||
"serde",
|
||||
"serde-cs",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sha-1",
|
||||
@ -2409,10 +2393,9 @@ dependencies = [
|
||||
"meili-snap",
|
||||
"memmap2",
|
||||
"milli",
|
||||
"proptest",
|
||||
"proptest-derive",
|
||||
"roaring",
|
||||
"serde",
|
||||
"serde-cs",
|
||||
"serde_json",
|
||||
"tar",
|
||||
"tempfile",
|
||||
@ -2448,8 +2431,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "milli"
|
||||
version = "0.39.0"
|
||||
source = "git+https://github.com/meilisearch/milli.git?tag=v0.39.0#e6bea999740b153871f665abce869ffbb5aa94c5"
|
||||
version = "0.39.1"
|
||||
source = "git+https://github.com/meilisearch/milli.git?tag=v0.39.1#0c7d1f761e5db6d086f27d3f0f47a97c7f4a5f08"
|
||||
dependencies = [
|
||||
"bimap",
|
||||
"bincode",
|
||||
@ -2800,9 +2783,9 @@ checksum = "46b53634d8c8196302953c74d5352f33d0c512a9499bd2ce468fc9f4128fa27c"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2900,9 +2883,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
@ -2912,20 +2895,11 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
|
||||
dependencies = [
|
||||
"unicode-xid 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.49"
|
||||
@ -2965,71 +2939,19 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"quick-error 2.0.1",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"rand_xorshift",
|
||||
"regex-syntax",
|
||||
"rusty-fork",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest-derive"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90b46295382dc76166cb7cf2bb4a97952464e4b7ed5a43e6cd34e1fec3349ddc"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30",
|
||||
"quote 0.6.13",
|
||||
"syn 0.15.44",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "2.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3062,15 +2984,6 @@ dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.6.1"
|
||||
@ -3285,18 +3198,6 @@ version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
|
||||
|
||||
[[package]]
|
||||
name = "rusty-fork"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"quick-error 1.2.3",
|
||||
"tempfile",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.12"
|
||||
@ -3372,9 +3273,9 @@ version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3569,25 +3470,14 @@ version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30",
|
||||
"quote 0.6.13",
|
||||
"unicode-xid 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@ -3606,10 +3496,10 @@ version = "0.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"unicode-xid 0.2.4",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3691,9 +3581,9 @@ version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3764,9 +3654,9 @@ version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3895,12 +3785,6 @@ version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
@ -3983,15 +3867,6 @@ version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.2"
|
||||
@ -4038,9 +3913,9 @@ dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -4062,7 +3937,7 @@ version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
dependencies = [
|
||||
"quote 1.0.23",
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
@ -4072,9 +3947,9 @@ version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -4271,8 +4146,8 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.49",
|
||||
"syn 1.0.107",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
|
@ -5,10 +5,8 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "test-traits", derive(proptest_derive::Arbitrary))]
|
||||
pub struct ResponseError {
|
||||
#[serde(skip)]
|
||||
#[cfg_attr(feature = "test-traits", proptest(strategy = "strategy::status_code_strategy()"))]
|
||||
pub code: StatusCode,
|
||||
pub message: String,
|
||||
#[serde(rename = "code")]
|
||||
|
@ -5,7 +5,6 @@ use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "test-traits", derive(proptest_derive::Arbitrary))]
|
||||
#[cfg_attr(test, derive(serde::Serialize))]
|
||||
pub struct ResponseError {
|
||||
#[serde(skip)]
|
||||
|
@ -3,7 +3,6 @@ pub mod error;
|
||||
mod store;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -86,15 +85,13 @@ impl AuthController {
|
||||
key.indexes
|
||||
.into_iter()
|
||||
.filter_map(|index| {
|
||||
search_rules.get_index_search_rules(index.deref()).map(
|
||||
|index_search_rules| {
|
||||
(String::from(index), Some(index_search_rules))
|
||||
},
|
||||
search_rules.get_index_search_rules(&format!("{index}")).map(
|
||||
|index_search_rules| (index.to_string(), Some(index_search_rules)),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
None => SearchRules::Set(key.indexes.into_iter().map(String::from).collect()),
|
||||
None => SearchRules::Set(key.indexes.into_iter().map(|x| x.to_string()).collect()),
|
||||
};
|
||||
} else if let Some(search_rules) = search_rules {
|
||||
filters.search_rules = search_rules;
|
||||
|
@ -3,7 +3,6 @@ use std::cmp::Reverse;
|
||||
use std::collections::HashSet;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fs::create_dir_all;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
@ -135,7 +134,7 @@ impl HeedAuthStore {
|
||||
for index in key.indexes.iter() {
|
||||
db.put(
|
||||
&mut wtxn,
|
||||
&(&uid, &action, Some(index.deref().as_bytes())),
|
||||
&(&uid, &action, Some(index.to_string().as_bytes())),
|
||||
&key.expires_at,
|
||||
)?;
|
||||
}
|
||||
|
@ -9,18 +9,17 @@ actix-web = { version = "4.2.1", default-features = false }
|
||||
anyhow = "1.0.65"
|
||||
convert_case = "0.6.0"
|
||||
csv = "1.1.6"
|
||||
deserr = "0.1.4"
|
||||
deserr = "0.1.5"
|
||||
either = { version = "1.6.1", features = ["serde"] }
|
||||
enum-iterator = "1.1.3"
|
||||
file-store = { path = "../file-store" }
|
||||
flate2 = "1.0.24"
|
||||
fst = "0.4.7"
|
||||
memmap2 = "0.5.7"
|
||||
milli = { git = "https://github.com/meilisearch/milli.git", tag = "v0.39.0", default-features = false }
|
||||
proptest = { version = "1.0.0", optional = true }
|
||||
proptest-derive = { version = "0.3.0", optional = true }
|
||||
milli = { git = "https://github.com/meilisearch/milli.git", tag = "v0.39.1", default-features = false }
|
||||
roaring = { version = "0.10.0", features = ["serde"] }
|
||||
serde = { version = "1.0.145", features = ["derive"] }
|
||||
serde-cs = "0.2.4"
|
||||
serde_json = "1.0.85"
|
||||
tar = "0.4.38"
|
||||
tempfile = "3.3.0"
|
||||
@ -32,8 +31,6 @@ uuid = { version = "1.1.2", features = ["serde", "v4"] }
|
||||
[dev-dependencies]
|
||||
insta = "1.19.1"
|
||||
meili-snap = { path = "../meili-snap" }
|
||||
proptest = "1.0.0"
|
||||
proptest-derive = "0.3.0"
|
||||
|
||||
[features]
|
||||
# all specialized tokenizations
|
||||
@ -47,4 +44,3 @@ hebrew = ["milli/hebrew"]
|
||||
japanese = ["milli/japanese"]
|
||||
# thai specialized tokenization
|
||||
thai = ["milli/thai"]
|
||||
test-traits = ["proptest", "proptest-derive"]
|
||||
|
315
meilisearch-types/src/deserr/error_messages.rs
Normal file
315
meilisearch-types/src/deserr/error_messages.rs
Normal file
@ -0,0 +1,315 @@
|
||||
/*!
|
||||
This module implements the error messages of deserialization errors.
|
||||
|
||||
We try to:
|
||||
1. Give a human-readable description of where the error originated.
|
||||
2. Use the correct terms depending on the format of the request (json/query param)
|
||||
3. Categorise the type of the error (e.g. missing field, wrong value type, unexpected error, etc.)
|
||||
*/
|
||||
use deserr::{ErrorKind, IntoValue, ValueKind, ValuePointerRef};
|
||||
|
||||
use super::{DeserrJsonError, DeserrQueryParamError};
|
||||
use crate::error::ErrorCode;
|
||||
|
||||
/// Return a description of the given location in a Json, preceded by the given article.
|
||||
/// e.g. `at .key1[8].key2`. If the location is the origin, the given article will not be
|
||||
/// included in the description.
|
||||
pub fn location_json_description(location: ValuePointerRef, article: &str) -> String {
|
||||
fn rec(location: ValuePointerRef) -> String {
|
||||
match location {
|
||||
ValuePointerRef::Origin => String::new(),
|
||||
ValuePointerRef::Key { key, prev } => rec(*prev) + "." + key,
|
||||
ValuePointerRef::Index { index, prev } => format!("{}[{index}]", rec(*prev)),
|
||||
}
|
||||
}
|
||||
match location {
|
||||
ValuePointerRef::Origin => String::new(),
|
||||
_ => {
|
||||
format!("{article} `{}`", rec(location))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a description of the list of value kinds for a Json payload.
|
||||
fn value_kinds_description_json(kinds: &[ValueKind]) -> String {
|
||||
// Rank each value kind so that they can be sorted (and deduplicated)
|
||||
// Having a predictable order helps with pattern matching
|
||||
fn order(kind: &ValueKind) -> u8 {
|
||||
match kind {
|
||||
ValueKind::Null => 0,
|
||||
ValueKind::Boolean => 1,
|
||||
ValueKind::Integer => 2,
|
||||
ValueKind::NegativeInteger => 3,
|
||||
ValueKind::Float => 4,
|
||||
ValueKind::String => 5,
|
||||
ValueKind::Sequence => 6,
|
||||
ValueKind::Map => 7,
|
||||
}
|
||||
}
|
||||
// Return a description of a single value kind, preceded by an article
|
||||
fn single_description(kind: &ValueKind) -> &'static str {
|
||||
match kind {
|
||||
ValueKind::Null => "null",
|
||||
ValueKind::Boolean => "a boolean",
|
||||
ValueKind::Integer => "a positive integer",
|
||||
ValueKind::NegativeInteger => "an integer",
|
||||
ValueKind::Float => "a number",
|
||||
ValueKind::String => "a string",
|
||||
ValueKind::Sequence => "an array",
|
||||
ValueKind::Map => "an object",
|
||||
}
|
||||
}
|
||||
|
||||
fn description_rec(kinds: &[ValueKind], count_items: &mut usize, message: &mut String) {
|
||||
let (msg_part, rest): (_, &[ValueKind]) = match kinds {
|
||||
[] => (String::new(), &[]),
|
||||
[ValueKind::Integer | ValueKind::NegativeInteger, ValueKind::Float, rest @ ..] => {
|
||||
("a number".to_owned(), rest)
|
||||
}
|
||||
[ValueKind::Integer, ValueKind::NegativeInteger, ValueKind::Float, rest @ ..] => {
|
||||
("a number".to_owned(), rest)
|
||||
}
|
||||
[ValueKind::Integer, ValueKind::NegativeInteger, rest @ ..] => {
|
||||
("an integer".to_owned(), rest)
|
||||
}
|
||||
[a] => (single_description(a).to_owned(), &[]),
|
||||
[a, rest @ ..] => (single_description(a).to_owned(), rest),
|
||||
};
|
||||
|
||||
if rest.is_empty() {
|
||||
if *count_items == 0 {
|
||||
message.push_str(&msg_part);
|
||||
} else if *count_items == 1 {
|
||||
message.push_str(&format!(" or {msg_part}"));
|
||||
} else {
|
||||
message.push_str(&format!(", or {msg_part}"));
|
||||
}
|
||||
} else {
|
||||
if *count_items == 0 {
|
||||
message.push_str(&msg_part);
|
||||
} else {
|
||||
message.push_str(&format!(", {msg_part}"));
|
||||
}
|
||||
|
||||
*count_items += 1;
|
||||
description_rec(rest, count_items, message);
|
||||
}
|
||||
}
|
||||
|
||||
let mut kinds = kinds.to_owned();
|
||||
kinds.sort_by_key(order);
|
||||
kinds.dedup();
|
||||
|
||||
if kinds.is_empty() {
|
||||
// Should not happen ideally
|
||||
"a different value".to_owned()
|
||||
} else {
|
||||
let mut message = String::new();
|
||||
description_rec(kinds.as_slice(), &mut 0, &mut message);
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the JSON string of the value preceded by a description of its kind
|
||||
fn value_description_with_kind_json(v: &serde_json::Value) -> String {
|
||||
match v.kind() {
|
||||
ValueKind::Null => "null".to_owned(),
|
||||
kind => {
|
||||
format!(
|
||||
"{}: `{}`",
|
||||
value_kinds_description_json(&[kind]),
|
||||
serde_json::to_string(v).unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Default + ErrorCode> deserr::DeserializeError for DeserrJsonError<C> {
|
||||
fn error<V: IntoValue>(
|
||||
_self_: Option<Self>,
|
||||
error: deserr::ErrorKind<V>,
|
||||
location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
let mut message = String::new();
|
||||
|
||||
message.push_str(&match error {
|
||||
ErrorKind::IncorrectValueKind { actual, accepted } => {
|
||||
let expected = value_kinds_description_json(accepted);
|
||||
let received = value_description_with_kind_json(&serde_json::Value::from(actual));
|
||||
|
||||
let location = location_json_description(location, " at");
|
||||
|
||||
format!("Invalid value type{location}: expected {expected}, but found {received}")
|
||||
}
|
||||
ErrorKind::MissingField { field } => {
|
||||
let location = location_json_description(location, " inside");
|
||||
format!("Missing field `{field}`{location}")
|
||||
}
|
||||
ErrorKind::UnknownKey { key, accepted } => {
|
||||
let location = location_json_description(location, " inside");
|
||||
format!(
|
||||
"Unknown field `{}`{location}: expected one of {}",
|
||||
key,
|
||||
accepted
|
||||
.iter()
|
||||
.map(|accepted| format!("`{}`", accepted))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
ErrorKind::UnknownValue { value, accepted } => {
|
||||
let location = location_json_description(location, " at");
|
||||
format!(
|
||||
"Unknown value `{}`{location}: expected one of {}",
|
||||
value,
|
||||
accepted
|
||||
.iter()
|
||||
.map(|accepted| format!("`{}`", accepted))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", "),
|
||||
)
|
||||
}
|
||||
ErrorKind::Unexpected { msg } => {
|
||||
let location = location_json_description(location, " at");
|
||||
format!("Invalid value{location}: {msg}")
|
||||
}
|
||||
});
|
||||
|
||||
Err(DeserrJsonError::new(message, C::default().error_code()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a description of the given location in query parameters, preceded by the
|
||||
/// given article. e.g. `at key5[2]`. If the location is the origin, the given article
|
||||
/// will not be included in the description.
|
||||
pub fn location_query_param_description(location: ValuePointerRef, article: &str) -> String {
|
||||
fn rec(location: ValuePointerRef) -> String {
|
||||
match location {
|
||||
ValuePointerRef::Origin => String::new(),
|
||||
ValuePointerRef::Key { key, prev } => {
|
||||
if matches!(prev, ValuePointerRef::Origin) {
|
||||
key.to_owned()
|
||||
} else {
|
||||
rec(*prev) + "." + key
|
||||
}
|
||||
}
|
||||
ValuePointerRef::Index { index, prev } => format!("{}[{index}]", rec(*prev)),
|
||||
}
|
||||
}
|
||||
match location {
|
||||
ValuePointerRef::Origin => String::new(),
|
||||
_ => {
|
||||
format!("{article} `{}`", rec(location))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Default + ErrorCode> deserr::DeserializeError for DeserrQueryParamError<C> {
|
||||
fn error<V: IntoValue>(
|
||||
_self_: Option<Self>,
|
||||
error: deserr::ErrorKind<V>,
|
||||
location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
let mut message = String::new();
|
||||
|
||||
message.push_str(&match error {
|
||||
ErrorKind::IncorrectValueKind { actual, accepted } => {
|
||||
let expected = value_kinds_description_query_param(accepted);
|
||||
let received = value_description_with_kind_query_param(actual);
|
||||
|
||||
let location = location_query_param_description(location, " for parameter");
|
||||
|
||||
format!("Invalid value type{location}: expected {expected}, but found {received}")
|
||||
}
|
||||
ErrorKind::MissingField { field } => {
|
||||
let location = location_query_param_description(location, " inside");
|
||||
format!("Missing parameter `{field}`{location}")
|
||||
}
|
||||
ErrorKind::UnknownKey { key, accepted } => {
|
||||
let location = location_query_param_description(location, " inside");
|
||||
format!(
|
||||
"Unknown parameter `{}`{location}: expected one of {}",
|
||||
key,
|
||||
accepted
|
||||
.iter()
|
||||
.map(|accepted| format!("`{}`", accepted))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
ErrorKind::UnknownValue { value, accepted } => {
|
||||
let location = location_query_param_description(location, " for parameter");
|
||||
format!(
|
||||
"Unknown value `{}`{location}: expected one of {}",
|
||||
value,
|
||||
accepted
|
||||
.iter()
|
||||
.map(|accepted| format!("`{}`", accepted))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", "),
|
||||
)
|
||||
}
|
||||
ErrorKind::Unexpected { msg } => {
|
||||
let location = location_query_param_description(location, " in parameter");
|
||||
format!("Invalid value{location}: {msg}")
|
||||
}
|
||||
});
|
||||
|
||||
Err(DeserrQueryParamError::new(message, C::default().error_code()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a description of the list of value kinds for query parameters
|
||||
/// Since query parameters are always treated as strings, we always return
|
||||
/// "a string" for now.
|
||||
fn value_kinds_description_query_param(_accepted: &[ValueKind]) -> String {
|
||||
"a string".to_owned()
|
||||
}
|
||||
|
||||
fn value_description_with_kind_query_param<V: IntoValue>(actual: deserr::Value<V>) -> String {
|
||||
match actual {
|
||||
deserr::Value::Null => "null".to_owned(),
|
||||
deserr::Value::Boolean(x) => format!("a boolean: `{x}`"),
|
||||
deserr::Value::Integer(x) => format!("an integer: `{x}`"),
|
||||
deserr::Value::NegativeInteger(x) => {
|
||||
format!("an integer: `{x}`")
|
||||
}
|
||||
deserr::Value::Float(x) => {
|
||||
format!("a number: `{x}`")
|
||||
}
|
||||
deserr::Value::String(x) => {
|
||||
format!("a string: `{x}`")
|
||||
}
|
||||
deserr::Value::Sequence(_) => "multiple values".to_owned(),
|
||||
deserr::Value::Map(_) => "multiple parameters".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use deserr::ValueKind;
|
||||
|
||||
use crate::deserr::error_messages::value_kinds_description_json;
|
||||
|
||||
#[test]
|
||||
fn test_value_kinds_description_json() {
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[]), @"a different value");
|
||||
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Boolean]), @"a boolean");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer]), @"a positive integer");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::NegativeInteger]), @"an integer");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer]), @"a positive integer");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::String]), @"a string");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Sequence]), @"an array");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Map]), @"an object");
|
||||
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer, ValueKind::Boolean]), @"a boolean or a positive integer");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Null, ValueKind::Integer]), @"null or a positive integer");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Sequence, ValueKind::NegativeInteger]), @"an integer or an array");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer, ValueKind::Float]), @"a number");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer, ValueKind::Float, ValueKind::NegativeInteger]), @"a number");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Integer, ValueKind::Float, ValueKind::NegativeInteger, ValueKind::Null]), @"null or a number");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Boolean, ValueKind::Integer, ValueKind::Float, ValueKind::NegativeInteger, ValueKind::Null]), @"null, a boolean, or a number");
|
||||
insta::assert_display_snapshot!(value_kinds_description_json(&[ValueKind::Null, ValueKind::Boolean, ValueKind::Integer, ValueKind::Float, ValueKind::NegativeInteger, ValueKind::Null]), @"null, a boolean, or a number");
|
||||
}
|
||||
}
|
134
meilisearch-types/src/deserr/mod.rs
Normal file
134
meilisearch-types/src/deserr/mod.rs
Normal file
@ -0,0 +1,134 @@
|
||||
use std::convert::Infallible;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use deserr::{DeserializeError, MergeWithError, ValuePointerRef};
|
||||
|
||||
use crate::error::deserr_codes::{self, *};
|
||||
use crate::error::{
|
||||
unwrap_any, Code, DeserrParseBoolError, DeserrParseIntError, ErrorCode, InvalidTaskDateError,
|
||||
ParseOffsetDateTimeError,
|
||||
};
|
||||
use crate::index_uid::IndexUidFormatError;
|
||||
use crate::tasks::{ParseTaskKindError, ParseTaskStatusError};
|
||||
|
||||
pub mod error_messages;
|
||||
pub mod query_params;
|
||||
|
||||
/// Marker type for the Json format
|
||||
pub struct DeserrJson;
|
||||
/// Marker type for the Query Parameter format
|
||||
pub struct DeserrQueryParam;
|
||||
|
||||
pub type DeserrJsonError<C = deserr_codes::BadRequest> = DeserrError<DeserrJson, C>;
|
||||
pub type DeserrQueryParamError<C = deserr_codes::BadRequest> = DeserrError<DeserrQueryParam, C>;
|
||||
|
||||
/// A request deserialization error.
|
||||
///
|
||||
/// The first generic paramater is a marker type describing the format of the request: either json (e.g. [`DeserrJson`] or [`DeserrQueryParam`]).
|
||||
/// The second generic parameter is the default error code for the deserialization error, in case it is not given.
|
||||
pub struct DeserrError<Format, C: Default + ErrorCode> {
|
||||
pub msg: String,
|
||||
pub code: Code,
|
||||
_phantom: PhantomData<(Format, C)>,
|
||||
}
|
||||
impl<Format, C: Default + ErrorCode> DeserrError<Format, C> {
|
||||
pub fn new(msg: String, code: Code) -> Self {
|
||||
Self { msg, code, _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
impl<Format, C: Default + ErrorCode> std::fmt::Debug for DeserrError<Format, C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DeserrError").field("msg", &self.msg).field("code", &self.code).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Format, C: Default + ErrorCode> std::fmt::Display for DeserrError<Format, C> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Format, C: Default + ErrorCode> std::error::Error for DeserrError<Format, C> {}
|
||||
impl<Format, C: Default + ErrorCode> ErrorCode for DeserrError<Format, C> {
|
||||
fn error_code(&self) -> Code {
|
||||
self.code
|
||||
}
|
||||
}
|
||||
|
||||
// For now, we don't accumulate errors. Only one deserialisation error is ever returned at a time.
|
||||
impl<Format, C1: Default + ErrorCode, C2: Default + ErrorCode>
|
||||
MergeWithError<DeserrError<Format, C2>> for DeserrError<Format, C1>
|
||||
{
|
||||
fn merge(
|
||||
_self_: Option<Self>,
|
||||
other: DeserrError<Format, C2>,
|
||||
_merge_location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
Err(DeserrError { msg: other.msg, code: other.code, _phantom: PhantomData })
|
||||
}
|
||||
}
|
||||
|
||||
impl<Format, C: Default + ErrorCode> MergeWithError<Infallible> for DeserrError<Format, C> {
|
||||
fn merge(
|
||||
_self_: Option<Self>,
|
||||
_other: Infallible,
|
||||
_merge_location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
// Implement a convenience function to build a `missing_field` error
|
||||
macro_rules! make_missing_field_convenience_builder {
|
||||
($err_code:ident, $fn_name:ident) => {
|
||||
impl DeserrJsonError<$err_code> {
|
||||
pub fn $fn_name(field: &str, location: ValuePointerRef) -> Self {
|
||||
let x = unwrap_any(Self::error::<Infallible>(
|
||||
None,
|
||||
deserr::ErrorKind::MissingField { field },
|
||||
location,
|
||||
));
|
||||
Self { msg: x.msg, code: $err_code.error_code(), _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
make_missing_field_convenience_builder!(MissingIndexUid, missing_index_uid);
|
||||
make_missing_field_convenience_builder!(MissingApiKeyActions, missing_api_key_actions);
|
||||
make_missing_field_convenience_builder!(MissingApiKeyExpiresAt, missing_api_key_expires_at);
|
||||
make_missing_field_convenience_builder!(MissingApiKeyIndexes, missing_api_key_indexes);
|
||||
make_missing_field_convenience_builder!(MissingSwapIndexes, missing_swap_indexes);
|
||||
|
||||
// Integrate a sub-error into a [`DeserrError`] by taking its error message but using
|
||||
// the default error code (C) from `Self`
|
||||
macro_rules! merge_with_error_impl_take_error_message {
|
||||
($err_type:ty) => {
|
||||
impl<Format, C: Default + ErrorCode> MergeWithError<$err_type> for DeserrError<Format, C>
|
||||
where
|
||||
DeserrError<Format, C>: deserr::DeserializeError,
|
||||
{
|
||||
fn merge(
|
||||
_self_: Option<Self>,
|
||||
other: $err_type,
|
||||
merge_location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
DeserrError::<Format, C>::error::<Infallible>(
|
||||
None,
|
||||
deserr::ErrorKind::Unexpected { msg: other.to_string() },
|
||||
merge_location,
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// All these errors can be merged into a `DeserrError`
|
||||
merge_with_error_impl_take_error_message!(DeserrParseIntError);
|
||||
merge_with_error_impl_take_error_message!(DeserrParseBoolError);
|
||||
merge_with_error_impl_take_error_message!(uuid::Error);
|
||||
merge_with_error_impl_take_error_message!(InvalidTaskDateError);
|
||||
merge_with_error_impl_take_error_message!(ParseOffsetDateTimeError);
|
||||
merge_with_error_impl_take_error_message!(ParseTaskKindError);
|
||||
merge_with_error_impl_take_error_message!(ParseTaskStatusError);
|
||||
merge_with_error_impl_take_error_message!(IndexUidFormatError);
|
115
meilisearch-types/src/deserr/query_params.rs
Normal file
115
meilisearch-types/src/deserr/query_params.rs
Normal file
@ -0,0 +1,115 @@
|
||||
/*!
|
||||
This module provides helper traits, types, and functions to deserialize query parameters.
|
||||
|
||||
The source of the problem is that query parameters only give us a string to work with.
|
||||
This means `deserr` is never given a sequence or numbers, and thus the default deserialization
|
||||
code for common types such as `usize` or `Vec<T>` does not work. To work around it, we create a
|
||||
wrapper type called `Param<T>`, which is deserialised using the `from_query_param` method of the trait
|
||||
`FromQueryParameter`.
|
||||
|
||||
We also use other helper types such as `CS` (i.e. comma-separated) from `serde_cs` as well as
|
||||
`StarOr`, `OptionStarOr`, and `OptionStarOrList`.
|
||||
*/
|
||||
|
||||
use std::convert::Infallible;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
|
||||
use deserr::{DeserializeError, DeserializeFromValue, MergeWithError, ValueKind};
|
||||
|
||||
use super::{DeserrParseBoolError, DeserrParseIntError};
|
||||
use crate::error::unwrap_any;
|
||||
use crate::index_uid::IndexUid;
|
||||
use crate::tasks::{Kind, Status};
|
||||
|
||||
/// A wrapper type indicating that the inner value should be
|
||||
/// deserialised from a query parameter string.
|
||||
///
|
||||
/// Note that if the field is optional, it is better to use
|
||||
/// `Option<Param<T>>` instead of `Param<Option<T>>`.
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct Param<T>(pub T);
|
||||
|
||||
impl<T> Deref for Param<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> DeserializeFromValue<E> for Param<T>
|
||||
where
|
||||
E: DeserializeError + MergeWithError<T::Err>,
|
||||
T: FromQueryParameter,
|
||||
{
|
||||
fn deserialize_from_value<V: deserr::IntoValue>(
|
||||
value: deserr::Value<V>,
|
||||
location: deserr::ValuePointerRef,
|
||||
) -> Result<Self, E> {
|
||||
match value {
|
||||
deserr::Value::String(s) => match T::from_query_param(&s) {
|
||||
Ok(x) => Ok(Param(x)),
|
||||
Err(e) => Err(unwrap_any(E::merge(None, e, location))),
|
||||
},
|
||||
_ => Err(unwrap_any(E::error(
|
||||
None,
|
||||
deserr::ErrorKind::IncorrectValueKind {
|
||||
actual: value,
|
||||
accepted: &[ValueKind::String],
|
||||
},
|
||||
location,
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a value from a query parameter string.
|
||||
///
|
||||
/// This trait is functionally equivalent to `FromStr`.
|
||||
/// Having a separate trait trait allows us to return better
|
||||
/// deserializatio error messages.
|
||||
pub trait FromQueryParameter: Sized {
|
||||
type Err;
|
||||
fn from_query_param(p: &str) -> Result<Self, Self::Err>;
|
||||
}
|
||||
|
||||
/// Implement `FromQueryParameter` for the given type using its `FromStr`
|
||||
/// trait implementation.
|
||||
macro_rules! impl_from_query_param_from_str {
|
||||
($type:ty) => {
|
||||
impl FromQueryParameter for $type {
|
||||
type Err = <$type as FromStr>::Err;
|
||||
fn from_query_param(p: &str) -> Result<Self, Self::Err> {
|
||||
p.parse()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_from_query_param_from_str!(Kind);
|
||||
impl_from_query_param_from_str!(Status);
|
||||
impl_from_query_param_from_str!(IndexUid);
|
||||
|
||||
/// Implement `FromQueryParameter` for the given type using its `FromStr`
|
||||
/// trait implementation, replacing the returned error with a struct
|
||||
/// that wraps the original query parameter.
|
||||
macro_rules! impl_from_query_param_wrap_original_value_in_error {
|
||||
($type:ty, $err_type:path) => {
|
||||
impl FromQueryParameter for $type {
|
||||
type Err = $err_type;
|
||||
fn from_query_param(p: &str) -> Result<Self, Self::Err> {
|
||||
p.parse().map_err(|_| $err_type(p.to_owned()))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_from_query_param_wrap_original_value_in_error!(usize, DeserrParseIntError);
|
||||
impl_from_query_param_wrap_original_value_in_error!(u32, DeserrParseIntError);
|
||||
impl_from_query_param_wrap_original_value_in_error!(bool, DeserrParseBoolError);
|
||||
|
||||
impl FromQueryParameter for String {
|
||||
type Err = Infallible;
|
||||
fn from_query_param(p: &str) -> Result<Self, Infallible> {
|
||||
Ok(p.to_owned())
|
||||
}
|
||||
}
|
@ -1,23 +1,16 @@
|
||||
use std::convert::Infallible;
|
||||
use std::marker::PhantomData;
|
||||
use std::{fmt, io};
|
||||
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{self as aweb, HttpResponseBuilder};
|
||||
use aweb::rt::task::JoinError;
|
||||
use convert_case::Casing;
|
||||
use deserr::{DeserializeError, IntoValue, MergeWithError, ValuePointerRef};
|
||||
use milli::heed::{Error as HeedError, MdbError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use self::deserr_codes::MissingIndexUid;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "test-traits", derive(proptest_derive::Arbitrary))]
|
||||
pub struct ResponseError {
|
||||
#[serde(skip)]
|
||||
#[cfg_attr(feature = "test-traits", proptest(strategy = "strategy::status_code_strategy()"))]
|
||||
code: StatusCode,
|
||||
message: String,
|
||||
#[serde(rename = "code")]
|
||||
@ -36,7 +29,7 @@ impl ResponseError {
|
||||
Self {
|
||||
code: code.http(),
|
||||
message,
|
||||
error_code: code.err_code().error_name,
|
||||
error_code: code.name(),
|
||||
error_type: code.type_(),
|
||||
error_link: code.url(),
|
||||
}
|
||||
@ -97,9 +90,9 @@ pub trait ErrorCode {
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum ErrorType {
|
||||
InternalError,
|
||||
InvalidRequestError,
|
||||
AuthenticationError,
|
||||
Internal,
|
||||
InvalidRequest,
|
||||
Auth,
|
||||
System,
|
||||
}
|
||||
|
||||
@ -108,14 +101,24 @@ impl fmt::Display for ErrorType {
|
||||
use ErrorType::*;
|
||||
|
||||
match self {
|
||||
InternalError => write!(f, "internal"),
|
||||
InvalidRequestError => write!(f, "invalid_request"),
|
||||
AuthenticationError => write!(f, "auth"),
|
||||
Internal => write!(f, "internal"),
|
||||
InvalidRequest => write!(f, "invalid_request"),
|
||||
Auth => write!(f, "auth"),
|
||||
System => write!(f, "system"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement all the error codes.
|
||||
///
|
||||
/// 1. Make an enum `Code` where each error code is a variant
|
||||
/// 2. Implement the `http`, `name`, and `type_` method on the enum
|
||||
/// 3. Make a unit type for each error code in the module `deserr_codes`.
|
||||
///
|
||||
/// The unit type's purpose is to be used as a marker type parameter, e.g.
|
||||
/// `DeserrJsonError<MyErrorCode>`. It implements `Default` and `ErrorCode`,
|
||||
/// so we can get a value of the `Code` enum with the correct variant by calling
|
||||
/// `MyErrorCode::default().error_code()`.
|
||||
macro_rules! make_error_codes {
|
||||
($($code_ident:ident, $err_type:ident, $status:ident);*) => {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -123,29 +126,31 @@ macro_rules! make_error_codes {
|
||||
$($code_ident),*
|
||||
}
|
||||
impl Code {
|
||||
/// associate a `Code` variant to the actual ErrCode
|
||||
fn err_code(&self) -> ErrCode {
|
||||
match self {
|
||||
$(
|
||||
Code::$code_ident => {
|
||||
ErrCode::$err_type( stringify!($code_ident).to_case(convert_case::Case::Snake), StatusCode::$status)
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
/// return the HTTP status code associated with the `Code`
|
||||
fn http(&self) -> StatusCode {
|
||||
self.err_code().status_code
|
||||
match self {
|
||||
$(
|
||||
Code::$code_ident => StatusCode::$status
|
||||
),*
|
||||
}
|
||||
}
|
||||
|
||||
/// return error name, used as error code
|
||||
fn name(&self) -> String {
|
||||
self.err_code().error_name.to_string()
|
||||
match self {
|
||||
$(
|
||||
Code::$code_ident => stringify!($code_ident).to_case(convert_case::Case::Snake)
|
||||
),*
|
||||
}
|
||||
}
|
||||
|
||||
/// return the error type
|
||||
fn type_(&self) -> String {
|
||||
self.err_code().error_type.to_string()
|
||||
match self {
|
||||
$(
|
||||
Code::$code_ident => ErrorType::$err_type.to_string()
|
||||
),*
|
||||
}
|
||||
}
|
||||
|
||||
/// return the doc url associated with the error
|
||||
@ -170,146 +175,121 @@ macro_rules! make_error_codes {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An exhaustive list of all the error codes used by meilisearch.
|
||||
make_error_codes! {
|
||||
ApiKeyAlreadyExists , invalid , CONFLICT ;
|
||||
ApiKeyNotFound , invalid , NOT_FOUND ;
|
||||
BadParameter , invalid , BAD_REQUEST;
|
||||
BadRequest , invalid , BAD_REQUEST;
|
||||
DatabaseSizeLimitReached , internal , INTERNAL_SERVER_ERROR;
|
||||
DocumentNotFound , invalid , NOT_FOUND;
|
||||
DumpAlreadyProcessing , invalid , CONFLICT;
|
||||
DumpNotFound , invalid , NOT_FOUND;
|
||||
DumpProcessFailed , internal , INTERNAL_SERVER_ERROR;
|
||||
DuplicateIndexFound , invalid , BAD_REQUEST;
|
||||
|
||||
ImmutableApiKeyUid , invalid , BAD_REQUEST;
|
||||
ImmutableApiKeyKey , invalid , BAD_REQUEST;
|
||||
ImmutableApiKeyActions , invalid , BAD_REQUEST;
|
||||
ImmutableApiKeyIndexes , invalid , BAD_REQUEST;
|
||||
ImmutableApiKeyExpiresAt , invalid , BAD_REQUEST;
|
||||
ImmutableApiKeyCreatedAt , invalid , BAD_REQUEST;
|
||||
ImmutableApiKeyUpdatedAt , invalid , BAD_REQUEST;
|
||||
|
||||
ImmutableIndexUid , invalid , BAD_REQUEST;
|
||||
ImmutableIndexCreatedAt , invalid , BAD_REQUEST;
|
||||
ImmutableIndexUpdatedAt , invalid , BAD_REQUEST;
|
||||
|
||||
IndexAlreadyExists , invalid , CONFLICT ;
|
||||
IndexCreationFailed , internal , INTERNAL_SERVER_ERROR;
|
||||
IndexNotFound , invalid , NOT_FOUND;
|
||||
IndexPrimaryKeyAlreadyExists , invalid , BAD_REQUEST ;
|
||||
IndexPrimaryKeyNoCandidateFound , invalid , BAD_REQUEST ;
|
||||
IndexPrimaryKeyMultipleCandidatesFound, invalid , BAD_REQUEST;
|
||||
Internal , internal , INTERNAL_SERVER_ERROR ;
|
||||
InvalidApiKeyActions , invalid , BAD_REQUEST ;
|
||||
InvalidApiKeyDescription , invalid , BAD_REQUEST ;
|
||||
InvalidApiKeyExpiresAt , invalid , BAD_REQUEST ;
|
||||
InvalidApiKeyIndexes , invalid , BAD_REQUEST ;
|
||||
InvalidApiKeyLimit , invalid , BAD_REQUEST ;
|
||||
InvalidApiKeyName , invalid , BAD_REQUEST ;
|
||||
InvalidApiKeyOffset , invalid , BAD_REQUEST ;
|
||||
InvalidApiKeyUid , invalid , BAD_REQUEST ;
|
||||
InvalidApiKey , authentication, FORBIDDEN ;
|
||||
InvalidContentType , invalid , UNSUPPORTED_MEDIA_TYPE ;
|
||||
InvalidDocumentFields , invalid , BAD_REQUEST ;
|
||||
InvalidDocumentGeoField , invalid , BAD_REQUEST ;
|
||||
InvalidDocumentId , invalid , BAD_REQUEST ;
|
||||
InvalidDocumentLimit , invalid , BAD_REQUEST ;
|
||||
InvalidDocumentOffset , invalid , BAD_REQUEST ;
|
||||
InvalidIndexLimit , invalid , BAD_REQUEST ;
|
||||
InvalidIndexOffset , invalid , BAD_REQUEST ;
|
||||
InvalidIndexPrimaryKey , invalid , BAD_REQUEST ;
|
||||
InvalidIndexUid , invalid , BAD_REQUEST ;
|
||||
InvalidMinWordLengthForTypo , invalid , BAD_REQUEST ;
|
||||
InvalidSearchAttributesToCrop , invalid , BAD_REQUEST ;
|
||||
InvalidSearchAttributesToHighlight , invalid , BAD_REQUEST ;
|
||||
InvalidSearchAttributesToRetrieve , invalid , BAD_REQUEST ;
|
||||
InvalidSearchCropLength , invalid , BAD_REQUEST ;
|
||||
InvalidSearchCropMarker , invalid , BAD_REQUEST ;
|
||||
InvalidSearchFacets , invalid , BAD_REQUEST ;
|
||||
InvalidSearchFilter , invalid , BAD_REQUEST ;
|
||||
InvalidSearchHighlightPostTag , invalid , BAD_REQUEST ;
|
||||
InvalidSearchHighlightPreTag , invalid , BAD_REQUEST ;
|
||||
InvalidSearchHitsPerPage , invalid , BAD_REQUEST ;
|
||||
InvalidSearchLimit , invalid , BAD_REQUEST ;
|
||||
InvalidSearchMatchingStrategy , invalid , BAD_REQUEST ;
|
||||
InvalidSearchOffset , invalid , BAD_REQUEST ;
|
||||
InvalidSearchPage , invalid , BAD_REQUEST ;
|
||||
InvalidSearchQ , invalid , BAD_REQUEST ;
|
||||
InvalidSearchShowMatchesPosition , invalid , BAD_REQUEST ;
|
||||
InvalidSearchSort , invalid , BAD_REQUEST ;
|
||||
InvalidSettingsDisplayedAttributes , invalid , BAD_REQUEST ;
|
||||
InvalidSettingsDistinctAttribute , invalid , BAD_REQUEST ;
|
||||
InvalidSettingsFaceting , invalid , BAD_REQUEST ;
|
||||
InvalidSettingsFilterableAttributes , invalid , BAD_REQUEST ;
|
||||
InvalidSettingsPagination , invalid , BAD_REQUEST ;
|
||||
InvalidSettingsRankingRules , invalid , BAD_REQUEST ;
|
||||
InvalidSettingsSearchableAttributes , invalid , BAD_REQUEST ;
|
||||
InvalidSettingsSortableAttributes , invalid , BAD_REQUEST ;
|
||||
InvalidSettingsStopWords , invalid , BAD_REQUEST ;
|
||||
InvalidSettingsSynonyms , invalid , BAD_REQUEST ;
|
||||
InvalidSettingsTypoTolerance , invalid , BAD_REQUEST ;
|
||||
InvalidState , internal , INTERNAL_SERVER_ERROR ;
|
||||
InvalidStoreFile , internal , INTERNAL_SERVER_ERROR ;
|
||||
InvalidSwapDuplicateIndexFound , invalid , BAD_REQUEST ;
|
||||
InvalidSwapIndexes , invalid , BAD_REQUEST ;
|
||||
InvalidTaskAfterEnqueuedAt , invalid , BAD_REQUEST ;
|
||||
InvalidTaskAfterFinishedAt , invalid , BAD_REQUEST ;
|
||||
InvalidTaskAfterStartedAt , invalid , BAD_REQUEST ;
|
||||
InvalidTaskBeforeEnqueuedAt , invalid , BAD_REQUEST ;
|
||||
InvalidTaskBeforeFinishedAt , invalid , BAD_REQUEST ;
|
||||
InvalidTaskBeforeStartedAt , invalid , BAD_REQUEST ;
|
||||
InvalidTaskCanceledBy , invalid , BAD_REQUEST ;
|
||||
InvalidTaskFrom , invalid , BAD_REQUEST ;
|
||||
InvalidTaskLimit , invalid , BAD_REQUEST ;
|
||||
InvalidTaskStatuses , invalid , BAD_REQUEST ;
|
||||
InvalidTaskTypes , invalid , BAD_REQUEST ;
|
||||
InvalidTaskUids , invalid , BAD_REQUEST ;
|
||||
IoError , system , UNPROCESSABLE_ENTITY;
|
||||
MalformedPayload , invalid , BAD_REQUEST ;
|
||||
MaxFieldsLimitExceeded , invalid , BAD_REQUEST ;
|
||||
MissingApiKeyActions , invalid , BAD_REQUEST ;
|
||||
MissingApiKeyExpiresAt , invalid , BAD_REQUEST ;
|
||||
MissingApiKeyIndexes , invalid , BAD_REQUEST ;
|
||||
MissingAuthorizationHeader , authentication, UNAUTHORIZED ;
|
||||
MissingContentType , invalid , UNSUPPORTED_MEDIA_TYPE ;
|
||||
MissingDocumentId , invalid , BAD_REQUEST ;
|
||||
MissingIndexUid , invalid , BAD_REQUEST ;
|
||||
MissingMasterKey , authentication, UNAUTHORIZED ;
|
||||
MissingPayload , invalid , BAD_REQUEST ;
|
||||
MissingTaskFilters , invalid , BAD_REQUEST ;
|
||||
NoSpaceLeftOnDevice , system , UNPROCESSABLE_ENTITY;
|
||||
PayloadTooLarge , invalid , PAYLOAD_TOO_LARGE ;
|
||||
TaskNotFound , invalid , NOT_FOUND ;
|
||||
TooManyOpenFiles , system , UNPROCESSABLE_ENTITY ;
|
||||
UnretrievableDocument , internal , BAD_REQUEST ;
|
||||
UnretrievableErrorCode , invalid , BAD_REQUEST ;
|
||||
UnsupportedMediaType , invalid , UNSUPPORTED_MEDIA_TYPE
|
||||
}
|
||||
|
||||
/// Internal structure providing a convenient way to create error codes
|
||||
struct ErrCode {
|
||||
status_code: StatusCode,
|
||||
error_type: ErrorType,
|
||||
error_name: String,
|
||||
}
|
||||
|
||||
impl ErrCode {
|
||||
fn authentication(error_name: String, status_code: StatusCode) -> ErrCode {
|
||||
ErrCode { status_code, error_name, error_type: ErrorType::AuthenticationError }
|
||||
}
|
||||
|
||||
fn internal(error_name: String, status_code: StatusCode) -> ErrCode {
|
||||
ErrCode { status_code, error_name, error_type: ErrorType::InternalError }
|
||||
}
|
||||
|
||||
fn invalid(error_name: String, status_code: StatusCode) -> ErrCode {
|
||||
ErrCode { status_code, error_name, error_type: ErrorType::InvalidRequestError }
|
||||
}
|
||||
|
||||
fn system(error_name: String, status_code: StatusCode) -> ErrCode {
|
||||
ErrCode { status_code, error_name, error_type: ErrorType::System }
|
||||
}
|
||||
ApiKeyAlreadyExists , InvalidRequest , CONFLICT ;
|
||||
ApiKeyNotFound , InvalidRequest , NOT_FOUND ;
|
||||
BadParameter , InvalidRequest , BAD_REQUEST;
|
||||
BadRequest , InvalidRequest , BAD_REQUEST;
|
||||
DatabaseSizeLimitReached , Internal , INTERNAL_SERVER_ERROR;
|
||||
DocumentNotFound , InvalidRequest , NOT_FOUND;
|
||||
DumpAlreadyProcessing , InvalidRequest , CONFLICT;
|
||||
DumpNotFound , InvalidRequest , NOT_FOUND;
|
||||
DumpProcessFailed , Internal , INTERNAL_SERVER_ERROR;
|
||||
DuplicateIndexFound , InvalidRequest , BAD_REQUEST;
|
||||
ImmutableApiKeyActions , InvalidRequest , BAD_REQUEST;
|
||||
ImmutableApiKeyCreatedAt , InvalidRequest , BAD_REQUEST;
|
||||
ImmutableApiKeyExpiresAt , InvalidRequest , BAD_REQUEST;
|
||||
ImmutableApiKeyIndexes , InvalidRequest , BAD_REQUEST;
|
||||
ImmutableApiKeyKey , InvalidRequest , BAD_REQUEST;
|
||||
ImmutableApiKeyUid , InvalidRequest , BAD_REQUEST;
|
||||
ImmutableApiKeyUpdatedAt , InvalidRequest , BAD_REQUEST;
|
||||
ImmutableIndexCreatedAt , InvalidRequest , BAD_REQUEST;
|
||||
ImmutableIndexUid , InvalidRequest , BAD_REQUEST;
|
||||
ImmutableIndexUpdatedAt , InvalidRequest , BAD_REQUEST;
|
||||
IndexAlreadyExists , InvalidRequest , CONFLICT ;
|
||||
IndexCreationFailed , Internal , INTERNAL_SERVER_ERROR;
|
||||
IndexNotFound , InvalidRequest , NOT_FOUND;
|
||||
IndexPrimaryKeyAlreadyExists , InvalidRequest , BAD_REQUEST ;
|
||||
IndexPrimaryKeyMultipleCandidatesFound, InvalidRequest , BAD_REQUEST;
|
||||
IndexPrimaryKeyNoCandidateFound , InvalidRequest , BAD_REQUEST ;
|
||||
Internal , Internal , INTERNAL_SERVER_ERROR ;
|
||||
InvalidApiKey , Auth , FORBIDDEN ;
|
||||
InvalidApiKeyActions , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidApiKeyDescription , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidApiKeyExpiresAt , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidApiKeyIndexes , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidApiKeyLimit , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidApiKeyName , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidApiKeyOffset , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidApiKeyUid , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidContentType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ;
|
||||
InvalidDocumentFields , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidDocumentGeoField , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidDocumentId , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidDocumentLimit , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidDocumentOffset , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidIndexLimit , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidIndexOffset , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidIndexPrimaryKey , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidIndexUid , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidMinWordLengthForTypo , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchAttributesToCrop , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchAttributesToHighlight , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchAttributesToRetrieve , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchCropLength , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchCropMarker , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchFacets , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchFilter , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchHighlightPostTag , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchHighlightPreTag , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchHitsPerPage , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchLimit , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchMatchingStrategy , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchOffset , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchPage , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchQ , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchShowMatchesPosition , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchSort , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsDisplayedAttributes , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsDistinctAttribute , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsFaceting , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsFilterableAttributes , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsPagination , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsRankingRules , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsSearchableAttributes , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsSortableAttributes , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsStopWords , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsSynonyms , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsTypoTolerance , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidState , Internal , INTERNAL_SERVER_ERROR ;
|
||||
InvalidStoreFile , Internal , INTERNAL_SERVER_ERROR ;
|
||||
InvalidSwapDuplicateIndexFound , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSwapIndexes , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidTaskAfterEnqueuedAt , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidTaskAfterFinishedAt , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidTaskAfterStartedAt , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidTaskBeforeEnqueuedAt , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidTaskBeforeFinishedAt , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidTaskBeforeStartedAt , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidTaskCanceledBy , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidTaskFrom , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidTaskLimit , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidTaskStatuses , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidTaskTypes , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidTaskUids , InvalidRequest , BAD_REQUEST ;
|
||||
IoError , System , UNPROCESSABLE_ENTITY;
|
||||
MalformedPayload , InvalidRequest , BAD_REQUEST ;
|
||||
MaxFieldsLimitExceeded , InvalidRequest , BAD_REQUEST ;
|
||||
MissingApiKeyActions , InvalidRequest , BAD_REQUEST ;
|
||||
MissingApiKeyExpiresAt , InvalidRequest , BAD_REQUEST ;
|
||||
MissingApiKeyIndexes , InvalidRequest , BAD_REQUEST ;
|
||||
MissingAuthorizationHeader , Auth , UNAUTHORIZED ;
|
||||
MissingContentType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ;
|
||||
MissingDocumentId , InvalidRequest , BAD_REQUEST ;
|
||||
MissingIndexUid , InvalidRequest , BAD_REQUEST ;
|
||||
MissingMasterKey , Auth , UNAUTHORIZED ;
|
||||
MissingPayload , InvalidRequest , BAD_REQUEST ;
|
||||
MissingSwapIndexes , InvalidRequest , BAD_REQUEST ;
|
||||
MissingTaskFilters , InvalidRequest , BAD_REQUEST ;
|
||||
NoSpaceLeftOnDevice , System , UNPROCESSABLE_ENTITY;
|
||||
PayloadTooLarge , InvalidRequest , PAYLOAD_TOO_LARGE ;
|
||||
TaskNotFound , InvalidRequest , NOT_FOUND ;
|
||||
TooManyOpenFiles , System , UNPROCESSABLE_ENTITY ;
|
||||
UnretrievableDocument , Internal , BAD_REQUEST ;
|
||||
UnretrievableErrorCode , InvalidRequest , BAD_REQUEST ;
|
||||
UnsupportedMediaType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE
|
||||
}
|
||||
|
||||
impl ErrorCode for JoinError {
|
||||
@ -404,6 +384,7 @@ impl ErrorCode for io::Error {
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwrap a result, either its Ok or Err value.
|
||||
pub fn unwrap_any<T>(any: Result<T, T>) -> T {
|
||||
match any {
|
||||
Ok(any) => any,
|
||||
@ -411,90 +392,41 @@ pub fn unwrap_any<T>(any: Result<T, T>) -> T {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-traits")]
|
||||
mod strategy {
|
||||
use proptest::strategy::Strategy;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(super) fn status_code_strategy() -> impl Strategy<Value = StatusCode> {
|
||||
(100..999u16).prop_map(|i| StatusCode::from_u16(i).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeserrError<C: ErrorCode = deserr_codes::BadRequest> {
|
||||
pub msg: String,
|
||||
pub code: Code,
|
||||
_phantom: PhantomData<C>,
|
||||
}
|
||||
impl<C: ErrorCode> std::fmt::Debug for DeserrError<C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DeserrError").field("msg", &self.msg).field("code", &self.code).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ErrorCode> std::fmt::Display for DeserrError<C> {
|
||||
/// Deserialization when `deserr` cannot parse an API key date.
|
||||
#[derive(Debug)]
|
||||
pub struct ParseOffsetDateTimeError(pub String);
|
||||
impl fmt::Display for ParseOffsetDateTimeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.msg)
|
||||
writeln!(f, "`{original}` is not a valid date. It should follow the RFC 3339 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'.", original = self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ErrorCode> std::error::Error for DeserrError<C> {}
|
||||
impl<C: ErrorCode> ErrorCode for DeserrError<C> {
|
||||
fn error_code(&self) -> Code {
|
||||
self.code
|
||||
/// Deserialization when `deserr` cannot parse a task date.
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidTaskDateError(pub String);
|
||||
impl std::fmt::Display for InvalidTaskDateError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "`{}` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C1: ErrorCode, C2: ErrorCode> MergeWithError<DeserrError<C2>> for DeserrError<C1> {
|
||||
fn merge(
|
||||
_self_: Option<Self>,
|
||||
other: DeserrError<C2>,
|
||||
_merge_location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
Err(DeserrError { msg: other.msg, code: other.code, _phantom: PhantomData })
|
||||
/// Deserialization error when `deserr` cannot parse a String
|
||||
/// into a bool.
|
||||
#[derive(Debug)]
|
||||
pub struct DeserrParseBoolError(pub String);
|
||||
impl fmt::Display for DeserrParseBoolError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "could not parse `{}` as a boolean, expected either `true` or `false`", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserrError<MissingIndexUid> {
|
||||
pub fn missing_index_uid(field: &str, location: ValuePointerRef) -> Self {
|
||||
let x = unwrap_any(Self::error::<Infallible>(
|
||||
None,
|
||||
deserr::ErrorKind::MissingField { field },
|
||||
location,
|
||||
));
|
||||
Self { msg: x.msg, code: MissingIndexUid.error_code(), _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Default + ErrorCode> deserr::DeserializeError for DeserrError<C> {
|
||||
fn error<V: IntoValue>(
|
||||
_self_: Option<Self>,
|
||||
error: deserr::ErrorKind<V>,
|
||||
location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
let msg = unwrap_any(deserr::serde_json::JsonError::error(None, error, location)).0;
|
||||
|
||||
Err(DeserrError { msg, code: C::default().error_code(), _phantom: PhantomData })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TakeErrorMessage<T>(pub T);
|
||||
|
||||
impl<C: Default + ErrorCode, T> MergeWithError<TakeErrorMessage<T>> for DeserrError<C>
|
||||
where
|
||||
T: std::error::Error,
|
||||
{
|
||||
fn merge(
|
||||
_self_: Option<Self>,
|
||||
other: TakeErrorMessage<T>,
|
||||
merge_location: ValuePointerRef,
|
||||
) -> Result<Self, Self> {
|
||||
DeserrError::error::<Infallible>(
|
||||
None,
|
||||
deserr::ErrorKind::Unexpected { msg: other.0.to_string() },
|
||||
merge_location,
|
||||
)
|
||||
/// Deserialization error when `deserr` cannot parse a String
|
||||
/// into an integer.
|
||||
#[derive(Debug)]
|
||||
pub struct DeserrParseIntError(pub String);
|
||||
impl fmt::Display for DeserrParseIntError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "could not parse `{}` as a positive integer", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,17 +2,15 @@ use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use deserr::DeserializeFromValue;
|
||||
|
||||
use crate::error::{Code, ErrorCode};
|
||||
|
||||
/// An index uid is composed of only ascii alphanumeric characters, - and _, between 1 and 400
|
||||
/// bytes long
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "test-traits", derive(proptest_derive::Arbitrary))]
|
||||
pub struct IndexUid(
|
||||
#[cfg_attr(feature = "test-traits", proptest(regex("[a-zA-Z0-9_-]{1,400}")))] String,
|
||||
);
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DeserializeFromValue)]
|
||||
#[deserr(from(String) = IndexUid::try_from -> IndexUidFormatError)]
|
||||
pub struct IndexUid(String);
|
||||
|
||||
impl IndexUid {
|
||||
pub fn new_unchecked(s: impl AsRef<str>) -> Self {
|
||||
@ -29,6 +27,12 @@ impl IndexUid {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IndexUid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for IndexUid {
|
||||
type Target = str;
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use std::convert::Infallible;
|
||||
use std::fmt::Display;
|
||||
use std::hash::Hash;
|
||||
use std::str::FromStr;
|
||||
|
||||
use deserr::{DeserializeError, DeserializeFromValue, MergeWithError, ValuePointerRef};
|
||||
use deserr::{DeserializeError, DeserializeFromValue, ValuePointerRef};
|
||||
use enum_iterator::Sequence;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
@ -10,45 +10,28 @@ use time::macros::{format_description, time};
|
||||
use time::{Date, OffsetDateTime, PrimitiveDateTime};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::deserr::DeserrJsonError;
|
||||
use crate::error::deserr_codes::*;
|
||||
use crate::error::{unwrap_any, Code, DeserrError, ErrorCode, TakeErrorMessage};
|
||||
use crate::index_uid::{IndexUid, IndexUidFormatError};
|
||||
use crate::error::{unwrap_any, Code, ParseOffsetDateTimeError};
|
||||
use crate::index_uid::IndexUid;
|
||||
use crate::star_or::StarOr;
|
||||
|
||||
pub type KeyId = Uuid;
|
||||
|
||||
impl<C: Default + ErrorCode> MergeWithError<IndexUidFormatError> for DeserrError<C> {
|
||||
fn merge(
|
||||
_self_: Option<Self>,
|
||||
other: IndexUidFormatError,
|
||||
merge_location: deserr::ValuePointerRef,
|
||||
) -> std::result::Result<Self, Self> {
|
||||
DeserrError::error::<Infallible>(
|
||||
None,
|
||||
deserr::ErrorKind::Unexpected { msg: other.to_string() },
|
||||
merge_location,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_uuid_from_str(s: &str) -> Result<Uuid, TakeErrorMessage<uuid::Error>> {
|
||||
Uuid::parse_str(s).map_err(TakeErrorMessage)
|
||||
}
|
||||
|
||||
#[derive(Debug, DeserializeFromValue)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct CreateApiKey {
|
||||
#[deserr(error = DeserrError<InvalidApiKeyDescription>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidApiKeyDescription>)]
|
||||
pub description: Option<String>,
|
||||
#[deserr(error = DeserrError<InvalidApiKeyName>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidApiKeyName>)]
|
||||
pub name: Option<String>,
|
||||
#[deserr(default = Uuid::new_v4(), error = DeserrError<InvalidApiKeyUid>, from(&String) = parse_uuid_from_str -> TakeErrorMessage<uuid::Error>)]
|
||||
#[deserr(default = Uuid::new_v4(), error = DeserrJsonError<InvalidApiKeyUid>, from(&String) = Uuid::from_str -> uuid::Error)]
|
||||
pub uid: KeyId,
|
||||
#[deserr(error = DeserrError<InvalidApiKeyActions>)]
|
||||
#[deserr(error = DeserrJsonError<InvalidApiKeyActions>, missing_field_error = DeserrJsonError::missing_api_key_actions)]
|
||||
pub actions: Vec<Action>,
|
||||
#[deserr(error = DeserrError<InvalidApiKeyIndexes>)]
|
||||
#[deserr(error = DeserrJsonError<InvalidApiKeyIndexes>, missing_field_error = DeserrJsonError::missing_api_key_indexes)]
|
||||
pub indexes: Vec<StarOr<IndexUid>>,
|
||||
#[deserr(error = DeserrError<InvalidApiKeyExpiresAt>, default = None, from(&String) = parse_expiration_date -> TakeErrorMessage<ParseOffsetDateTimeError>)]
|
||||
#[deserr(error = DeserrJsonError<InvalidApiKeyExpiresAt>, from(Option<String>) = parse_expiration_date -> ParseOffsetDateTimeError, missing_field_error = DeserrJsonError::missing_api_key_expires_at)]
|
||||
pub expires_at: Option<OffsetDateTime>,
|
||||
}
|
||||
impl CreateApiKey {
|
||||
@ -72,8 +55,8 @@ fn deny_immutable_fields_api_key(
|
||||
field: &str,
|
||||
accepted: &[&str],
|
||||
location: ValuePointerRef,
|
||||
) -> DeserrError {
|
||||
let mut error = unwrap_any(DeserrError::<BadRequest>::error::<Infallible>(
|
||||
) -> DeserrJsonError {
|
||||
let mut error = unwrap_any(DeserrJsonError::<BadRequest>::error::<Infallible>(
|
||||
None,
|
||||
deserr::ErrorKind::UnknownKey { key: field, accepted },
|
||||
location,
|
||||
@ -92,11 +75,11 @@ fn deny_immutable_fields_api_key(
|
||||
}
|
||||
|
||||
#[derive(Debug, DeserializeFromValue)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields = deny_immutable_fields_api_key)]
|
||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields = deny_immutable_fields_api_key)]
|
||||
pub struct PatchApiKey {
|
||||
#[deserr(error = DeserrError<InvalidApiKeyDescription>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidApiKeyDescription>)]
|
||||
pub description: Option<String>,
|
||||
#[deserr(error = DeserrError<InvalidApiKeyName>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidApiKeyName>)]
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
@ -149,46 +132,40 @@ impl Key {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseOffsetDateTimeError(String);
|
||||
impl Display for ParseOffsetDateTimeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "`{original}` is not a valid date. It should follow the RFC 3339 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'.", original = self.0)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ParseOffsetDateTimeError {}
|
||||
|
||||
fn parse_expiration_date(
|
||||
string: &str,
|
||||
) -> std::result::Result<Option<OffsetDateTime>, TakeErrorMessage<ParseOffsetDateTimeError>> {
|
||||
let datetime = if let Ok(datetime) = OffsetDateTime::parse(string, &Rfc3339) {
|
||||
string: Option<String>,
|
||||
) -> std::result::Result<Option<OffsetDateTime>, ParseOffsetDateTimeError> {
|
||||
let Some(string) = string else {
|
||||
return Ok(None)
|
||||
};
|
||||
let datetime = if let Ok(datetime) = OffsetDateTime::parse(&string, &Rfc3339) {
|
||||
datetime
|
||||
} else if let Ok(primitive_datetime) = PrimitiveDateTime::parse(
|
||||
string,
|
||||
&string,
|
||||
format_description!(
|
||||
"[year repr:full base:calendar]-[month repr:numerical]-[day]T[hour]:[minute]:[second]"
|
||||
),
|
||||
) {
|
||||
primitive_datetime.assume_utc()
|
||||
} else if let Ok(primitive_datetime) = PrimitiveDateTime::parse(
|
||||
string,
|
||||
&string,
|
||||
format_description!(
|
||||
"[year repr:full base:calendar]-[month repr:numerical]-[day] [hour]:[minute]:[second]"
|
||||
),
|
||||
) {
|
||||
primitive_datetime.assume_utc()
|
||||
} else if let Ok(date) = Date::parse(
|
||||
string,
|
||||
&string,
|
||||
format_description!("[year repr:full base:calendar]-[month repr:numerical]-[day]"),
|
||||
) {
|
||||
PrimitiveDateTime::new(date, time!(00:00)).assume_utc()
|
||||
} else {
|
||||
return Err(TakeErrorMessage(ParseOffsetDateTimeError(string.to_owned())));
|
||||
return Err(ParseOffsetDateTimeError(string));
|
||||
};
|
||||
if datetime > OffsetDateTime::now_utc() {
|
||||
Ok(Some(datetime))
|
||||
} else {
|
||||
Err(TakeErrorMessage(ParseOffsetDateTimeError(string.to_owned())))
|
||||
Err(ParseOffsetDateTimeError(string))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
pub mod compression;
|
||||
pub mod deserr;
|
||||
pub mod document_formats;
|
||||
pub mod error;
|
||||
pub mod index_uid;
|
||||
@ -7,11 +8,10 @@ pub mod settings;
|
||||
pub mod star_or;
|
||||
pub mod tasks;
|
||||
pub mod versioning;
|
||||
|
||||
pub use milli;
|
||||
pub use milli::{heed, Index};
|
||||
use uuid::Uuid;
|
||||
pub use versioning::VERSION_FILE_NAME;
|
||||
pub use {milli, serde_cs};
|
||||
|
||||
pub type Document = serde_json::Map<String, serde_json::Value>;
|
||||
pub type InstanceUid = Uuid;
|
||||
|
@ -11,8 +11,9 @@ use milli::update::Setting;
|
||||
use milli::{Criterion, CriterionError, Index, DEFAULT_VALUES_PER_FACET};
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::deserr::DeserrJsonError;
|
||||
use crate::error::deserr_codes::*;
|
||||
use crate::error::{unwrap_any, DeserrError};
|
||||
use crate::error::unwrap_any;
|
||||
|
||||
/// The maximimum number of results that the engine
|
||||
/// will be able to return in one search call.
|
||||
@ -66,26 +67,31 @@ fn validate_min_word_size_for_typo_setting<E: DeserializeError>(
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, DeserializeFromValue)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
#[deserr(deny_unknown_fields, rename_all = camelCase, validate = validate_min_word_size_for_typo_setting -> DeserrError<InvalidMinWordLengthForTypo>)]
|
||||
#[deserr(deny_unknown_fields, rename_all = camelCase, validate = validate_min_word_size_for_typo_setting -> DeserrJsonError<InvalidMinWordLengthForTypo>)]
|
||||
pub struct MinWordSizeTyposSetting {
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(default)]
|
||||
pub one_typo: Setting<u8>,
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(default)]
|
||||
pub two_typos: Setting<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, DeserializeFromValue)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
#[deserr(deny_unknown_fields, rename_all = camelCase, where_predicate = __Deserr_E: deserr::MergeWithError<DeserrError<InvalidMinWordLengthForTypo>>)]
|
||||
#[deserr(deny_unknown_fields, rename_all = camelCase, where_predicate = __Deserr_E: deserr::MergeWithError<DeserrJsonError<InvalidMinWordLengthForTypo>>)]
|
||||
pub struct TypoSettings {
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(default)]
|
||||
pub enabled: Setting<bool>,
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(error = DeserrError<InvalidMinWordLengthForTypo>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidMinWordLengthForTypo>)]
|
||||
pub min_word_size_for_typos: Setting<MinWordSizeTyposSetting>,
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(default)]
|
||||
pub disable_on_words: Setting<BTreeSet<String>>,
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(default)]
|
||||
pub disable_on_attributes: Setting<BTreeSet<String>>,
|
||||
}
|
||||
|
||||
@ -94,6 +100,7 @@ pub struct TypoSettings {
|
||||
#[deserr(rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct FacetingSettings {
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(default)]
|
||||
pub max_values_per_facet: Setting<usize>,
|
||||
}
|
||||
|
||||
@ -102,10 +109,11 @@ pub struct FacetingSettings {
|
||||
#[deserr(rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct PaginationSettings {
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(default)]
|
||||
pub max_total_hits: Setting<usize>,
|
||||
}
|
||||
|
||||
impl MergeWithError<milli::CriterionError> for DeserrError<InvalidSettingsRankingRules> {
|
||||
impl MergeWithError<milli::CriterionError> for DeserrJsonError<InvalidSettingsRankingRules> {
|
||||
fn merge(
|
||||
_self_: Option<Self>,
|
||||
other: milli::CriterionError,
|
||||
@ -128,14 +136,14 @@ impl MergeWithError<milli::CriterionError> for DeserrError<InvalidSettingsRankin
|
||||
rename_all = "camelCase",
|
||||
bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'static>")
|
||||
)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct Settings<T> {
|
||||
#[serde(
|
||||
default,
|
||||
serialize_with = "serialize_with_wildcard",
|
||||
skip_serializing_if = "Setting::is_not_set"
|
||||
)]
|
||||
#[deserr(error = DeserrError<InvalidSettingsDisplayedAttributes>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsDisplayedAttributes>)]
|
||||
pub displayed_attributes: Setting<Vec<String>>,
|
||||
|
||||
#[serde(
|
||||
@ -143,35 +151,35 @@ pub struct Settings<T> {
|
||||
serialize_with = "serialize_with_wildcard",
|
||||
skip_serializing_if = "Setting::is_not_set"
|
||||
)]
|
||||
#[deserr(error = DeserrError<InvalidSettingsSearchableAttributes>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsSearchableAttributes>)]
|
||||
pub searchable_attributes: Setting<Vec<String>>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(error = DeserrError<InvalidSettingsFilterableAttributes>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsFilterableAttributes>)]
|
||||
pub filterable_attributes: Setting<BTreeSet<String>>,
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(error = DeserrError<InvalidSettingsSortableAttributes>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsSortableAttributes>)]
|
||||
pub sortable_attributes: Setting<BTreeSet<String>>,
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(error = DeserrError<InvalidSettingsRankingRules>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsRankingRules>)]
|
||||
pub ranking_rules: Setting<Vec<RankingRuleView>>,
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(error = DeserrError<InvalidSettingsStopWords>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsStopWords>)]
|
||||
pub stop_words: Setting<BTreeSet<String>>,
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(error = DeserrError<InvalidSettingsSynonyms>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsSynonyms>)]
|
||||
pub synonyms: Setting<BTreeMap<String, Vec<String>>>,
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(error = DeserrError<InvalidSettingsDistinctAttribute>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsDistinctAttribute>)]
|
||||
pub distinct_attribute: Setting<String>,
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(error = DeserrError<InvalidSettingsTypoTolerance>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsTypoTolerance>)]
|
||||
pub typo_tolerance: Setting<TypoSettings>,
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(error = DeserrError<InvalidSettingsFaceting>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsFaceting>)]
|
||||
pub faceting: Setting<FacetingSettings>,
|
||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||
#[deserr(error = DeserrError<InvalidSettingsPagination>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsPagination>)]
|
||||
pub pagination: Setting<PaginationSettings>,
|
||||
|
||||
#[serde(skip)]
|
||||
|
@ -1,12 +1,12 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
|
||||
use deserr::{DeserializeError, DeserializeFromValue, MergeWithError, ValueKind};
|
||||
use serde::de::Visitor;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::deserr::query_params::FromQueryParameter;
|
||||
use crate::error::unwrap_any;
|
||||
|
||||
/// A type that tries to match either a star (*) or
|
||||
@ -17,35 +17,6 @@ pub enum StarOr<T> {
|
||||
Other(T),
|
||||
}
|
||||
|
||||
impl<E: DeserializeError, T> DeserializeFromValue<E> for StarOr<T>
|
||||
where
|
||||
T: FromStr,
|
||||
E: MergeWithError<T::Err>,
|
||||
{
|
||||
fn deserialize_from_value<V: deserr::IntoValue>(
|
||||
value: deserr::Value<V>,
|
||||
location: deserr::ValuePointerRef,
|
||||
) -> Result<Self, E> {
|
||||
match value {
|
||||
deserr::Value::String(v) => match v.as_str() {
|
||||
"*" => Ok(StarOr::Star),
|
||||
v => match FromStr::from_str(v) {
|
||||
Ok(x) => Ok(StarOr::Other(x)),
|
||||
Err(e) => Err(unwrap_any(E::merge(None, e, location))),
|
||||
},
|
||||
},
|
||||
_ => Err(unwrap_any(E::error::<V>(
|
||||
None,
|
||||
deserr::ErrorKind::IncorrectValueKind {
|
||||
actual: value,
|
||||
accepted: &[ValueKind::String],
|
||||
},
|
||||
location,
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromStr> FromStr for StarOr<T> {
|
||||
type Err = T::Err;
|
||||
|
||||
@ -57,23 +28,11 @@ impl<T: FromStr> FromStr for StarOr<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Deref<Target = str>> Deref for StarOr<T> {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
impl<T: fmt::Display> fmt::Display for StarOr<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Star => "*",
|
||||
Self::Other(t) => t.deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<String>> From<StarOr<T>> for String {
|
||||
fn from(s: StarOr<T>) -> Self {
|
||||
match s {
|
||||
StarOr::Star => "*".to_string(),
|
||||
StarOr::Other(t) => t.into(),
|
||||
StarOr::Star => write!(f, "*"),
|
||||
StarOr::Other(x) => fmt::Display::fmt(x, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,7 +52,7 @@ impl<T: PartialEq + Eq> Eq for StarOr<T> {}
|
||||
impl<'de, T, E> Deserialize<'de> for StarOr<T>
|
||||
where
|
||||
T: FromStr<Err = E>,
|
||||
E: Display,
|
||||
E: fmt::Display,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
@ -109,11 +68,11 @@ where
|
||||
impl<'de, T, FE> Visitor<'de> for StarOrVisitor<T>
|
||||
where
|
||||
T: FromStr<Err = FE>,
|
||||
FE: Display,
|
||||
FE: fmt::Display,
|
||||
{
|
||||
type Value = StarOr<T>;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a string")
|
||||
}
|
||||
|
||||
@ -139,7 +98,7 @@ where
|
||||
|
||||
impl<T> Serialize for StarOr<T>
|
||||
where
|
||||
T: Deref<Target = str>,
|
||||
T: ToString,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
@ -147,7 +106,222 @@ where
|
||||
{
|
||||
match self {
|
||||
StarOr::Star => serializer.serialize_str("*"),
|
||||
StarOr::Other(other) => serializer.serialize_str(other.deref()),
|
||||
StarOr::Other(other) => serializer.serialize_str(&other.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> DeserializeFromValue<E> for StarOr<T>
|
||||
where
|
||||
T: FromStr,
|
||||
E: DeserializeError + MergeWithError<T::Err>,
|
||||
{
|
||||
fn deserialize_from_value<V: deserr::IntoValue>(
|
||||
value: deserr::Value<V>,
|
||||
location: deserr::ValuePointerRef,
|
||||
) -> Result<Self, E> {
|
||||
match value {
|
||||
deserr::Value::String(v) => {
|
||||
if v == "*" {
|
||||
Ok(StarOr::Star)
|
||||
} else {
|
||||
match T::from_str(&v) {
|
||||
Ok(parsed) => Ok(StarOr::Other(parsed)),
|
||||
Err(e) => Err(unwrap_any(E::merge(None, e, location))),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Err(unwrap_any(E::error::<V>(
|
||||
None,
|
||||
deserr::ErrorKind::IncorrectValueKind {
|
||||
actual: value,
|
||||
accepted: &[ValueKind::String],
|
||||
},
|
||||
location,
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type representing the content of a query parameter that can either not exist,
|
||||
/// be equal to a star (*), or another value
|
||||
///
|
||||
/// It is a convenient alternative to `Option<StarOr<T>>`.
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub enum OptionStarOr<T> {
|
||||
#[default]
|
||||
None,
|
||||
Star,
|
||||
Other(T),
|
||||
}
|
||||
|
||||
impl<T> OptionStarOr<T> {
|
||||
pub fn is_some(&self) -> bool {
|
||||
match self {
|
||||
Self::None => false,
|
||||
Self::Star => false,
|
||||
Self::Other(_) => true,
|
||||
}
|
||||
}
|
||||
pub fn merge_star_and_none(self) -> Option<T> {
|
||||
match self {
|
||||
Self::None | Self::Star => None,
|
||||
Self::Other(x) => Some(x),
|
||||
}
|
||||
}
|
||||
pub fn try_map<U, E, F: Fn(T) -> Result<U, E>>(self, map_f: F) -> Result<OptionStarOr<U>, E> {
|
||||
match self {
|
||||
OptionStarOr::None => Ok(OptionStarOr::None),
|
||||
OptionStarOr::Star => Ok(OptionStarOr::Star),
|
||||
OptionStarOr::Other(x) => map_f(x).map(OptionStarOr::Other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromQueryParameter for OptionStarOr<T>
|
||||
where
|
||||
T: FromQueryParameter,
|
||||
{
|
||||
type Err = T::Err;
|
||||
fn from_query_param(p: &str) -> Result<Self, Self::Err> {
|
||||
match p {
|
||||
"*" => Ok(OptionStarOr::Star),
|
||||
s => T::from_query_param(s).map(OptionStarOr::Other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> DeserializeFromValue<E> for OptionStarOr<T>
|
||||
where
|
||||
E: DeserializeError + MergeWithError<T::Err>,
|
||||
T: FromQueryParameter,
|
||||
{
|
||||
fn deserialize_from_value<V: deserr::IntoValue>(
|
||||
value: deserr::Value<V>,
|
||||
location: deserr::ValuePointerRef,
|
||||
) -> Result<Self, E> {
|
||||
match value {
|
||||
deserr::Value::String(s) => match s.as_str() {
|
||||
"*" => Ok(OptionStarOr::Star),
|
||||
s => match T::from_query_param(s) {
|
||||
Ok(x) => Ok(OptionStarOr::Other(x)),
|
||||
Err(e) => Err(unwrap_any(E::merge(None, e, location))),
|
||||
},
|
||||
},
|
||||
_ => Err(unwrap_any(E::error::<V>(
|
||||
None,
|
||||
deserr::ErrorKind::IncorrectValueKind {
|
||||
actual: value,
|
||||
accepted: &[ValueKind::String],
|
||||
},
|
||||
location,
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type representing the content of a query parameter that can either not exist, be equal to a star (*), or represent a list of other values
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub enum OptionStarOrList<T> {
|
||||
#[default]
|
||||
None,
|
||||
Star,
|
||||
List(Vec<T>),
|
||||
}
|
||||
|
||||
impl<T> OptionStarOrList<T> {
|
||||
pub fn is_some(&self) -> bool {
|
||||
match self {
|
||||
Self::None => false,
|
||||
Self::Star => false,
|
||||
Self::List(_) => true,
|
||||
}
|
||||
}
|
||||
pub fn map<U, F: Fn(T) -> U>(self, map_f: F) -> OptionStarOrList<U> {
|
||||
match self {
|
||||
Self::None => OptionStarOrList::None,
|
||||
Self::Star => OptionStarOrList::Star,
|
||||
Self::List(xs) => OptionStarOrList::List(xs.into_iter().map(map_f).collect()),
|
||||
}
|
||||
}
|
||||
pub fn try_map<U, E, F: Fn(T) -> Result<U, E>>(
|
||||
self,
|
||||
map_f: F,
|
||||
) -> Result<OptionStarOrList<U>, E> {
|
||||
match self {
|
||||
Self::None => Ok(OptionStarOrList::None),
|
||||
Self::Star => Ok(OptionStarOrList::Star),
|
||||
Self::List(xs) => {
|
||||
xs.into_iter().map(map_f).collect::<Result<Vec<_>, _>>().map(OptionStarOrList::List)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn merge_star_and_none(self) -> Option<Vec<T>> {
|
||||
match self {
|
||||
Self::None | Self::Star => None,
|
||||
Self::List(xs) => Some(xs),
|
||||
}
|
||||
}
|
||||
pub fn push(&mut self, el: T) {
|
||||
match self {
|
||||
Self::None => *self = Self::List(vec![el]),
|
||||
Self::Star => (),
|
||||
Self::List(xs) => xs.push(el),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> DeserializeFromValue<E> for OptionStarOrList<T>
|
||||
where
|
||||
E: DeserializeError + MergeWithError<T::Err>,
|
||||
T: FromQueryParameter,
|
||||
{
|
||||
fn deserialize_from_value<V: deserr::IntoValue>(
|
||||
value: deserr::Value<V>,
|
||||
location: deserr::ValuePointerRef,
|
||||
) -> Result<Self, E> {
|
||||
match value {
|
||||
deserr::Value::String(s) => {
|
||||
let mut error = None;
|
||||
let mut is_star = false;
|
||||
// CS::<String>::from_str is infaillible
|
||||
let cs = serde_cs::vec::CS::<String>::from_str(&s).unwrap();
|
||||
let len_cs = cs.0.len();
|
||||
let mut els = vec![];
|
||||
for (i, el_str) in cs.into_iter().enumerate() {
|
||||
if el_str == "*" {
|
||||
is_star = true;
|
||||
} else {
|
||||
match T::from_query_param(&el_str) {
|
||||
Ok(el) => {
|
||||
els.push(el);
|
||||
}
|
||||
Err(e) => {
|
||||
let location =
|
||||
if len_cs > 1 { location.push_index(i) } else { location };
|
||||
error = Some(E::merge(error, e, location)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(error) = error {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
if is_star {
|
||||
Ok(OptionStarOrList::Star)
|
||||
} else {
|
||||
Ok(OptionStarOrList::List(els))
|
||||
}
|
||||
}
|
||||
_ => Err(unwrap_any(E::error::<V>(
|
||||
None,
|
||||
deserr::ErrorKind::IncorrectValueKind {
|
||||
actual: value,
|
||||
accepted: &[ValueKind::String],
|
||||
},
|
||||
location,
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use core::fmt;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::{Display, Write};
|
||||
use std::str::FromStr;
|
||||
@ -9,7 +10,7 @@ use serde::{Deserialize, Serialize, Serializer};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::error::{Code, ResponseError};
|
||||
use crate::error::ResponseError;
|
||||
use crate::keys::Key;
|
||||
use crate::settings::{Settings, Unchecked};
|
||||
use crate::InstanceUid;
|
||||
@ -332,7 +333,7 @@ impl Display for Status {
|
||||
}
|
||||
|
||||
impl FromStr for Status {
|
||||
type Err = ResponseError;
|
||||
type Err = ParseTaskStatusError;
|
||||
|
||||
fn from_str(status: &str) -> Result<Self, Self::Err> {
|
||||
if status.eq_ignore_ascii_case("enqueued") {
|
||||
@ -346,21 +347,28 @@ impl FromStr for Status {
|
||||
} else if status.eq_ignore_ascii_case("canceled") {
|
||||
Ok(Status::Canceled)
|
||||
} else {
|
||||
Err(ResponseError::from_msg(
|
||||
format!(
|
||||
"`{}` is not a status. Available status are {}.",
|
||||
status,
|
||||
enum_iterator::all::<Status>()
|
||||
.map(|s| format!("`{s}`"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
),
|
||||
Code::BadRequest,
|
||||
))
|
||||
Err(ParseTaskStatusError(status.to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseTaskStatusError(pub String);
|
||||
impl fmt::Display for ParseTaskStatusError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"`{}` is not a valid task status. Available statuses are {}.",
|
||||
self.0,
|
||||
enum_iterator::all::<Status>()
|
||||
.map(|s| format!("`{s}`"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ParseTaskStatusError {}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Sequence)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Kind {
|
||||
@ -412,7 +420,7 @@ impl Display for Kind {
|
||||
}
|
||||
}
|
||||
impl FromStr for Kind {
|
||||
type Err = ResponseError;
|
||||
type Err = ParseTaskKindError;
|
||||
|
||||
fn from_str(kind: &str) -> Result<Self, Self::Err> {
|
||||
if kind.eq_ignore_ascii_case("indexCreation") {
|
||||
@ -438,25 +446,32 @@ impl FromStr for Kind {
|
||||
} else if kind.eq_ignore_ascii_case("snapshotCreation") {
|
||||
Ok(Kind::SnapshotCreation)
|
||||
} else {
|
||||
Err(ResponseError::from_msg(
|
||||
format!(
|
||||
"`{}` is not a type. Available types are {}.",
|
||||
kind,
|
||||
enum_iterator::all::<Kind>()
|
||||
.map(|k| format!(
|
||||
"`{}`",
|
||||
// by default serde is going to insert `"` around the value.
|
||||
serde_json::to_string(&k).unwrap().trim_matches('"')
|
||||
))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
),
|
||||
Code::BadRequest,
|
||||
))
|
||||
Err(ParseTaskKindError(kind.to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseTaskKindError(pub String);
|
||||
impl fmt::Display for ParseTaskKindError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"`{}` is not a valid task type. Available types are {}.",
|
||||
self.0,
|
||||
enum_iterator::all::<Kind>()
|
||||
.map(|k| format!(
|
||||
"`{}`",
|
||||
// by default serde is going to insert `"` around the value.
|
||||
serde_json::to_string(&k).unwrap().trim_matches('"')
|
||||
))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ParseTaskKindError {}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
pub enum Details {
|
||||
DocumentAdditionOrUpdate { received_documents: u64, indexed_documents: Option<u64> },
|
||||
|
@ -19,7 +19,7 @@ byte-unit = { version = "4.0.14", default-features = false, features = ["std", "
|
||||
bytes = "1.2.1"
|
||||
clap = { version = "4.0.9", features = ["derive", "env"] }
|
||||
crossbeam-channel = "0.5.6"
|
||||
deserr = "0.1.4"
|
||||
deserr = "0.1.5"
|
||||
dump = { path = "../dump" }
|
||||
either = "1.8.0"
|
||||
env_logger = "0.9.1"
|
||||
@ -55,7 +55,6 @@ rustls = "0.20.6"
|
||||
rustls-pemfile = "1.0.1"
|
||||
segment = { version = "0.2.1", optional = true }
|
||||
serde = { version = "1.0.145", features = ["derive"] }
|
||||
serde-cs = "0.2.4"
|
||||
serde_json = { version = "1.0.85", features = ["preserve_order"] }
|
||||
sha2 = "0.10.6"
|
||||
siphasher = "0.3.10"
|
||||
|
@ -2,7 +2,7 @@ use actix_web as aweb;
|
||||
use aweb::error::{JsonPayloadError, QueryPayloadError};
|
||||
use meilisearch_types::document_formats::{DocumentFormatError, PayloadType};
|
||||
use meilisearch_types::error::{Code, ErrorCode, ResponseError};
|
||||
use meilisearch_types::index_uid::IndexUidFormatError;
|
||||
use meilisearch_types::index_uid::{IndexUid, IndexUidFormatError};
|
||||
use serde_json::Value;
|
||||
use tokio::task::JoinError;
|
||||
|
||||
@ -27,7 +27,7 @@ pub enum MeilisearchHttpError {
|
||||
#[error("Two indexes must be given for each swap. The list `{:?}` contains {} indexes.",
|
||||
.0, .0.len()
|
||||
)]
|
||||
SwapIndexPayloadWrongLength(Vec<String>),
|
||||
SwapIndexPayloadWrongLength(Vec<IndexUid>),
|
||||
#[error(transparent)]
|
||||
IndexUid(#[from] IndexUidFormatError),
|
||||
#[error(transparent)]
|
||||
|
@ -4,14 +4,15 @@ use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use deserr::DeserializeFromValue;
|
||||
use meilisearch_auth::error::AuthControllerError;
|
||||
use meilisearch_auth::AuthController;
|
||||
use meilisearch_types::deserr::query_params::Param;
|
||||
use meilisearch_types::deserr::{DeserrJsonError, DeserrQueryParamError};
|
||||
use meilisearch_types::error::deserr_codes::*;
|
||||
use meilisearch_types::error::{Code, DeserrError, ResponseError, TakeErrorMessage};
|
||||
use meilisearch_types::error::{Code, ResponseError};
|
||||
use meilisearch_types::keys::{Action, CreateApiKey, Key, PatchApiKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::indexes::search::parse_usize_take_error_message;
|
||||
use super::PAGINATION_DEFAULT_LIMIT;
|
||||
use crate::extractors::authentication::policies::*;
|
||||
use crate::extractors::authentication::GuardedData;
|
||||
@ -36,7 +37,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
|
||||
pub async fn create_api_key(
|
||||
auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_CREATE }>, AuthController>,
|
||||
body: ValidatedJson<CreateApiKey, DeserrError>,
|
||||
body: ValidatedJson<CreateApiKey, DeserrJsonError>,
|
||||
_req: HttpRequest,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let v = body.into_inner();
|
||||
@ -50,26 +51,23 @@ pub async fn create_api_key(
|
||||
Ok(HttpResponse::Created().json(res))
|
||||
}
|
||||
|
||||
#[derive(DeserializeFromValue, Deserialize, Debug, Clone, Copy)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[derive(DeserializeFromValue, Debug, Clone, Copy)]
|
||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct ListApiKeys {
|
||||
#[serde(default)]
|
||||
#[deserr(error = DeserrError<InvalidApiKeyOffset>, default, from(&String) = parse_usize_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
||||
pub offset: usize,
|
||||
#[serde(default = "PAGINATION_DEFAULT_LIMIT")]
|
||||
#[deserr(error = DeserrError<InvalidApiKeyLimit>, default = PAGINATION_DEFAULT_LIMIT(), from(&String) = parse_usize_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
||||
pub limit: usize,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidApiKeyOffset>)]
|
||||
pub offset: Param<usize>,
|
||||
#[deserr(default = Param(PAGINATION_DEFAULT_LIMIT), error = DeserrQueryParamError<InvalidApiKeyLimit>)]
|
||||
pub limit: Param<usize>,
|
||||
}
|
||||
impl ListApiKeys {
|
||||
fn as_pagination(self) -> Pagination {
|
||||
Pagination { offset: self.offset, limit: self.limit }
|
||||
Pagination { offset: self.offset.0, limit: self.limit.0 }
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_api_keys(
|
||||
auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_GET }>, AuthController>,
|
||||
list_api_keys: QueryParameter<ListApiKeys, DeserrError>,
|
||||
list_api_keys: QueryParameter<ListApiKeys, DeserrQueryParamError>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let paginate = list_api_keys.into_inner().as_pagination();
|
||||
let page_view = tokio::task::spawn_blocking(move || -> Result<_, AuthControllerError> {
|
||||
@ -106,7 +104,7 @@ pub async fn get_api_key(
|
||||
|
||||
pub async fn patch_api_key(
|
||||
auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_UPDATE }>, AuthController>,
|
||||
body: ValidatedJson<PatchApiKey, DeserrError>,
|
||||
body: ValidatedJson<PatchApiKey, DeserrJsonError>,
|
||||
path: web::Path<AuthParam>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let key = path.into_inner().key;
|
||||
@ -172,7 +170,7 @@ impl KeyView {
|
||||
key: generated_key,
|
||||
uid: key.uid,
|
||||
actions: key.actions,
|
||||
indexes: key.indexes.into_iter().map(String::from).collect(),
|
||||
indexes: key.indexes.into_iter().map(|x| x.to_string()).collect(),
|
||||
expires_at: key.expires_at,
|
||||
created_at: key.created_at,
|
||||
updated_at: key.updated_at,
|
||||
|
@ -1,5 +1,4 @@
|
||||
use std::io::ErrorKind;
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use actix_web::http::header::CONTENT_TYPE;
|
||||
use actix_web::web::Data;
|
||||
@ -9,25 +8,25 @@ use deserr::DeserializeFromValue;
|
||||
use futures::StreamExt;
|
||||
use index_scheduler::IndexScheduler;
|
||||
use log::debug;
|
||||
use meilisearch_types::deserr::query_params::Param;
|
||||
use meilisearch_types::deserr::{DeserrJsonError, DeserrQueryParamError};
|
||||
use meilisearch_types::document_formats::{read_csv, read_json, read_ndjson, PayloadType};
|
||||
use meilisearch_types::error::deserr_codes::*;
|
||||
use meilisearch_types::error::{DeserrError, ResponseError, TakeErrorMessage};
|
||||
use meilisearch_types::error::ResponseError;
|
||||
use meilisearch_types::heed::RoTxn;
|
||||
use meilisearch_types::index_uid::IndexUid;
|
||||
use meilisearch_types::milli::update::IndexDocumentsMethod;
|
||||
use meilisearch_types::star_or::StarOr;
|
||||
use meilisearch_types::star_or::OptionStarOrList;
|
||||
use meilisearch_types::tasks::KindWithContent;
|
||||
use meilisearch_types::{milli, Document, Index};
|
||||
use mime::Mime;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::Deserialize;
|
||||
use serde_cs::vec::CS;
|
||||
use serde_json::Value;
|
||||
use tempfile::tempfile;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncSeekExt, AsyncWriteExt, BufWriter};
|
||||
|
||||
use super::search::parse_usize_take_error_message;
|
||||
use crate::analytics::{Analytics, DocumentDeletionKind};
|
||||
use crate::error::MeilisearchHttpError;
|
||||
use crate::error::PayloadError::ReceivePayload;
|
||||
@ -36,7 +35,7 @@ use crate::extractors::authentication::GuardedData;
|
||||
use crate::extractors::payload::Payload;
|
||||
use crate::extractors::query_parameters::QueryParameter;
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
use crate::routes::{fold_star_or, PaginationView, SummarizedTaskView};
|
||||
use crate::routes::{PaginationView, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT};
|
||||
|
||||
static ACCEPTED_CONTENT_TYPE: Lazy<Vec<String>> = Lazy::new(|| {
|
||||
vec!["application/json".to_string(), "application/x-ndjson".to_string(), "text/csv".to_string()]
|
||||
@ -81,23 +80,26 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, DeserializeFromValue)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[derive(Debug, DeserializeFromValue)]
|
||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct GetDocument {
|
||||
#[deserr(error = DeserrError<InvalidDocumentFields>)]
|
||||
fields: Option<CS<StarOr<String>>>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidDocumentFields>)]
|
||||
fields: OptionStarOrList<String>,
|
||||
}
|
||||
|
||||
pub async fn get_document(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_GET }>, Data<IndexScheduler>>,
|
||||
path: web::Path<DocumentParam>,
|
||||
params: QueryParameter<GetDocument, DeserrError>,
|
||||
document_param: web::Path<DocumentParam>,
|
||||
params: QueryParameter<GetDocument, DeserrQueryParamError>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let GetDocument { fields } = params.into_inner();
|
||||
let attributes_to_retrieve = fields.and_then(fold_star_or);
|
||||
let DocumentParam { index_uid, document_id } = document_param.into_inner();
|
||||
let index_uid = IndexUid::try_from(index_uid)?;
|
||||
|
||||
let index = index_scheduler.index(&path.index_uid)?;
|
||||
let document = retrieve_document(&index, &path.document_id, attributes_to_retrieve)?;
|
||||
let GetDocument { fields } = params.into_inner();
|
||||
let attributes_to_retrieve = fields.merge_star_and_none();
|
||||
|
||||
let index = index_scheduler.index(&index_uid)?;
|
||||
let document = retrieve_document(&index, &document_id, attributes_to_retrieve)?;
|
||||
debug!("returns: {:?}", document);
|
||||
Ok(HttpResponse::Ok().json(document))
|
||||
}
|
||||
@ -108,60 +110,68 @@ pub async fn delete_document(
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let DocumentParam { index_uid, document_id } = path.into_inner();
|
||||
let index_uid = IndexUid::try_from(index_uid)?;
|
||||
|
||||
analytics.delete_documents(DocumentDeletionKind::PerDocumentId, &req);
|
||||
|
||||
let DocumentParam { document_id, index_uid } = path.into_inner();
|
||||
let task = KindWithContent::DocumentDeletion { index_uid, documents_ids: vec![document_id] };
|
||||
let task = KindWithContent::DocumentDeletion {
|
||||
index_uid: index_uid.to_string(),
|
||||
documents_ids: vec![document_id],
|
||||
};
|
||||
let task: SummarizedTaskView =
|
||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||
debug!("returns: {:?}", task);
|
||||
Ok(HttpResponse::Accepted().json(task))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, DeserializeFromValue)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[derive(Debug, DeserializeFromValue)]
|
||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct BrowseQuery {
|
||||
#[deserr(error = DeserrError<InvalidDocumentFields>, default, from(&String) = parse_usize_take_error_message -> TakeErrorMessage<ParseIntError>)]
|
||||
offset: usize,
|
||||
#[deserr(error = DeserrError<InvalidDocumentLimit>, default = crate::routes::PAGINATION_DEFAULT_LIMIT(), from(&String) = parse_usize_take_error_message -> TakeErrorMessage<ParseIntError>)]
|
||||
limit: usize,
|
||||
#[deserr(error = DeserrError<InvalidDocumentLimit>)]
|
||||
fields: Option<CS<StarOr<String>>>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidDocumentFields>)]
|
||||
offset: Param<usize>,
|
||||
#[deserr(default = Param(PAGINATION_DEFAULT_LIMIT), error = DeserrQueryParamError<InvalidDocumentLimit>)]
|
||||
limit: Param<usize>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidDocumentLimit>)]
|
||||
fields: OptionStarOrList<String>,
|
||||
}
|
||||
|
||||
pub async fn get_all_documents(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_GET }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
params: QueryParameter<BrowseQuery, DeserrError>,
|
||||
params: QueryParameter<BrowseQuery, DeserrQueryParamError>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
debug!("called with params: {:?}", params);
|
||||
let BrowseQuery { limit, offset, fields } = params.into_inner();
|
||||
let attributes_to_retrieve = fields.and_then(fold_star_or);
|
||||
let attributes_to_retrieve = fields.merge_star_and_none();
|
||||
|
||||
let index = index_scheduler.index(&index_uid)?;
|
||||
let (total, documents) = retrieve_documents(&index, offset, limit, attributes_to_retrieve)?;
|
||||
let (total, documents) = retrieve_documents(&index, offset.0, limit.0, attributes_to_retrieve)?;
|
||||
|
||||
let ret = PaginationView::new(offset, limit, total as usize, documents);
|
||||
let ret = PaginationView::new(offset.0, limit.0, total as usize, documents);
|
||||
|
||||
debug!("returns: {:?}", ret);
|
||||
Ok(HttpResponse::Ok().json(ret))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, DeserializeFromValue)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct UpdateDocumentsQuery {
|
||||
#[deserr(error = DeserrError<InvalidIndexPrimaryKey>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidIndexPrimaryKey>)]
|
||||
pub primary_key: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn add_documents(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_ADD }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
params: QueryParameter<UpdateDocumentsQuery, DeserrError>,
|
||||
params: QueryParameter<UpdateDocumentsQuery, DeserrJsonError>,
|
||||
body: Payload,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
|
||||
debug!("called with params: {:?}", params);
|
||||
let params = params.into_inner();
|
||||
|
||||
@ -171,7 +181,7 @@ pub async fn add_documents(
|
||||
let task = document_addition(
|
||||
extract_mime_type(&req)?,
|
||||
index_scheduler,
|
||||
index_uid.into_inner(),
|
||||
index_uid,
|
||||
params.primary_key,
|
||||
body,
|
||||
IndexDocumentsMethod::ReplaceDocuments,
|
||||
@ -184,14 +194,15 @@ pub async fn add_documents(
|
||||
|
||||
pub async fn update_documents(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_ADD }>, Data<IndexScheduler>>,
|
||||
path: web::Path<String>,
|
||||
params: QueryParameter<UpdateDocumentsQuery, DeserrError>,
|
||||
index_uid: web::Path<String>,
|
||||
params: QueryParameter<UpdateDocumentsQuery, DeserrJsonError>,
|
||||
body: Payload,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
|
||||
debug!("called with params: {:?}", params);
|
||||
let index_uid = path.into_inner();
|
||||
|
||||
analytics.update_documents(¶ms, index_scheduler.index(&index_uid).is_err(), &req);
|
||||
|
||||
@ -213,7 +224,7 @@ pub async fn update_documents(
|
||||
async fn document_addition(
|
||||
mime_type: Option<Mime>,
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_ADD }>, Data<IndexScheduler>>,
|
||||
index_uid: String,
|
||||
index_uid: IndexUid,
|
||||
primary_key: Option<String>,
|
||||
mut body: Payload,
|
||||
method: IndexDocumentsMethod,
|
||||
@ -234,9 +245,6 @@ async fn document_addition(
|
||||
}
|
||||
};
|
||||
|
||||
// is your indexUid valid?
|
||||
let index_uid = IndexUid::try_from(index_uid)?.into_inner();
|
||||
|
||||
let (uuid, mut update_file) = index_scheduler.create_update_file()?;
|
||||
|
||||
let temp_file = match tempfile() {
|
||||
@ -312,7 +320,7 @@ async fn document_addition(
|
||||
documents_count,
|
||||
primary_key,
|
||||
allow_index_creation,
|
||||
index_uid,
|
||||
index_uid: index_uid.to_string(),
|
||||
};
|
||||
|
||||
let scheduler = index_scheduler.clone();
|
||||
@ -330,12 +338,13 @@ async fn document_addition(
|
||||
|
||||
pub async fn delete_documents(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_DELETE }>, Data<IndexScheduler>>,
|
||||
path: web::Path<String>,
|
||||
index_uid: web::Path<String>,
|
||||
body: web::Json<Vec<Value>>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
debug!("called with params: {:?}", body);
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
|
||||
analytics.delete_documents(DocumentDeletionKind::PerBatch, &req);
|
||||
|
||||
@ -345,7 +354,7 @@ pub async fn delete_documents(
|
||||
.collect();
|
||||
|
||||
let task =
|
||||
KindWithContent::DocumentDeletion { index_uid: path.into_inner(), documents_ids: ids };
|
||||
KindWithContent::DocumentDeletion { index_uid: index_uid.to_string(), documents_ids: ids };
|
||||
let task: SummarizedTaskView =
|
||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||
|
||||
@ -355,13 +364,14 @@ pub async fn delete_documents(
|
||||
|
||||
pub async fn clear_all_documents(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::DOCUMENTS_DELETE }>, Data<IndexScheduler>>,
|
||||
path: web::Path<String>,
|
||||
index_uid: web::Path<String>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
analytics.delete_documents(DocumentDeletionKind::ClearAll, &req);
|
||||
|
||||
let task = KindWithContent::DocumentClear { index_uid: path.into_inner() };
|
||||
let task = KindWithContent::DocumentClear { index_uid: index_uid.to_string() };
|
||||
let task: SummarizedTaskView =
|
||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||
|
||||
|
@ -5,16 +5,17 @@ use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use deserr::{DeserializeError, DeserializeFromValue, ValuePointerRef};
|
||||
use index_scheduler::IndexScheduler;
|
||||
use log::debug;
|
||||
use meilisearch_types::deserr::query_params::Param;
|
||||
use meilisearch_types::deserr::{DeserrJsonError, DeserrQueryParamError};
|
||||
use meilisearch_types::error::deserr_codes::*;
|
||||
use meilisearch_types::error::{unwrap_any, Code, DeserrError, ResponseError, TakeErrorMessage};
|
||||
use meilisearch_types::error::{unwrap_any, Code, ResponseError};
|
||||
use meilisearch_types::index_uid::IndexUid;
|
||||
use meilisearch_types::milli::{self, FieldDistribution, Index};
|
||||
use meilisearch_types::tasks::KindWithContent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use self::search::parse_usize_take_error_message;
|
||||
use super::{Pagination, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT};
|
||||
use crate::analytics::Analytics;
|
||||
use crate::extractors::authentication::policies::*;
|
||||
@ -48,7 +49,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IndexView {
|
||||
pub uid: String,
|
||||
@ -71,26 +72,23 @@ impl IndexView {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeserializeFromValue, Deserialize, Debug, Clone, Copy)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[derive(DeserializeFromValue, Debug, Clone, Copy)]
|
||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct ListIndexes {
|
||||
#[serde(default)]
|
||||
#[deserr(error = DeserrError<InvalidIndexOffset>, default, from(&String) = parse_usize_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
||||
pub offset: usize,
|
||||
#[serde(default = "PAGINATION_DEFAULT_LIMIT")]
|
||||
#[deserr(error = DeserrError<InvalidIndexLimit>, default = PAGINATION_DEFAULT_LIMIT(), from(&String) = parse_usize_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
||||
pub limit: usize,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidIndexOffset>)]
|
||||
pub offset: Param<usize>,
|
||||
#[deserr(default = Param(PAGINATION_DEFAULT_LIMIT), error = DeserrQueryParamError<InvalidIndexLimit>)]
|
||||
pub limit: Param<usize>,
|
||||
}
|
||||
impl ListIndexes {
|
||||
fn as_pagination(self) -> Pagination {
|
||||
Pagination { offset: self.offset, limit: self.limit }
|
||||
Pagination { offset: self.offset.0, limit: self.limit.0 }
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_indexes(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_GET }>, Data<IndexScheduler>>,
|
||||
paginate: QueryParameter<ListIndexes, DeserrError>,
|
||||
paginate: QueryParameter<ListIndexes, DeserrQueryParamError>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let search_rules = &index_scheduler.filters().search_rules;
|
||||
let indexes: Vec<_> = index_scheduler.indexes()?;
|
||||
@ -107,22 +105,21 @@ pub async fn list_indexes(
|
||||
}
|
||||
|
||||
#[derive(DeserializeFromValue, Debug)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct IndexCreateRequest {
|
||||
#[deserr(error = DeserrError<InvalidIndexUid>, missing_field_error = DeserrError::missing_index_uid)]
|
||||
uid: String,
|
||||
#[deserr(error = DeserrError<InvalidIndexPrimaryKey>)]
|
||||
#[deserr(error = DeserrJsonError<InvalidIndexUid>, missing_field_error = DeserrJsonError::missing_index_uid)]
|
||||
uid: IndexUid,
|
||||
#[deserr(default, error = DeserrJsonError<InvalidIndexPrimaryKey>)]
|
||||
primary_key: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn create_index(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_CREATE }>, Data<IndexScheduler>>,
|
||||
body: ValidatedJson<IndexCreateRequest, DeserrError>,
|
||||
body: ValidatedJson<IndexCreateRequest, DeserrJsonError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let IndexCreateRequest { primary_key, uid } = body.into_inner();
|
||||
let uid = IndexUid::try_from(uid)?.into_inner();
|
||||
|
||||
let allow_index_creation = index_scheduler.filters().search_rules.is_index_authorized(&uid);
|
||||
if allow_index_creation {
|
||||
@ -132,7 +129,7 @@ pub async fn create_index(
|
||||
Some(&req),
|
||||
);
|
||||
|
||||
let task = KindWithContent::IndexCreation { index_uid: uid, primary_key };
|
||||
let task = KindWithContent::IndexCreation { index_uid: uid.to_string(), primary_key };
|
||||
let task: SummarizedTaskView =
|
||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||
|
||||
@ -146,8 +143,8 @@ fn deny_immutable_fields_index(
|
||||
field: &str,
|
||||
accepted: &[&str],
|
||||
location: ValuePointerRef,
|
||||
) -> DeserrError {
|
||||
let mut error = unwrap_any(DeserrError::<BadRequest>::error::<Infallible>(
|
||||
) -> DeserrJsonError {
|
||||
let mut error = unwrap_any(DeserrJsonError::<BadRequest>::error::<Infallible>(
|
||||
None,
|
||||
deserr::ErrorKind::UnknownKey { key: field, accepted },
|
||||
location,
|
||||
@ -162,9 +159,9 @@ fn deny_immutable_fields_index(
|
||||
error
|
||||
}
|
||||
#[derive(DeserializeFromValue, Debug)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields = deny_immutable_fields_index)]
|
||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields = deny_immutable_fields_index)]
|
||||
pub struct UpdateIndexRequest {
|
||||
#[deserr(error = DeserrError<InvalidIndexPrimaryKey>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidIndexPrimaryKey>)]
|
||||
primary_key: Option<String>,
|
||||
}
|
||||
|
||||
@ -172,6 +169,8 @@ pub async fn get_index(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_GET }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
|
||||
let index = index_scheduler.index(&index_uid)?;
|
||||
let index_view = IndexView::new(index_uid.into_inner(), &index)?;
|
||||
|
||||
@ -182,12 +181,13 @@ pub async fn get_index(
|
||||
|
||||
pub async fn update_index(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_UPDATE }>, Data<IndexScheduler>>,
|
||||
path: web::Path<String>,
|
||||
body: ValidatedJson<UpdateIndexRequest, DeserrError>,
|
||||
index_uid: web::Path<String>,
|
||||
body: ValidatedJson<UpdateIndexRequest, DeserrJsonError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
debug!("called with params: {:?}", body);
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
let body = body.into_inner();
|
||||
analytics.publish(
|
||||
"Index Updated".to_string(),
|
||||
@ -196,7 +196,7 @@ pub async fn update_index(
|
||||
);
|
||||
|
||||
let task = KindWithContent::IndexUpdate {
|
||||
index_uid: path.into_inner(),
|
||||
index_uid: index_uid.into_inner(),
|
||||
primary_key: body.primary_key,
|
||||
};
|
||||
|
||||
@ -211,6 +211,7 @@ pub async fn delete_index(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_DELETE }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
let task = KindWithContent::IndexDeletion { index_uid: index_uid.into_inner() };
|
||||
let task: SummarizedTaskView =
|
||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||
@ -224,6 +225,7 @@ pub async fn get_index_stats(
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
analytics.publish("Stats Seen".to_string(), json!({ "per_index_uid": true }), Some(&req));
|
||||
|
||||
let stats = IndexStats::new((*index_scheduler).clone(), index_uid.into_inner())?;
|
||||
|
@ -1,13 +1,14 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use index_scheduler::IndexScheduler;
|
||||
use log::debug;
|
||||
use meilisearch_auth::IndexSearchRules;
|
||||
use meilisearch_types::deserr::query_params::Param;
|
||||
use meilisearch_types::deserr::{DeserrJsonError, DeserrQueryParamError};
|
||||
use meilisearch_types::error::deserr_codes::*;
|
||||
use meilisearch_types::error::{DeserrError, ResponseError, TakeErrorMessage};
|
||||
use serde_cs::vec::CS;
|
||||
use meilisearch_types::error::ResponseError;
|
||||
use meilisearch_types::index_uid::IndexUid;
|
||||
use meilisearch_types::serde_cs::vec::CS;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::analytics::{Analytics, SearchAggregator};
|
||||
@ -16,7 +17,6 @@ use crate::extractors::authentication::GuardedData;
|
||||
use crate::extractors::json::ValidatedJson;
|
||||
use crate::extractors::query_parameters::QueryParameter;
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
use crate::routes::from_string_to_option_take_error_message;
|
||||
use crate::search::{
|
||||
perform_search, MatchingStrategy, SearchQuery, DEFAULT_CROP_LENGTH, DEFAULT_CROP_MARKER,
|
||||
DEFAULT_HIGHLIGHT_POST_TAG, DEFAULT_HIGHLIGHT_PRE_TAG, DEFAULT_SEARCH_LIMIT,
|
||||
@ -31,54 +31,42 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn parse_usize_take_error_message(
|
||||
s: &str,
|
||||
) -> Result<usize, TakeErrorMessage<std::num::ParseIntError>> {
|
||||
usize::from_str(s).map_err(TakeErrorMessage)
|
||||
}
|
||||
|
||||
pub fn parse_bool_take_error_message(
|
||||
s: &str,
|
||||
) -> Result<bool, TakeErrorMessage<std::str::ParseBoolError>> {
|
||||
s.parse().map_err(TakeErrorMessage)
|
||||
}
|
||||
|
||||
#[derive(Debug, deserr::DeserializeFromValue)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct SearchQueryGet {
|
||||
#[deserr(error = DeserrError<InvalidSearchQ>)]
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchQ>)]
|
||||
q: Option<String>,
|
||||
#[deserr(error = DeserrError<InvalidSearchOffset>, default = DEFAULT_SEARCH_OFFSET(), from(&String) = parse_usize_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
||||
offset: usize,
|
||||
#[deserr(error = DeserrError<InvalidSearchLimit>, default = DEFAULT_SEARCH_LIMIT(), from(&String) = parse_usize_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
||||
limit: usize,
|
||||
#[deserr(error = DeserrError<InvalidSearchPage>, from(&String) = from_string_to_option_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
||||
page: Option<usize>,
|
||||
#[deserr(error = DeserrError<InvalidSearchHitsPerPage>, from(&String) = from_string_to_option_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
||||
hits_per_page: Option<usize>,
|
||||
#[deserr(error = DeserrError<InvalidSearchAttributesToRetrieve>)]
|
||||
#[deserr(default = Param(DEFAULT_SEARCH_OFFSET()), error = DeserrQueryParamError<InvalidSearchOffset>)]
|
||||
offset: Param<usize>,
|
||||
#[deserr(default = Param(DEFAULT_SEARCH_LIMIT()), error = DeserrQueryParamError<InvalidSearchLimit>)]
|
||||
limit: Param<usize>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchPage>)]
|
||||
page: Option<Param<usize>>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchHitsPerPage>)]
|
||||
hits_per_page: Option<Param<usize>>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchAttributesToRetrieve>)]
|
||||
attributes_to_retrieve: Option<CS<String>>,
|
||||
#[deserr(error = DeserrError<InvalidSearchAttributesToCrop>)]
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchAttributesToCrop>)]
|
||||
attributes_to_crop: Option<CS<String>>,
|
||||
#[deserr(error = DeserrError<InvalidSearchCropLength>, default = DEFAULT_CROP_LENGTH(), from(&String) = parse_usize_take_error_message -> TakeErrorMessage<std::num::ParseIntError>)]
|
||||
crop_length: usize,
|
||||
#[deserr(error = DeserrError<InvalidSearchAttributesToHighlight>)]
|
||||
#[deserr(default = Param(DEFAULT_CROP_LENGTH()), error = DeserrQueryParamError<InvalidSearchCropLength>)]
|
||||
crop_length: Param<usize>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchAttributesToHighlight>)]
|
||||
attributes_to_highlight: Option<CS<String>>,
|
||||
#[deserr(error = DeserrError<InvalidSearchFilter>)]
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchFilter>)]
|
||||
filter: Option<String>,
|
||||
#[deserr(error = DeserrError<InvalidSearchSort>)]
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchSort>)]
|
||||
sort: Option<String>,
|
||||
#[deserr(error = DeserrError<InvalidSearchShowMatchesPosition>, default, from(&String) = parse_bool_take_error_message -> TakeErrorMessage<std::str::ParseBoolError>)]
|
||||
show_matches_position: bool,
|
||||
#[deserr(error = DeserrError<InvalidSearchFacets>)]
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchShowMatchesPosition>)]
|
||||
show_matches_position: Param<bool>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchFacets>)]
|
||||
facets: Option<CS<String>>,
|
||||
#[deserr(error = DeserrError<InvalidSearchHighlightPreTag>, default = DEFAULT_HIGHLIGHT_PRE_TAG())]
|
||||
#[deserr( default = DEFAULT_HIGHLIGHT_PRE_TAG(), error = DeserrQueryParamError<InvalidSearchHighlightPreTag>)]
|
||||
highlight_pre_tag: String,
|
||||
#[deserr(error = DeserrError<InvalidSearchHighlightPostTag>, default = DEFAULT_HIGHLIGHT_POST_TAG())]
|
||||
#[deserr( default = DEFAULT_HIGHLIGHT_POST_TAG(), error = DeserrQueryParamError<InvalidSearchHighlightPostTag>)]
|
||||
highlight_post_tag: String,
|
||||
#[deserr(error = DeserrError<InvalidSearchCropMarker>, default = DEFAULT_CROP_MARKER())]
|
||||
#[deserr(default = DEFAULT_CROP_MARKER(), error = DeserrQueryParamError<InvalidSearchCropMarker>)]
|
||||
crop_marker: String,
|
||||
#[deserr(error = DeserrError<InvalidSearchMatchingStrategy>, default)]
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchMatchingStrategy>)]
|
||||
matching_strategy: MatchingStrategy,
|
||||
}
|
||||
|
||||
@ -94,17 +82,17 @@ impl From<SearchQueryGet> for SearchQuery {
|
||||
|
||||
Self {
|
||||
q: other.q,
|
||||
offset: other.offset,
|
||||
limit: other.limit,
|
||||
page: other.page,
|
||||
hits_per_page: other.hits_per_page,
|
||||
offset: other.offset.0,
|
||||
limit: other.limit.0,
|
||||
page: other.page.as_deref().copied(),
|
||||
hits_per_page: other.hits_per_page.as_deref().copied(),
|
||||
attributes_to_retrieve: other.attributes_to_retrieve.map(|o| o.into_iter().collect()),
|
||||
attributes_to_crop: other.attributes_to_crop.map(|o| o.into_iter().collect()),
|
||||
crop_length: other.crop_length,
|
||||
crop_length: other.crop_length.0,
|
||||
attributes_to_highlight: other.attributes_to_highlight.map(|o| o.into_iter().collect()),
|
||||
filter,
|
||||
sort: other.sort.map(|attr| fix_sort_query_parameters(&attr)),
|
||||
show_matches_position: other.show_matches_position,
|
||||
show_matches_position: other.show_matches_position.0,
|
||||
facets: other.facets.map(|o| o.into_iter().collect()),
|
||||
highlight_pre_tag: other.highlight_pre_tag,
|
||||
highlight_post_tag: other.highlight_post_tag,
|
||||
@ -162,11 +150,13 @@ fn fix_sort_query_parameters(sort_query: &str) -> Vec<String> {
|
||||
pub async fn search_with_url_query(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::SEARCH }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
params: QueryParameter<SearchQueryGet, DeserrError>,
|
||||
params: QueryParameter<SearchQueryGet, DeserrQueryParamError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
debug!("called with params: {:?}", params);
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
|
||||
let mut query: SearchQuery = params.into_inner().into();
|
||||
|
||||
// Tenant token search_rules.
|
||||
@ -194,10 +184,12 @@ pub async fn search_with_url_query(
|
||||
pub async fn search_with_post(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::SEARCH }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
params: ValidatedJson<SearchQuery, DeserrError>,
|
||||
params: ValidatedJson<SearchQuery, DeserrJsonError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
|
||||
let mut query = params.into_inner();
|
||||
debug!("search called with params: {:?}", query);
|
||||
|
||||
|
@ -2,7 +2,8 @@ use actix_web::web::Data;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use index_scheduler::IndexScheduler;
|
||||
use log::debug;
|
||||
use meilisearch_types::error::{DeserrError, ResponseError};
|
||||
use meilisearch_types::deserr::DeserrJsonError;
|
||||
use meilisearch_types::error::ResponseError;
|
||||
use meilisearch_types::index_uid::IndexUid;
|
||||
use meilisearch_types::settings::{settings, RankingRuleView, Settings, Unchecked};
|
||||
use meilisearch_types::tasks::KindWithContent;
|
||||
@ -40,12 +41,14 @@ macro_rules! make_setting_route {
|
||||
>,
|
||||
index_uid: web::Path<String>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
|
||||
let new_settings = Settings { $attr: Setting::Reset.into(), ..Default::default() };
|
||||
|
||||
let allow_index_creation = index_scheduler.filters().allow_index_creation;
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?.into_inner();
|
||||
|
||||
let task = KindWithContent::SettingsUpdate {
|
||||
index_uid,
|
||||
index_uid: index_uid.to_string(),
|
||||
new_settings: Box::new(new_settings),
|
||||
is_deletion: true,
|
||||
allow_index_creation,
|
||||
@ -69,6 +72,8 @@ macro_rules! make_setting_route {
|
||||
req: HttpRequest,
|
||||
$analytics_var: web::Data<dyn Analytics>,
|
||||
) -> std::result::Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
|
||||
let body = body.into_inner();
|
||||
|
||||
$analytics(&body, &req);
|
||||
@ -82,9 +87,9 @@ macro_rules! make_setting_route {
|
||||
};
|
||||
|
||||
let allow_index_creation = index_scheduler.filters().allow_index_creation;
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?.into_inner();
|
||||
|
||||
let task = KindWithContent::SettingsUpdate {
|
||||
index_uid,
|
||||
index_uid: index_uid.to_string(),
|
||||
new_settings: Box::new(new_settings),
|
||||
is_deletion: false,
|
||||
allow_index_creation,
|
||||
@ -105,6 +110,8 @@ macro_rules! make_setting_route {
|
||||
>,
|
||||
index_uid: actix_web::web::Path<String>,
|
||||
) -> std::result::Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
|
||||
let index = index_scheduler.index(&index_uid)?;
|
||||
let rtxn = index.read_txn()?;
|
||||
let settings = settings(&index, &rtxn)?;
|
||||
@ -130,7 +137,7 @@ make_setting_route!(
|
||||
"/filterable-attributes",
|
||||
put,
|
||||
std::collections::BTreeSet<String>,
|
||||
meilisearch_types::error::DeserrError<
|
||||
meilisearch_types::deserr::DeserrJsonError<
|
||||
meilisearch_types::error::deserr_codes::InvalidSettingsFilterableAttributes,
|
||||
>,
|
||||
filterable_attributes,
|
||||
@ -156,7 +163,7 @@ make_setting_route!(
|
||||
"/sortable-attributes",
|
||||
put,
|
||||
std::collections::BTreeSet<String>,
|
||||
meilisearch_types::error::DeserrError<
|
||||
meilisearch_types::deserr::DeserrJsonError<
|
||||
meilisearch_types::error::deserr_codes::InvalidSettingsSortableAttributes,
|
||||
>,
|
||||
sortable_attributes,
|
||||
@ -182,7 +189,7 @@ make_setting_route!(
|
||||
"/displayed-attributes",
|
||||
put,
|
||||
Vec<String>,
|
||||
meilisearch_types::error::DeserrError<
|
||||
meilisearch_types::deserr::DeserrJsonError<
|
||||
meilisearch_types::error::deserr_codes::InvalidSettingsDisplayedAttributes,
|
||||
>,
|
||||
displayed_attributes,
|
||||
@ -208,7 +215,7 @@ make_setting_route!(
|
||||
"/typo-tolerance",
|
||||
patch,
|
||||
meilisearch_types::settings::TypoSettings,
|
||||
meilisearch_types::error::DeserrError<
|
||||
meilisearch_types::deserr::DeserrJsonError<
|
||||
meilisearch_types::error::deserr_codes::InvalidSettingsTypoTolerance,
|
||||
>,
|
||||
typo_tolerance,
|
||||
@ -253,7 +260,7 @@ make_setting_route!(
|
||||
"/searchable-attributes",
|
||||
put,
|
||||
Vec<String>,
|
||||
meilisearch_types::error::DeserrError<
|
||||
meilisearch_types::deserr::DeserrJsonError<
|
||||
meilisearch_types::error::deserr_codes::InvalidSettingsSearchableAttributes,
|
||||
>,
|
||||
searchable_attributes,
|
||||
@ -279,7 +286,7 @@ make_setting_route!(
|
||||
"/stop-words",
|
||||
put,
|
||||
std::collections::BTreeSet<String>,
|
||||
meilisearch_types::error::DeserrError<
|
||||
meilisearch_types::deserr::DeserrJsonError<
|
||||
meilisearch_types::error::deserr_codes::InvalidSettingsStopWords,
|
||||
>,
|
||||
stop_words,
|
||||
@ -304,7 +311,7 @@ make_setting_route!(
|
||||
"/synonyms",
|
||||
put,
|
||||
std::collections::BTreeMap<String, Vec<String>>,
|
||||
meilisearch_types::error::DeserrError<
|
||||
meilisearch_types::deserr::DeserrJsonError<
|
||||
meilisearch_types::error::deserr_codes::InvalidSettingsSynonyms,
|
||||
>,
|
||||
synonyms,
|
||||
@ -329,7 +336,7 @@ make_setting_route!(
|
||||
"/distinct-attribute",
|
||||
put,
|
||||
String,
|
||||
meilisearch_types::error::DeserrError<
|
||||
meilisearch_types::deserr::DeserrJsonError<
|
||||
meilisearch_types::error::deserr_codes::InvalidSettingsDistinctAttribute,
|
||||
>,
|
||||
distinct_attribute,
|
||||
@ -353,7 +360,7 @@ make_setting_route!(
|
||||
"/ranking-rules",
|
||||
put,
|
||||
Vec<meilisearch_types::settings::RankingRuleView>,
|
||||
meilisearch_types::error::DeserrError<
|
||||
meilisearch_types::deserr::DeserrJsonError<
|
||||
meilisearch_types::error::deserr_codes::InvalidSettingsRankingRules,
|
||||
>,
|
||||
ranking_rules,
|
||||
@ -384,7 +391,7 @@ make_setting_route!(
|
||||
"/faceting",
|
||||
patch,
|
||||
meilisearch_types::settings::FacetingSettings,
|
||||
meilisearch_types::error::DeserrError<
|
||||
meilisearch_types::deserr::DeserrJsonError<
|
||||
meilisearch_types::error::deserr_codes::InvalidSettingsFaceting,
|
||||
>,
|
||||
faceting,
|
||||
@ -409,7 +416,7 @@ make_setting_route!(
|
||||
"/pagination",
|
||||
patch,
|
||||
meilisearch_types::settings::PaginationSettings,
|
||||
meilisearch_types::error::DeserrError<
|
||||
meilisearch_types::deserr::DeserrJsonError<
|
||||
meilisearch_types::error::deserr_codes::InvalidSettingsPagination,
|
||||
>,
|
||||
pagination,
|
||||
@ -461,10 +468,12 @@ generate_configure!(
|
||||
pub async fn update_all(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::SETTINGS_UPDATE }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
body: ValidatedJson<Settings<Unchecked>, DeserrError>,
|
||||
body: ValidatedJson<Settings<Unchecked>, DeserrJsonError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
|
||||
let new_settings = body.into_inner();
|
||||
|
||||
analytics.publish(
|
||||
@ -570,6 +579,8 @@ pub async fn get_all(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::SETTINGS_GET }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
|
||||
let index = index_scheduler.index(&index_uid)?;
|
||||
let rtxn = index.read_txn()?;
|
||||
let new_settings = settings(&index, &rtxn)?;
|
||||
@ -581,6 +592,8 @@ pub async fn delete_all(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::SETTINGS_UPDATE }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
|
||||
let new_settings = Settings::cleared().into_unchecked();
|
||||
|
||||
let allow_index_creation = index_scheduler.filters().allow_index_creation;
|
||||
|
@ -1,13 +1,11 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use index_scheduler::{IndexScheduler, Query};
|
||||
use log::debug;
|
||||
use meilisearch_types::error::{ResponseError, TakeErrorMessage};
|
||||
use meilisearch_types::error::ResponseError;
|
||||
use meilisearch_types::settings::{Settings, Unchecked};
|
||||
use meilisearch_types::star_or::StarOr;
|
||||
use meilisearch_types::tasks::{Kind, Status, Task, TaskId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
@ -35,37 +33,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
.service(web::scope("/swap-indexes").configure(swap_indexes::configure));
|
||||
}
|
||||
|
||||
/// Extracts the raw values from the `StarOr` types and
|
||||
/// return None if a `StarOr::Star` is encountered.
|
||||
pub fn fold_star_or<T, O>(content: impl IntoIterator<Item = StarOr<T>>) -> Option<O>
|
||||
where
|
||||
O: FromIterator<T>,
|
||||
{
|
||||
content
|
||||
.into_iter()
|
||||
.map(|value| match value {
|
||||
StarOr::Star => None,
|
||||
StarOr::Other(val) => Some(val),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn from_string_to_option<T, E>(input: &str) -> Result<Option<T>, E>
|
||||
where
|
||||
T: FromStr<Err = E>,
|
||||
{
|
||||
Ok(Some(input.parse()?))
|
||||
}
|
||||
pub fn from_string_to_option_take_error_message<T, E>(
|
||||
input: &str,
|
||||
) -> Result<Option<T>, TakeErrorMessage<E>>
|
||||
where
|
||||
T: FromStr<Err = E>,
|
||||
{
|
||||
Ok(Some(input.parse().map_err(TakeErrorMessage)?))
|
||||
}
|
||||
|
||||
const PAGINATION_DEFAULT_LIMIT: fn() -> usize = || 20;
|
||||
const PAGINATION_DEFAULT_LIMIT: usize = 20;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -2,8 +2,10 @@ use actix_web::web::Data;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use deserr::DeserializeFromValue;
|
||||
use index_scheduler::IndexScheduler;
|
||||
use meilisearch_types::deserr::DeserrJsonError;
|
||||
use meilisearch_types::error::deserr_codes::InvalidSwapIndexes;
|
||||
use meilisearch_types::error::{DeserrError, ResponseError};
|
||||
use meilisearch_types::error::ResponseError;
|
||||
use meilisearch_types::index_uid::IndexUid;
|
||||
use meilisearch_types::tasks::{IndexSwap, KindWithContent};
|
||||
use serde_json::json;
|
||||
|
||||
@ -20,15 +22,15 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
}
|
||||
|
||||
#[derive(DeserializeFromValue, Debug, Clone, PartialEq, Eq)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct SwapIndexesPayload {
|
||||
#[deserr(error = DeserrError<InvalidSwapIndexes>)]
|
||||
indexes: Vec<String>,
|
||||
#[deserr(error = DeserrJsonError<InvalidSwapIndexes>, missing_field_error = DeserrJsonError::missing_swap_indexes)]
|
||||
indexes: Vec<IndexUid>,
|
||||
}
|
||||
|
||||
pub async fn swap_indexes(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_SWAP }>, Data<IndexScheduler>>,
|
||||
params: ValidatedJson<Vec<SwapIndexesPayload>, DeserrError>,
|
||||
params: ValidatedJson<Vec<SwapIndexesPayload>, DeserrJsonError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
@ -44,6 +46,7 @@ pub async fn swap_indexes(
|
||||
|
||||
let mut swaps = vec![];
|
||||
for SwapIndexesPayload { indexes } in params.into_iter() {
|
||||
// TODO: switch to deserr
|
||||
let (lhs, rhs) = match indexes.as_slice() {
|
||||
[lhs, rhs] => (lhs, rhs),
|
||||
_ => {
|
||||
@ -53,7 +56,7 @@ pub async fn swap_indexes(
|
||||
if !search_rules.is_index_authorized(lhs) || !search_rules.is_index_authorized(rhs) {
|
||||
return Err(AuthenticationError::InvalidToken.into());
|
||||
}
|
||||
swaps.push(IndexSwap { indexes: (lhs.clone(), rhs.clone()) });
|
||||
swaps.push(IndexSwap { indexes: (lhs.to_string(), rhs.to_string()) });
|
||||
}
|
||||
|
||||
let task = KindWithContent::IndexSwap { swaps };
|
||||
|
@ -1,34 +1,32 @@
|
||||
use std::num::ParseIntError;
|
||||
use std::str::FromStr;
|
||||
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use deserr::DeserializeFromValue;
|
||||
use index_scheduler::{IndexScheduler, Query, TaskId};
|
||||
use meilisearch_types::deserr::query_params::Param;
|
||||
use meilisearch_types::deserr::DeserrQueryParamError;
|
||||
use meilisearch_types::error::deserr_codes::*;
|
||||
use meilisearch_types::error::{DeserrError, ResponseError, TakeErrorMessage};
|
||||
use meilisearch_types::error::{InvalidTaskDateError, ResponseError};
|
||||
use meilisearch_types::index_uid::IndexUid;
|
||||
use meilisearch_types::settings::{Settings, Unchecked};
|
||||
use meilisearch_types::star_or::StarOr;
|
||||
use meilisearch_types::star_or::{OptionStarOr, OptionStarOrList};
|
||||
use meilisearch_types::tasks::{
|
||||
serialize_duration, Details, IndexSwap, Kind, KindWithContent, Status, Task,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_cs::vec::CS;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::macros::format_description;
|
||||
use time::{Date, Duration, OffsetDateTime, Time};
|
||||
use tokio::task;
|
||||
|
||||
use super::{fold_star_or, SummarizedTaskView};
|
||||
use super::SummarizedTaskView;
|
||||
use crate::analytics::Analytics;
|
||||
use crate::extractors::authentication::policies::*;
|
||||
use crate::extractors::authentication::GuardedData;
|
||||
use crate::extractors::query_parameters::QueryParameter;
|
||||
use crate::extractors::sequential_extractor::SeqHandler;
|
||||
|
||||
const DEFAULT_LIMIT: fn() -> u32 = || 20;
|
||||
const DEFAULT_LIMIT: u32 = 20;
|
||||
|
||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
@ -164,162 +162,157 @@ impl From<Details> for DetailsView {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_option_cs<T: FromStr>(
|
||||
s: Option<CS<String>>,
|
||||
) -> Result<Option<Vec<T>>, TakeErrorMessage<T::Err>> {
|
||||
if let Some(s) = s {
|
||||
s.into_iter()
|
||||
.map(|s| T::from_str(&s))
|
||||
.collect::<Result<Vec<T>, T::Err>>()
|
||||
.map_err(TakeErrorMessage)
|
||||
.map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
#[derive(Debug, DeserializeFromValue)]
|
||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct TasksFilterQuery {
|
||||
#[deserr(default = Param(DEFAULT_LIMIT), error = DeserrQueryParamError<InvalidTaskLimit>)]
|
||||
pub limit: Param<u32>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskFrom>)]
|
||||
pub from: Option<Param<TaskId>>,
|
||||
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskUids>)]
|
||||
pub uids: OptionStarOrList<u32>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskCanceledBy>)]
|
||||
pub canceled_by: OptionStarOrList<u32>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskTypes>)]
|
||||
pub types: OptionStarOrList<Kind>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskStatuses>)]
|
||||
pub statuses: OptionStarOrList<Status>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidIndexUid>)]
|
||||
pub index_uids: OptionStarOrList<IndexUid>,
|
||||
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterEnqueuedAt>, from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
|
||||
pub after_enqueued_at: OptionStarOr<OffsetDateTime>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeEnqueuedAt>, from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
|
||||
pub before_enqueued_at: OptionStarOr<OffsetDateTime>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterStartedAt>, from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
|
||||
pub after_started_at: OptionStarOr<OffsetDateTime>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeStartedAt>, from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
|
||||
pub before_started_at: OptionStarOr<OffsetDateTime>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterFinishedAt>, from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
|
||||
pub after_finished_at: OptionStarOr<OffsetDateTime>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeFinishedAt>, from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
|
||||
pub before_finished_at: OptionStarOr<OffsetDateTime>,
|
||||
}
|
||||
fn parse_option_cs_star_or<T: FromStr>(
|
||||
s: Option<CS<StarOr<String>>>,
|
||||
) -> Result<Option<Vec<T>>, TakeErrorMessage<T::Err>> {
|
||||
if let Some(s) = s.and_then(fold_star_or) as Option<Vec<String>> {
|
||||
s.into_iter()
|
||||
.map(|s| T::from_str(&s))
|
||||
.collect::<Result<Vec<T>, T::Err>>()
|
||||
.map_err(TakeErrorMessage)
|
||||
.map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
fn parse_option_str<T: FromStr>(s: Option<String>) -> Result<Option<T>, TakeErrorMessage<T::Err>> {
|
||||
if let Some(s) = s {
|
||||
T::from_str(&s).map_err(TakeErrorMessage).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
impl TasksFilterQuery {
|
||||
fn into_query(self) -> Query {
|
||||
Query {
|
||||
limit: Some(self.limit.0),
|
||||
from: self.from.as_deref().copied(),
|
||||
statuses: self.statuses.merge_star_and_none(),
|
||||
types: self.types.merge_star_and_none(),
|
||||
index_uids: self.index_uids.map(|x| x.to_string()).merge_star_and_none(),
|
||||
uids: self.uids.merge_star_and_none(),
|
||||
canceled_by: self.canceled_by.merge_star_and_none(),
|
||||
before_enqueued_at: self.before_enqueued_at.merge_star_and_none(),
|
||||
after_enqueued_at: self.after_enqueued_at.merge_star_and_none(),
|
||||
before_started_at: self.before_started_at.merge_star_and_none(),
|
||||
after_started_at: self.after_started_at.merge_star_and_none(),
|
||||
before_finished_at: self.before_finished_at.merge_star_and_none(),
|
||||
after_finished_at: self.after_finished_at.merge_star_and_none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_str<T: FromStr>(s: String) -> Result<T, TakeErrorMessage<T::Err>> {
|
||||
T::from_str(&s).map_err(TakeErrorMessage)
|
||||
impl TaskDeletionOrCancelationQuery {
|
||||
fn is_empty(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
TaskDeletionOrCancelationQuery {
|
||||
uids: OptionStarOrList::None,
|
||||
canceled_by: OptionStarOrList::None,
|
||||
types: OptionStarOrList::None,
|
||||
statuses: OptionStarOrList::None,
|
||||
index_uids: OptionStarOrList::None,
|
||||
after_enqueued_at: OptionStarOr::None,
|
||||
before_enqueued_at: OptionStarOr::None,
|
||||
after_started_at: OptionStarOr::None,
|
||||
before_started_at: OptionStarOr::None,
|
||||
after_finished_at: OptionStarOr::None,
|
||||
before_finished_at: OptionStarOr::None
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, DeserializeFromValue)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct TasksFilterQuery {
|
||||
#[deserr(error = DeserrError<InvalidTaskLimit>, default = DEFAULT_LIMIT(), from(String) = parse_str::<u32> -> TakeErrorMessage<ParseIntError>)]
|
||||
pub limit: u32,
|
||||
#[deserr(error = DeserrError<InvalidTaskFrom>, from(Option<String>) = parse_option_str::<TaskId> -> TakeErrorMessage<ParseIntError>)]
|
||||
pub from: Option<TaskId>,
|
||||
|
||||
#[deserr(error = DeserrError<InvalidTaskUids>, from(Option<CS<String>>) = parse_option_cs::<u32> -> TakeErrorMessage<ParseIntError>)]
|
||||
pub uids: Option<Vec<u32>>,
|
||||
#[deserr(error = DeserrError<InvalidTaskCanceledBy>, from(Option<CS<String>>) = parse_option_cs::<u32> -> TakeErrorMessage<ParseIntError>)]
|
||||
pub canceled_by: Option<Vec<u32>>,
|
||||
#[deserr(error = DeserrError<InvalidTaskTypes>, default = None, from(Option<CS<StarOr<String>>>) = parse_option_cs_star_or::<Kind> -> TakeErrorMessage<ResponseError>)]
|
||||
pub types: Option<Vec<Kind>>,
|
||||
#[deserr(error = DeserrError<InvalidTaskStatuses>, default = None, from(Option<CS<StarOr<String>>>) = parse_option_cs_star_or::<Status> -> TakeErrorMessage<ResponseError>)]
|
||||
pub statuses: Option<Vec<Status>>,
|
||||
#[deserr(error = DeserrError<InvalidIndexUid>, default = None, from(Option<CS<StarOr<String>>>) = parse_option_cs_star_or::<IndexUid> -> TakeErrorMessage<ResponseError>)]
|
||||
pub index_uids: Option<Vec<IndexUid>>,
|
||||
|
||||
#[deserr(error = DeserrError<InvalidTaskAfterEnqueuedAt>, default = None, from(Option<String>) = deserialize_date_after -> TakeErrorMessage<InvalidTaskDateError>)]
|
||||
pub after_enqueued_at: Option<OffsetDateTime>,
|
||||
#[deserr(error = DeserrError<InvalidTaskBeforeEnqueuedAt>, default = None, from(Option<String>) = deserialize_date_before -> TakeErrorMessage<InvalidTaskDateError>)]
|
||||
pub before_enqueued_at: Option<OffsetDateTime>,
|
||||
#[deserr(error = DeserrError<InvalidTaskAfterStartedAt>, default = None, from(Option<String>) = deserialize_date_after -> TakeErrorMessage<InvalidTaskDateError>)]
|
||||
pub after_started_at: Option<OffsetDateTime>,
|
||||
#[deserr(error = DeserrError<InvalidTaskBeforeStartedAt>, default = None, from(Option<String>) = deserialize_date_before -> TakeErrorMessage<InvalidTaskDateError>)]
|
||||
pub before_started_at: Option<OffsetDateTime>,
|
||||
#[deserr(error = DeserrError<InvalidTaskAfterFinishedAt>, default = None, from(Option<String>) = deserialize_date_after -> TakeErrorMessage<InvalidTaskDateError>)]
|
||||
pub after_finished_at: Option<OffsetDateTime>,
|
||||
#[deserr(error = DeserrError<InvalidTaskBeforeFinishedAt>, default = None, from(Option<String>) = deserialize_date_before -> TakeErrorMessage<InvalidTaskDateError>)]
|
||||
pub before_finished_at: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, DeserializeFromValue)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct TaskDeletionOrCancelationQuery {
|
||||
#[deserr(error = DeserrError<InvalidTaskUids>, from(Option<CS<String>>) = parse_option_cs::<u32> -> TakeErrorMessage<ParseIntError>)]
|
||||
pub uids: Option<Vec<u32>>,
|
||||
#[deserr(error = DeserrError<InvalidTaskCanceledBy>, from(Option<CS<String>>) = parse_option_cs::<u32> -> TakeErrorMessage<ParseIntError>)]
|
||||
pub canceled_by: Option<Vec<u32>>,
|
||||
#[deserr(error = DeserrError<InvalidTaskTypes>, default = None, from(Option<CS<StarOr<String>>>) = parse_option_cs_star_or::<Kind> -> TakeErrorMessage<ResponseError>)]
|
||||
pub types: Option<Vec<Kind>>,
|
||||
#[deserr(error = DeserrError<InvalidTaskStatuses>, default = None, from(Option<CS<StarOr<String>>>) = parse_option_cs_star_or::<Status> -> TakeErrorMessage<ResponseError>)]
|
||||
pub statuses: Option<Vec<Status>>,
|
||||
#[deserr(error = DeserrError<InvalidIndexUid>, default = None, from(Option<CS<StarOr<String>>>) = parse_option_cs_star_or::<IndexUid> -> TakeErrorMessage<ResponseError>)]
|
||||
pub index_uids: Option<Vec<IndexUid>>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskUids>)]
|
||||
pub uids: OptionStarOrList<u32>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskCanceledBy>)]
|
||||
pub canceled_by: OptionStarOrList<u32>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskTypes>)]
|
||||
pub types: OptionStarOrList<Kind>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskStatuses>)]
|
||||
pub statuses: OptionStarOrList<Status>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidIndexUid>)]
|
||||
pub index_uids: OptionStarOrList<IndexUid>,
|
||||
|
||||
#[deserr(error = DeserrError<InvalidTaskAfterEnqueuedAt>, default = None, from(Option<String>) = deserialize_date_after -> TakeErrorMessage<InvalidTaskDateError>)]
|
||||
pub after_enqueued_at: Option<OffsetDateTime>,
|
||||
#[deserr(error = DeserrError<InvalidTaskBeforeEnqueuedAt>, default = None, from(Option<String>) = deserialize_date_before -> TakeErrorMessage<InvalidTaskDateError>)]
|
||||
pub before_enqueued_at: Option<OffsetDateTime>,
|
||||
#[deserr(error = DeserrError<InvalidTaskAfterStartedAt>, default = None, from(Option<String>) = deserialize_date_after -> TakeErrorMessage<InvalidTaskDateError>)]
|
||||
pub after_started_at: Option<OffsetDateTime>,
|
||||
#[deserr(error = DeserrError<InvalidTaskBeforeStartedAt>, default = None, from(Option<String>) = deserialize_date_before -> TakeErrorMessage<InvalidTaskDateError>)]
|
||||
pub before_started_at: Option<OffsetDateTime>,
|
||||
#[deserr(error = DeserrError<InvalidTaskAfterFinishedAt>, default = None, from(Option<String>) = deserialize_date_after -> TakeErrorMessage<InvalidTaskDateError>)]
|
||||
pub after_finished_at: Option<OffsetDateTime>,
|
||||
#[deserr(error = DeserrError<InvalidTaskBeforeFinishedAt>, default = None, from(Option<String>) = deserialize_date_before -> TakeErrorMessage<InvalidTaskDateError>)]
|
||||
pub before_finished_at: Option<OffsetDateTime>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterEnqueuedAt>, from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
|
||||
pub after_enqueued_at: OptionStarOr<OffsetDateTime>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeEnqueuedAt>, from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
|
||||
pub before_enqueued_at: OptionStarOr<OffsetDateTime>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterStartedAt>, from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
|
||||
pub after_started_at: OptionStarOr<OffsetDateTime>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeStartedAt>, from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
|
||||
pub before_started_at: OptionStarOr<OffsetDateTime>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskAfterFinishedAt>, from(OptionStarOr<String>) = deserialize_date_after -> InvalidTaskDateError)]
|
||||
pub after_finished_at: OptionStarOr<OffsetDateTime>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidTaskBeforeFinishedAt>, from(OptionStarOr<String>) = deserialize_date_before -> InvalidTaskDateError)]
|
||||
pub before_finished_at: OptionStarOr<OffsetDateTime>,
|
||||
}
|
||||
impl TaskDeletionOrCancelationQuery {
|
||||
fn into_query(self) -> Query {
|
||||
Query {
|
||||
limit: None,
|
||||
from: None,
|
||||
statuses: self.statuses.merge_star_and_none(),
|
||||
types: self.types.merge_star_and_none(),
|
||||
index_uids: self.index_uids.map(|x| x.to_string()).merge_star_and_none(),
|
||||
uids: self.uids.merge_star_and_none(),
|
||||
canceled_by: self.canceled_by.merge_star_and_none(),
|
||||
before_enqueued_at: self.before_enqueued_at.merge_star_and_none(),
|
||||
after_enqueued_at: self.after_enqueued_at.merge_star_and_none(),
|
||||
before_started_at: self.before_started_at.merge_star_and_none(),
|
||||
after_started_at: self.after_started_at.merge_star_and_none(),
|
||||
before_finished_at: self.before_finished_at.merge_star_and_none(),
|
||||
after_finished_at: self.after_finished_at.merge_star_and_none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn cancel_tasks(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::TASKS_CANCEL }>, Data<IndexScheduler>>,
|
||||
params: QueryParameter<TaskDeletionOrCancelationQuery, DeserrError>,
|
||||
params: QueryParameter<TaskDeletionOrCancelationQuery, DeserrQueryParamError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let TaskDeletionOrCancelationQuery {
|
||||
types,
|
||||
uids,
|
||||
canceled_by,
|
||||
statuses,
|
||||
index_uids,
|
||||
after_enqueued_at,
|
||||
before_enqueued_at,
|
||||
after_started_at,
|
||||
before_started_at,
|
||||
after_finished_at,
|
||||
before_finished_at,
|
||||
} = params.into_inner();
|
||||
let params = params.into_inner();
|
||||
|
||||
if params.is_empty() {
|
||||
return Err(index_scheduler::Error::TaskCancelationWithEmptyQuery.into());
|
||||
}
|
||||
|
||||
analytics.publish(
|
||||
"Tasks Canceled".to_string(),
|
||||
json!({
|
||||
"filtered_by_uid": uids.is_some(),
|
||||
"filtered_by_index_uid": index_uids.is_some(),
|
||||
"filtered_by_type": types.is_some(),
|
||||
"filtered_by_status": statuses.is_some(),
|
||||
"filtered_by_canceled_by": canceled_by.is_some(),
|
||||
"filtered_by_before_enqueued_at": before_enqueued_at.is_some(),
|
||||
"filtered_by_after_enqueued_at": after_enqueued_at.is_some(),
|
||||
"filtered_by_before_started_at": before_started_at.is_some(),
|
||||
"filtered_by_after_started_at": after_started_at.is_some(),
|
||||
"filtered_by_before_finished_at": before_finished_at.is_some(),
|
||||
"filtered_by_after_finished_at": after_finished_at.is_some(),
|
||||
"filtered_by_uid": params.uids.is_some(),
|
||||
"filtered_by_index_uid": params.index_uids.is_some(),
|
||||
"filtered_by_type": params.types.is_some(),
|
||||
"filtered_by_status": params.statuses.is_some(),
|
||||
"filtered_by_canceled_by": params.canceled_by.is_some(),
|
||||
"filtered_by_before_enqueued_at": params.before_enqueued_at.is_some(),
|
||||
"filtered_by_after_enqueued_at": params.after_enqueued_at.is_some(),
|
||||
"filtered_by_before_started_at": params.before_started_at.is_some(),
|
||||
"filtered_by_after_started_at": params.after_started_at.is_some(),
|
||||
"filtered_by_before_finished_at": params.before_finished_at.is_some(),
|
||||
"filtered_by_after_finished_at": params.after_finished_at.is_some(),
|
||||
}),
|
||||
Some(&req),
|
||||
);
|
||||
|
||||
let query = Query {
|
||||
limit: None,
|
||||
from: None,
|
||||
statuses,
|
||||
types,
|
||||
index_uids: index_uids.map(|xs| xs.into_iter().map(|s| s.to_string()).collect()),
|
||||
uids,
|
||||
canceled_by,
|
||||
before_enqueued_at,
|
||||
after_enqueued_at,
|
||||
before_started_at,
|
||||
after_started_at,
|
||||
before_finished_at,
|
||||
after_finished_at,
|
||||
};
|
||||
|
||||
if query.is_empty() {
|
||||
return Err(index_scheduler::Error::TaskCancelationWithEmptyQuery.into());
|
||||
}
|
||||
let query = params.into_query();
|
||||
|
||||
let tasks = index_scheduler.get_task_ids_from_authorized_indexes(
|
||||
&index_scheduler.read_txn()?,
|
||||
@ -337,62 +330,34 @@ async fn cancel_tasks(
|
||||
|
||||
async fn delete_tasks(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::TASKS_DELETE }>, Data<IndexScheduler>>,
|
||||
params: QueryParameter<TaskDeletionOrCancelationQuery, DeserrError>,
|
||||
params: QueryParameter<TaskDeletionOrCancelationQuery, DeserrQueryParamError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let TaskDeletionOrCancelationQuery {
|
||||
types,
|
||||
uids,
|
||||
canceled_by,
|
||||
statuses,
|
||||
index_uids,
|
||||
let params = params.into_inner();
|
||||
|
||||
after_enqueued_at,
|
||||
before_enqueued_at,
|
||||
after_started_at,
|
||||
before_started_at,
|
||||
after_finished_at,
|
||||
before_finished_at,
|
||||
} = params.into_inner();
|
||||
if params.is_empty() {
|
||||
return Err(index_scheduler::Error::TaskDeletionWithEmptyQuery.into());
|
||||
}
|
||||
|
||||
analytics.publish(
|
||||
"Tasks Deleted".to_string(),
|
||||
json!({
|
||||
"filtered_by_uid": uids.is_some(),
|
||||
"filtered_by_index_uid": index_uids.is_some(),
|
||||
"filtered_by_type": types.is_some(),
|
||||
"filtered_by_status": statuses.is_some(),
|
||||
"filtered_by_canceled_by": canceled_by.is_some(),
|
||||
"filtered_by_before_enqueued_at": before_enqueued_at.is_some(),
|
||||
"filtered_by_after_enqueued_at": after_enqueued_at.is_some(),
|
||||
"filtered_by_before_started_at": before_started_at.is_some(),
|
||||
"filtered_by_after_started_at": after_started_at.is_some(),
|
||||
"filtered_by_before_finished_at": before_finished_at.is_some(),
|
||||
"filtered_by_after_finished_at": after_finished_at.is_some(),
|
||||
"filtered_by_uid": params.uids.is_some(),
|
||||
"filtered_by_index_uid": params.index_uids.is_some(),
|
||||
"filtered_by_type": params.types.is_some(),
|
||||
"filtered_by_status": params.statuses.is_some(),
|
||||
"filtered_by_canceled_by": params.canceled_by.is_some(),
|
||||
"filtered_by_before_enqueued_at": params.before_enqueued_at.is_some(),
|
||||
"filtered_by_after_enqueued_at": params.after_enqueued_at.is_some(),
|
||||
"filtered_by_before_started_at": params.before_started_at.is_some(),
|
||||
"filtered_by_after_started_at": params.after_started_at.is_some(),
|
||||
"filtered_by_before_finished_at": params.before_finished_at.is_some(),
|
||||
"filtered_by_after_finished_at": params.after_finished_at.is_some(),
|
||||
}),
|
||||
Some(&req),
|
||||
);
|
||||
|
||||
let query = Query {
|
||||
limit: None,
|
||||
from: None,
|
||||
statuses,
|
||||
types,
|
||||
index_uids: index_uids.map(|xs| xs.into_iter().map(|s| s.to_string()).collect()),
|
||||
uids,
|
||||
canceled_by,
|
||||
after_enqueued_at,
|
||||
before_enqueued_at,
|
||||
after_started_at,
|
||||
before_started_at,
|
||||
after_finished_at,
|
||||
before_finished_at,
|
||||
};
|
||||
|
||||
if query.is_empty() {
|
||||
return Err(index_scheduler::Error::TaskDeletionWithEmptyQuery.into());
|
||||
}
|
||||
let query = params.into_query();
|
||||
|
||||
let tasks = index_scheduler.get_task_ids_from_authorized_indexes(
|
||||
&index_scheduler.read_txn()?,
|
||||
@ -418,47 +383,17 @@ pub struct AllTasks {
|
||||
|
||||
async fn get_tasks(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::TASKS_GET }>, Data<IndexScheduler>>,
|
||||
params: QueryParameter<TasksFilterQuery, DeserrError>,
|
||||
params: QueryParameter<TasksFilterQuery, DeserrQueryParamError>,
|
||||
req: HttpRequest,
|
||||
analytics: web::Data<dyn Analytics>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let params = params.into_inner();
|
||||
let mut params = params.into_inner();
|
||||
analytics.get_tasks(¶ms, &req);
|
||||
|
||||
let TasksFilterQuery {
|
||||
types,
|
||||
uids,
|
||||
canceled_by,
|
||||
statuses,
|
||||
index_uids,
|
||||
limit,
|
||||
from,
|
||||
after_enqueued_at,
|
||||
before_enqueued_at,
|
||||
after_started_at,
|
||||
before_started_at,
|
||||
after_finished_at,
|
||||
before_finished_at,
|
||||
} = params;
|
||||
|
||||
// We +1 just to know if there is more after this "page" or not.
|
||||
let limit = limit.saturating_add(1);
|
||||
|
||||
let query = index_scheduler::Query {
|
||||
limit: Some(limit),
|
||||
from,
|
||||
statuses,
|
||||
types,
|
||||
index_uids: index_uids.map(|xs| xs.into_iter().map(|s| s.to_string()).collect()),
|
||||
uids,
|
||||
canceled_by,
|
||||
before_enqueued_at,
|
||||
after_enqueued_at,
|
||||
before_started_at,
|
||||
after_started_at,
|
||||
before_finished_at,
|
||||
after_finished_at,
|
||||
};
|
||||
params.limit.0 = params.limit.0.saturating_add(1);
|
||||
let limit = params.limit.0;
|
||||
let query = params.into_query();
|
||||
|
||||
let mut tasks_results: Vec<TaskView> = index_scheduler
|
||||
.get_tasks_from_authorized_indexes(
|
||||
@ -524,7 +459,7 @@ pub enum DeserializeDateOption {
|
||||
pub fn deserialize_date(
|
||||
value: &str,
|
||||
option: DeserializeDateOption,
|
||||
) -> std::result::Result<OffsetDateTime, TakeErrorMessage<InvalidTaskDateError>> {
|
||||
) -> std::result::Result<OffsetDateTime, InvalidTaskDateError> {
|
||||
// We can't parse using time's rfc3339 format, since then we won't know what part of the
|
||||
// datetime was not explicitly specified, and thus we won't be able to increment it to the
|
||||
// next step.
|
||||
@ -546,54 +481,41 @@ pub fn deserialize_date(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(TakeErrorMessage(InvalidTaskDateError(value.to_owned())))
|
||||
Err(InvalidTaskDateError(value.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_date_before(
|
||||
value: Option<String>,
|
||||
) -> std::result::Result<Option<OffsetDateTime>, TakeErrorMessage<InvalidTaskDateError>> {
|
||||
if let Some(value) = value {
|
||||
let date = deserialize_date(&value, DeserializeDateOption::Before)?;
|
||||
Ok(Some(date))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
pub fn deserialize_date_after(
|
||||
value: Option<String>,
|
||||
) -> std::result::Result<Option<OffsetDateTime>, TakeErrorMessage<InvalidTaskDateError>> {
|
||||
if let Some(value) = value {
|
||||
let date = deserialize_date(&value, DeserializeDateOption::After)?;
|
||||
Ok(Some(date))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
value: OptionStarOr<String>,
|
||||
) -> std::result::Result<OptionStarOr<OffsetDateTime>, InvalidTaskDateError> {
|
||||
value.try_map(|x| deserialize_date(&x, DeserializeDateOption::After))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidTaskDateError(String);
|
||||
impl std::fmt::Display for InvalidTaskDateError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "`{}` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.", self.0)
|
||||
}
|
||||
pub fn deserialize_date_before(
|
||||
value: OptionStarOr<String>,
|
||||
) -> std::result::Result<OptionStarOr<OffsetDateTime>, InvalidTaskDateError> {
|
||||
value.try_map(|x| deserialize_date(&x, DeserializeDateOption::Before))
|
||||
}
|
||||
impl std::error::Error for InvalidTaskDateError {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use deserr::DeserializeFromValue;
|
||||
use meili_snap::snapshot;
|
||||
use meilisearch_types::error::DeserrError;
|
||||
use meilisearch_types::deserr::DeserrQueryParamError;
|
||||
use meilisearch_types::error::{Code, ResponseError};
|
||||
|
||||
use crate::extractors::query_parameters::QueryParameter;
|
||||
use crate::routes::tasks::{TaskDeletionOrCancelationQuery, TasksFilterQuery};
|
||||
|
||||
fn deserr_query_params<T>(j: &str) -> Result<T, actix_web::Error>
|
||||
fn deserr_query_params<T>(j: &str) -> Result<T, ResponseError>
|
||||
where
|
||||
T: DeserializeFromValue<DeserrError>,
|
||||
T: DeserializeFromValue<DeserrQueryParamError>,
|
||||
{
|
||||
QueryParameter::<T, DeserrError>::from_query(j).map(|p| p.0)
|
||||
let value = serde_urlencoded::from_str::<serde_json::Value>(j)
|
||||
.map_err(|e| ResponseError::from_msg(e.to_string(), Code::BadRequest))?;
|
||||
|
||||
match deserr::deserialize::<_, _, DeserrQueryParamError>(value) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(e) => Err(ResponseError::from(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -602,65 +524,113 @@ mod tests {
|
||||
let params = "afterEnqueuedAt=2021-12-03&beforeEnqueuedAt=2021-12-03&afterStartedAt=2021-12-03&beforeStartedAt=2021-12-03&afterFinishedAt=2021-12-03&beforeFinishedAt=2021-12-03";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
|
||||
snapshot!(format!("{:?}", query.after_enqueued_at.unwrap()), @"2021-12-04 0:00:00.0 +00:00:00");
|
||||
snapshot!(format!("{:?}", query.before_enqueued_at.unwrap()), @"2021-12-03 0:00:00.0 +00:00:00");
|
||||
snapshot!(format!("{:?}", query.after_started_at.unwrap()), @"2021-12-04 0:00:00.0 +00:00:00");
|
||||
snapshot!(format!("{:?}", query.before_started_at.unwrap()), @"2021-12-03 0:00:00.0 +00:00:00");
|
||||
snapshot!(format!("{:?}", query.after_finished_at.unwrap()), @"2021-12-04 0:00:00.0 +00:00:00");
|
||||
snapshot!(format!("{:?}", query.before_finished_at.unwrap()), @"2021-12-03 0:00:00.0 +00:00:00");
|
||||
snapshot!(format!("{:?}", query.after_enqueued_at), @"Other(2021-12-04 0:00:00.0 +00:00:00)");
|
||||
snapshot!(format!("{:?}", query.before_enqueued_at), @"Other(2021-12-03 0:00:00.0 +00:00:00)");
|
||||
snapshot!(format!("{:?}", query.after_started_at), @"Other(2021-12-04 0:00:00.0 +00:00:00)");
|
||||
snapshot!(format!("{:?}", query.before_started_at), @"Other(2021-12-03 0:00:00.0 +00:00:00)");
|
||||
snapshot!(format!("{:?}", query.after_finished_at), @"Other(2021-12-04 0:00:00.0 +00:00:00)");
|
||||
snapshot!(format!("{:?}", query.before_finished_at), @"Other(2021-12-03 0:00:00.0 +00:00:00)");
|
||||
}
|
||||
{
|
||||
let params =
|
||||
"afterEnqueuedAt=2021-12-03T23:45:23Z&beforeEnqueuedAt=2021-12-03T23:45:23Z";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query.after_enqueued_at.unwrap()), @"2021-12-03 23:45:23.0 +00:00:00");
|
||||
snapshot!(format!("{:?}", query.before_enqueued_at.unwrap()), @"2021-12-03 23:45:23.0 +00:00:00");
|
||||
snapshot!(format!("{:?}", query.after_enqueued_at), @"Other(2021-12-03 23:45:23.0 +00:00:00)");
|
||||
snapshot!(format!("{:?}", query.before_enqueued_at), @"Other(2021-12-03 23:45:23.0 +00:00:00)");
|
||||
}
|
||||
{
|
||||
let params = "afterEnqueuedAt=1997-11-12T09:55:06-06:20";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query.after_enqueued_at.unwrap()), @"1997-11-12 9:55:06.0 -06:20:00");
|
||||
snapshot!(format!("{:?}", query.after_enqueued_at), @"Other(1997-11-12 9:55:06.0 -06:20:00)");
|
||||
}
|
||||
{
|
||||
let params = "afterEnqueuedAt=1997-11-12T09:55:06%2B00:00";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query.after_enqueued_at.unwrap()), @"1997-11-12 9:55:06.0 +00:00:00");
|
||||
snapshot!(format!("{:?}", query.after_enqueued_at), @"Other(1997-11-12 9:55:06.0 +00:00:00)");
|
||||
}
|
||||
{
|
||||
let params = "afterEnqueuedAt=1997-11-12T09:55:06.200000300Z";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query.after_enqueued_at.unwrap()), @"1997-11-12 9:55:06.2000003 +00:00:00");
|
||||
snapshot!(format!("{:?}", query.after_enqueued_at), @"Other(1997-11-12 9:55:06.2000003 +00:00:00)");
|
||||
}
|
||||
{
|
||||
// Stars are allowed in date fields as well
|
||||
let params = "afterEnqueuedAt=*&beforeStartedAt=*&afterFinishedAt=*&beforeFinishedAt=*&afterStartedAt=*&beforeEnqueuedAt=*";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query), @"TaskDeletionOrCancelationQuery { uids: None, canceled_by: None, types: None, statuses: None, index_uids: None, after_enqueued_at: Star, before_enqueued_at: Star, after_started_at: Star, before_started_at: Star, after_finished_at: Star, before_finished_at: Star }");
|
||||
}
|
||||
{
|
||||
let params = "afterFinishedAt=2021";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"`2021` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.afterFinishedAt`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `afterFinishedAt`: `2021` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_after_finished_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-after-finished-at"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
{
|
||||
let params = "beforeFinishedAt=2021";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"`2021` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeFinishedAt`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `beforeFinishedAt`: `2021` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_finished_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-finished-at"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
{
|
||||
let params = "afterEnqueuedAt=2021-12";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"`2021-12` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.afterEnqueuedAt`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `afterEnqueuedAt`: `2021-12` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_after_enqueued_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-after-enqueued-at"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
|
||||
{
|
||||
let params = "beforeEnqueuedAt=2021-12-03T23";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"`2021-12-03T23` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeEnqueuedAt`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `beforeEnqueuedAt`: `2021-12-03T23` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_enqueued_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-enqueued-at"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
{
|
||||
let params = "afterStartedAt=2021-12-03T23:45";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"`2021-12-03T23:45` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.afterStartedAt`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `afterStartedAt`: `2021-12-03T23:45` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_after_started_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-after-started-at"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
{
|
||||
let params = "beforeStartedAt=2021-12-03T23:45";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"`2021-12-03T23:45` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeStartedAt`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `beforeStartedAt`: `2021-12-03T23:45` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_started_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-started-at"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
}
|
||||
|
||||
@ -669,22 +639,48 @@ mod tests {
|
||||
{
|
||||
let params = "uids=78,1,12,73";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query.uids.unwrap()), @"[78, 1, 12, 73]");
|
||||
snapshot!(format!("{:?}", query.uids), @"List([78, 1, 12, 73])");
|
||||
}
|
||||
{
|
||||
let params = "uids=1";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query.uids.unwrap()), @"[1]");
|
||||
snapshot!(format!("{:?}", query.uids), @"List([1])");
|
||||
}
|
||||
{
|
||||
let params = "uids=cat,*,dog";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `uids[0]`: could not parse `cat` as a positive integer",
|
||||
"code": "invalid_task_uids",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-uids"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
{
|
||||
let params = "uids=78,hello,world";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"invalid digit found in string at `.uids`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `uids[1]`: could not parse `hello` as a positive integer",
|
||||
"code": "invalid_task_uids",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-uids"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
{
|
||||
let params = "uids=cat";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"invalid digit found in string at `.uids`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `uids`: could not parse `cat` as a positive integer",
|
||||
"code": "invalid_task_uids",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-uids"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
}
|
||||
|
||||
@ -693,17 +689,24 @@ mod tests {
|
||||
{
|
||||
let params = "statuses=succeeded,failed,enqueued,processing,canceled";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query.statuses.unwrap()), @"[Succeeded, Failed, Enqueued, Processing, Canceled]");
|
||||
snapshot!(format!("{:?}", query.statuses), @"List([Succeeded, Failed, Enqueued, Processing, Canceled])");
|
||||
}
|
||||
{
|
||||
let params = "statuses=enqueued";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query.statuses.unwrap()), @"[Enqueued]");
|
||||
snapshot!(format!("{:?}", query.statuses), @"List([Enqueued])");
|
||||
}
|
||||
{
|
||||
let params = "statuses=finished";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"`finished` is not a status. Available status are `enqueued`, `processing`, `succeeded`, `failed`, `canceled`. at `.statuses`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `statuses`: `finished` is not a valid task status. Available statuses are `enqueued`, `processing`, `succeeded`, `failed`, `canceled`.",
|
||||
"code": "invalid_task_statuses",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-statuses"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
@ -711,17 +714,24 @@ mod tests {
|
||||
{
|
||||
let params = "types=documentAdditionOrUpdate,documentDeletion,settingsUpdate,indexCreation,indexDeletion,indexUpdate,indexSwap,taskCancelation,taskDeletion,dumpCreation,snapshotCreation";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query.types.unwrap()), @"[DocumentAdditionOrUpdate, DocumentDeletion, SettingsUpdate, IndexCreation, IndexDeletion, IndexUpdate, IndexSwap, TaskCancelation, TaskDeletion, DumpCreation, SnapshotCreation]");
|
||||
snapshot!(format!("{:?}", query.types), @"List([DocumentAdditionOrUpdate, DocumentDeletion, SettingsUpdate, IndexCreation, IndexDeletion, IndexUpdate, IndexSwap, TaskCancelation, TaskDeletion, DumpCreation, SnapshotCreation])");
|
||||
}
|
||||
{
|
||||
let params = "types=settingsUpdate";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query.types.unwrap()), @"[SettingsUpdate]");
|
||||
snapshot!(format!("{:?}", query.types), @"List([SettingsUpdate])");
|
||||
}
|
||||
{
|
||||
let params = "types=createIndex";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"`createIndex` is not a type. Available types are `documentAdditionOrUpdate`, `documentDeletion`, `settingsUpdate`, `indexCreation`, `indexDeletion`, `indexUpdate`, `indexSwap`, `taskCancelation`, `taskDeletion`, `dumpCreation`, `snapshotCreation`. at `.types`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `types`: `createIndex` is not a valid task type. Available types are `documentAdditionOrUpdate`, `documentDeletion`, `settingsUpdate`, `indexCreation`, `indexDeletion`, `indexUpdate`, `indexSwap`, `taskCancelation`, `taskDeletion`, `dumpCreation`, `snapshotCreation`.",
|
||||
"code": "invalid_task_types",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-types"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
@ -729,22 +739,36 @@ mod tests {
|
||||
{
|
||||
let params = "indexUids=toto,tata-78";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query.index_uids.unwrap()), @r###"[IndexUid("toto"), IndexUid("tata-78")]"###);
|
||||
snapshot!(format!("{:?}", query.index_uids), @r###"List([IndexUid("toto"), IndexUid("tata-78")])"###);
|
||||
}
|
||||
{
|
||||
let params = "indexUids=index_a";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query.index_uids.unwrap()), @r###"[IndexUid("index_a")]"###);
|
||||
snapshot!(format!("{:?}", query.index_uids), @r###"List([IndexUid("index_a")])"###);
|
||||
}
|
||||
{
|
||||
let params = "indexUids=1,hé";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"`hé` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_). at `.indexUids`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `indexUids[1]`: `hé` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
|
||||
"code": "invalid_index_uid",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-index-uid"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
{
|
||||
let params = "indexUids=hé";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"`hé` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_). at `.indexUids`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `indexUids`: `hé` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
|
||||
"code": "invalid_index_uid",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-index-uid"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
}
|
||||
|
||||
@ -753,38 +777,74 @@ mod tests {
|
||||
{
|
||||
let params = "from=12&limit=15&indexUids=toto,tata-78&statuses=succeeded,enqueued&afterEnqueuedAt=2012-04-23&uids=1,2,3";
|
||||
let query = deserr_query_params::<TasksFilterQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query), @r###"TasksFilterQuery { limit: 15, from: Some(12), uids: Some([1, 2, 3]), canceled_by: None, types: None, statuses: Some([Succeeded, Enqueued]), index_uids: Some([IndexUid("toto"), IndexUid("tata-78")]), after_enqueued_at: Some(2012-04-24 0:00:00.0 +00:00:00), before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }"###);
|
||||
snapshot!(format!("{:?}", query), @r###"TasksFilterQuery { limit: Param(15), from: Some(Param(12)), uids: List([1, 2, 3]), canceled_by: None, types: None, statuses: List([Succeeded, Enqueued]), index_uids: List([IndexUid("toto"), IndexUid("tata-78")]), after_enqueued_at: Other(2012-04-24 0:00:00.0 +00:00:00), before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }"###);
|
||||
}
|
||||
{
|
||||
// Stars should translate to `None` in the query
|
||||
// Verify value of the default limit
|
||||
let params = "indexUids=*&statuses=succeeded,*&afterEnqueuedAt=2012-04-23&uids=1,2,3";
|
||||
let query = deserr_query_params::<TasksFilterQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query), @"TasksFilterQuery { limit: 20, from: None, uids: Some([1, 2, 3]), canceled_by: None, types: None, statuses: None, index_uids: None, after_enqueued_at: Some(2012-04-24 0:00:00.0 +00:00:00), before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }");
|
||||
snapshot!(format!("{:?}", query), @"TasksFilterQuery { limit: Param(20), from: None, uids: List([1, 2, 3]), canceled_by: None, types: None, statuses: Star, index_uids: Star, after_enqueued_at: Other(2012-04-24 0:00:00.0 +00:00:00), before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }");
|
||||
}
|
||||
{
|
||||
// Stars should also translate to `None` in task deletion/cancelation queries
|
||||
let params = "indexUids=*&statuses=succeeded,*&afterEnqueuedAt=2012-04-23&uids=1,2,3";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
snapshot!(format!("{:?}", query), @"TaskDeletionOrCancelationQuery { uids: Some([1, 2, 3]), canceled_by: None, types: None, statuses: None, index_uids: None, after_enqueued_at: Some(2012-04-24 0:00:00.0 +00:00:00), before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }");
|
||||
snapshot!(format!("{:?}", query), @"TaskDeletionOrCancelationQuery { uids: List([1, 2, 3]), canceled_by: None, types: None, statuses: Star, index_uids: Star, after_enqueued_at: Other(2012-04-24 0:00:00.0 +00:00:00), before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }");
|
||||
}
|
||||
{
|
||||
// Stars in uids not allowed
|
||||
let params = "uids=*";
|
||||
// Star in from not allowed
|
||||
let params = "uids=*&from=*";
|
||||
let err = deserr_query_params::<TasksFilterQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"invalid digit found in string at `.uids`.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `from`: could not parse `*` as a positive integer",
|
||||
"code": "invalid_task_from",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-from"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
{
|
||||
// From not allowed in task deletion/cancelation queries
|
||||
let params = "from=12";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"Json deserialize error: unknown field `from`, expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt` at ``.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Unknown parameter `from`: expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`",
|
||||
"code": "bad_request",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
{
|
||||
// Limit not allowed in task deletion/cancelation queries
|
||||
let params = "limit=12";
|
||||
let err = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap_err();
|
||||
snapshot!(format!("{err}"), @"Json deserialize error: unknown field `limit`, expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt` at ``.");
|
||||
snapshot!(meili_snap::json_string!(err), @r###"
|
||||
{
|
||||
"message": "Unknown parameter `limit`: expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`",
|
||||
"code": "bad_request",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_task_delete_or_cancel_empty() {
|
||||
{
|
||||
let params = "";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
assert!(query.is_empty());
|
||||
}
|
||||
{
|
||||
let params = "statuses=*";
|
||||
let query = deserr_query_params::<TaskDeletionOrCancelationQuery>(params).unwrap();
|
||||
assert!(!query.is_empty());
|
||||
snapshot!(format!("{query:?}"), @"TaskDeletionOrCancelationQuery { uids: None, canceled_by: None, types: None, statuses: Star, index_uids: None, after_enqueued_at: None, before_enqueued_at: None, after_started_at: None, before_started_at: None, after_finished_at: None, before_finished_at: None }");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ use std::time::Instant;
|
||||
|
||||
use deserr::DeserializeFromValue;
|
||||
use either::Either;
|
||||
use meilisearch_types::deserr::DeserrJsonError;
|
||||
use meilisearch_types::error::deserr_codes::*;
|
||||
use meilisearch_types::error::DeserrError;
|
||||
use meilisearch_types::settings::DEFAULT_PAGINATION_MAX_TOTAL_HITS;
|
||||
use meilisearch_types::{milli, Document};
|
||||
use milli::tokenizer::TokenizerBuilder;
|
||||
@ -15,7 +15,7 @@ use milli::{
|
||||
SortError, TermsMatchingStrategy, DEFAULT_VALUES_PER_FACET,
|
||||
};
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::error::MeilisearchHttpError;
|
||||
@ -30,41 +30,41 @@ pub const DEFAULT_HIGHLIGHT_PRE_TAG: fn() -> String = || "<em>".to_string();
|
||||
pub const DEFAULT_HIGHLIGHT_POST_TAG: fn() -> String = || "</em>".to_string();
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, DeserializeFromValue)]
|
||||
#[deserr(error = DeserrError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct SearchQuery {
|
||||
#[deserr(error = DeserrError<InvalidSearchQ>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchQ>)]
|
||||
pub q: Option<String>,
|
||||
#[deserr(error = DeserrError<InvalidSearchOffset>, default = DEFAULT_SEARCH_OFFSET())]
|
||||
#[deserr(default = DEFAULT_SEARCH_OFFSET(), error = DeserrJsonError<InvalidSearchOffset>)]
|
||||
pub offset: usize,
|
||||
#[deserr(error = DeserrError<InvalidSearchLimit>, default = DEFAULT_SEARCH_LIMIT())]
|
||||
#[deserr(default = DEFAULT_SEARCH_LIMIT(), error = DeserrJsonError<InvalidSearchLimit>)]
|
||||
pub limit: usize,
|
||||
#[deserr(error = DeserrError<InvalidSearchPage>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchPage>)]
|
||||
pub page: Option<usize>,
|
||||
#[deserr(error = DeserrError<InvalidSearchHitsPerPage>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchHitsPerPage>)]
|
||||
pub hits_per_page: Option<usize>,
|
||||
#[deserr(error = DeserrError<InvalidSearchAttributesToRetrieve>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchAttributesToRetrieve>)]
|
||||
pub attributes_to_retrieve: Option<BTreeSet<String>>,
|
||||
#[deserr(error = DeserrError<InvalidSearchAttributesToCrop>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchAttributesToCrop>)]
|
||||
pub attributes_to_crop: Option<Vec<String>>,
|
||||
#[deserr(error = DeserrError<InvalidSearchCropLength>, default = DEFAULT_CROP_LENGTH())]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchCropLength>, default = DEFAULT_CROP_LENGTH())]
|
||||
pub crop_length: usize,
|
||||
#[deserr(error = DeserrError<InvalidSearchAttributesToHighlight>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchAttributesToHighlight>)]
|
||||
pub attributes_to_highlight: Option<HashSet<String>>,
|
||||
#[deserr(error = DeserrError<InvalidSearchShowMatchesPosition>, default)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchShowMatchesPosition>, default)]
|
||||
pub show_matches_position: bool,
|
||||
#[deserr(error = DeserrError<InvalidSearchFilter>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchFilter>)]
|
||||
pub filter: Option<Value>,
|
||||
#[deserr(error = DeserrError<InvalidSearchSort>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchSort>)]
|
||||
pub sort: Option<Vec<String>>,
|
||||
#[deserr(error = DeserrError<InvalidSearchFacets>)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchFacets>)]
|
||||
pub facets: Option<Vec<String>>,
|
||||
#[deserr(error = DeserrError<InvalidSearchHighlightPreTag>, default = DEFAULT_HIGHLIGHT_PRE_TAG())]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchHighlightPreTag>, default = DEFAULT_HIGHLIGHT_PRE_TAG())]
|
||||
pub highlight_pre_tag: String,
|
||||
#[deserr(error = DeserrError<InvalidSearchHighlightPostTag>, default = DEFAULT_HIGHLIGHT_POST_TAG())]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchHighlightPostTag>, default = DEFAULT_HIGHLIGHT_POST_TAG())]
|
||||
pub highlight_post_tag: String,
|
||||
#[deserr(error = DeserrError<InvalidSearchCropMarker>, default = DEFAULT_CROP_MARKER())]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchCropMarker>, default = DEFAULT_CROP_MARKER())]
|
||||
pub crop_marker: String,
|
||||
#[deserr(error = DeserrError<InvalidSearchMatchingStrategy>, default)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchMatchingStrategy>, default)]
|
||||
pub matching_strategy: MatchingStrategy,
|
||||
}
|
||||
|
||||
@ -74,9 +74,8 @@ impl SearchQuery {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, DeserializeFromValue)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DeserializeFromValue)]
|
||||
#[deserr(rename_all = camelCase)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum MatchingStrategy {
|
||||
/// Remove query words from last to first
|
||||
Last,
|
||||
|
@ -248,10 +248,10 @@ async fn error_add_api_key_missing_parameter() {
|
||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: missing field `indexes` at ``",
|
||||
"code": "bad_request",
|
||||
"message": "Missing field `indexes`",
|
||||
"code": "missing_api_key_indexes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||
"link": "https://docs.meilisearch.com/errors#missing-api-key-indexes"
|
||||
}
|
||||
"###);
|
||||
|
||||
@ -265,10 +265,10 @@ async fn error_add_api_key_missing_parameter() {
|
||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: missing field `actions` at ``",
|
||||
"code": "bad_request",
|
||||
"message": "Missing field `actions`",
|
||||
"code": "missing_api_key_actions",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||
"link": "https://docs.meilisearch.com/errors#missing-api-key-actions"
|
||||
}
|
||||
"###);
|
||||
|
||||
@ -279,22 +279,13 @@ async fn error_add_api_key_missing_parameter() {
|
||||
"actions": ["documents.add"],
|
||||
});
|
||||
let (response, code) = server.add_api_key(content).await;
|
||||
meili_snap::snapshot!(code, @"201 Created");
|
||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]", ".uid" => "[ignored]", ".key" => "[ignored]" }), @r###"
|
||||
{
|
||||
"name": null,
|
||||
"description": "Indexing API key",
|
||||
"key": "[ignored]",
|
||||
"uid": "[ignored]",
|
||||
"actions": [
|
||||
"documents.add"
|
||||
],
|
||||
"indexes": [
|
||||
"products"
|
||||
],
|
||||
"expiresAt": null,
|
||||
"createdAt": "[ignored]",
|
||||
"updatedAt": "[ignored]"
|
||||
"message": "Missing field `expiresAt`",
|
||||
"code": "missing_api_key_expires_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#missing-api-key-expires-at"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
@ -314,7 +305,7 @@ async fn error_add_api_key_invalid_parameters_description() {
|
||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "invalid type: Map `{\"name\":\"products\"}`, expected a String at `.description`.",
|
||||
"message": "Invalid value type at `.description`: expected a string, but found an object: `{\"name\":\"products\"}`",
|
||||
"code": "invalid_api_key_description",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-api-key-description"
|
||||
@ -337,7 +328,7 @@ async fn error_add_api_key_invalid_parameters_name() {
|
||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "invalid type: Map `{\"name\":\"products\"}`, expected a String at `.name`.",
|
||||
"message": "Invalid value type at `.name`: expected a string, but found an object: `{\"name\":\"products\"}`",
|
||||
"code": "invalid_api_key_name",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-api-key-name"
|
||||
@ -360,7 +351,7 @@ async fn error_add_api_key_invalid_parameters_indexes() {
|
||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "invalid type: Map `{\"name\":\"products\"}`, expected a Sequence at `.indexes`.",
|
||||
"message": "Invalid value type at `.indexes`: expected an array, but found an object: `{\"name\":\"products\"}`",
|
||||
"code": "invalid_api_key_indexes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-api-key-indexes"
|
||||
@ -386,7 +377,7 @@ async fn error_add_api_key_invalid_index_uids() {
|
||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "`invalid index # / \\name with spaces` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_). at `.indexes[0]`.",
|
||||
"message": "Invalid value at `.indexes[0]`: `invalid index # / \\name with spaces` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
|
||||
"code": "invalid_api_key_indexes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-api-key-indexes"
|
||||
@ -411,7 +402,7 @@ async fn error_add_api_key_invalid_parameters_actions() {
|
||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "invalid type: Map `{\"name\":\"products\"}`, expected a Sequence at `.actions`.",
|
||||
"message": "Invalid value type at `.actions`: expected an array, but found an object: `{\"name\":\"products\"}`",
|
||||
"code": "invalid_api_key_actions",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-api-key-actions"
|
||||
@ -431,7 +422,7 @@ async fn error_add_api_key_invalid_parameters_actions() {
|
||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown value `doc.add`, expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete` at `.actions[0]`.",
|
||||
"message": "Unknown value `doc.add` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`",
|
||||
"code": "invalid_api_key_actions",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-api-key-actions"
|
||||
@ -455,7 +446,7 @@ async fn error_add_api_key_invalid_parameters_expires_at() {
|
||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "invalid type: Map `{\"name\":\"products\"}`, expected a String at `.expiresAt`.",
|
||||
"message": "Invalid value type at `.expiresAt`: expected a string, but found an object: `{\"name\":\"products\"}`",
|
||||
"code": "invalid_api_key_expires_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-api-key-expires-at"
|
||||
@ -478,7 +469,7 @@ async fn error_add_api_key_invalid_parameters_expires_at_in_the_past() {
|
||||
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "`2010-11-13T00:00:00Z` is not a valid date. It should follow the RFC 3339 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'.\n at `.expiresAt`.",
|
||||
"message": "Invalid value at `.expiresAt`: `2010-11-13T00:00:00Z` is not a valid date. It should follow the RFC 3339 format to represents a date or datetime in the future or specified as a null value. e.g. 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'.\n",
|
||||
"code": "invalid_api_key_expires_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-api-key-expires-at"
|
||||
@ -503,7 +494,7 @@ async fn error_add_api_key_invalid_parameters_uid() {
|
||||
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "invalid length: expected length 32 for simple format, found 13 at `.uid`.",
|
||||
"message": "Invalid value at `.uid`: invalid length: expected length 32 for simple format, found 13",
|
||||
"code": "invalid_api_key_uid",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-api-key-uid"
|
||||
@ -1403,7 +1394,7 @@ async fn error_patch_api_key_indexes() {
|
||||
let (response, code) = server.patch_api_key(&uid, content).await;
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown field `indexes`, expected one of `description`, `name` at ``.",
|
||||
"message": "Unknown field `indexes`: expected one of `description`, `name`",
|
||||
"code": "immutable_api_key_indexes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#immutable-api-key-indexes"
|
||||
@ -1480,7 +1471,7 @@ async fn error_patch_api_key_actions() {
|
||||
let (response, code) = server.patch_api_key(&uid, content).await;
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown field `actions`, expected one of `description`, `name` at ``.",
|
||||
"message": "Unknown field `actions`: expected one of `description`, `name`",
|
||||
"code": "immutable_api_key_actions",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#immutable-api-key-actions"
|
||||
@ -1549,7 +1540,7 @@ async fn error_patch_api_key_expiration_date() {
|
||||
let (response, code) = server.patch_api_key(&uid, content).await;
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown field `expiresAt`, expected one of `description`, `name` at ``.",
|
||||
"message": "Unknown field `expiresAt`: expected one of `description`, `name`",
|
||||
"code": "immutable_api_key_expires_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#immutable-api-key-expires-at"
|
||||
@ -1670,7 +1661,7 @@ async fn error_patch_api_key_indexes_invalid_parameters() {
|
||||
let (response, code) = server.patch_api_key(&uid, content).await;
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "invalid type: Integer `13`, expected a String at `.description`.",
|
||||
"message": "Invalid value type at `.description`: expected a string, but found a positive integer: `13`",
|
||||
"code": "invalid_api_key_description",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-api-key-description"
|
||||
@ -1686,7 +1677,7 @@ async fn error_patch_api_key_indexes_invalid_parameters() {
|
||||
let (response, code) = server.patch_api_key(&uid, content).await;
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###"
|
||||
{
|
||||
"message": "invalid type: Integer `13`, expected a String at `.name`.",
|
||||
"message": "Invalid value type at `.name`: expected a string, but found a positive integer: `13`",
|
||||
"code": "invalid_api_key_name",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-api-key-name"
|
||||
|
@ -289,8 +289,8 @@ impl Index<'_> {
|
||||
eprintln!("Error with post search");
|
||||
resume_unwind(e);
|
||||
}
|
||||
|
||||
let (response, code) = self.search_get(query).await;
|
||||
let query = yaup::to_string(&query).unwrap();
|
||||
let (response, code) = self.search_get(&query).await;
|
||||
if let Err(e) = catch_unwind(move || test(response, code)) {
|
||||
eprintln!("Error with get search");
|
||||
resume_unwind(e);
|
||||
@ -302,9 +302,8 @@ impl Index<'_> {
|
||||
self.service.post_encoded(url, query, self.encoder).await
|
||||
}
|
||||
|
||||
pub async fn search_get(&self, query: Value) -> (Value, StatusCode) {
|
||||
let params = yaup::to_string(&query).unwrap();
|
||||
let url = format!("/indexes/{}/search?{}", urlencode(self.uid.as_ref()), params);
|
||||
pub async fn search_get(&self, query: &str) -> (Value, StatusCode) {
|
||||
let url = format!("/indexes/{}/search?{}", urlencode(self.uid.as_ref()), query);
|
||||
self.service.get(url).await
|
||||
}
|
||||
|
||||
|
@ -132,8 +132,8 @@ impl Server {
|
||||
self.service.get("/tasks").await
|
||||
}
|
||||
|
||||
pub async fn tasks_filter(&self, filter: Value) -> (Value, StatusCode) {
|
||||
self.service.get(format!("/tasks?{}", yaup::to_string(&filter).unwrap())).await
|
||||
pub async fn tasks_filter(&self, filter: &str) -> (Value, StatusCode) {
|
||||
self.service.get(format!("/tasks?{}", filter)).await
|
||||
}
|
||||
|
||||
pub async fn get_dump_status(&self, uid: &str) -> (Value, StatusCode) {
|
||||
@ -148,14 +148,12 @@ impl Server {
|
||||
self.service.post("/swap-indexes", value).await
|
||||
}
|
||||
|
||||
pub async fn cancel_tasks(&self, value: Value) -> (Value, StatusCode) {
|
||||
self.service
|
||||
.post(format!("/tasks/cancel?{}", yaup::to_string(&value).unwrap()), json!(null))
|
||||
.await
|
||||
pub async fn cancel_tasks(&self, value: &str) -> (Value, StatusCode) {
|
||||
self.service.post(format!("/tasks/cancel?{}", value), json!(null)).await
|
||||
}
|
||||
|
||||
pub async fn delete_tasks(&self, value: Value) -> (Value, StatusCode) {
|
||||
self.service.delete(format!("/tasks?{}", yaup::to_string(&value).unwrap())).await
|
||||
pub async fn delete_tasks(&self, value: &str) -> (Value, StatusCode) {
|
||||
self.service.delete(format!("/tasks?{}", value)).await
|
||||
}
|
||||
|
||||
pub async fn wait_task(&self, update_id: u64) -> Value {
|
||||
|
@ -1,6 +1,7 @@
|
||||
use actix_web::http::header::ContentType;
|
||||
use actix_web::test;
|
||||
use http::header::ACCEPT_ENCODING;
|
||||
use meili_snap::{json_string, snapshot};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::common::encoder::Encoder;
|
||||
@ -188,13 +189,13 @@ async fn error_create_with_invalid_index_uid() {
|
||||
let index = server.index("test test#!");
|
||||
let (response, code) = index.create(None).await;
|
||||
|
||||
let expected_response = json!({
|
||||
"message": "`test test#!` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
|
||||
"code": "invalid_index_uid",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-index-uid"
|
||||
});
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
assert_eq!(code, 400);
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "Invalid value at `.uid`: `test test#!` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
|
||||
"code": "invalid_index_uid",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-index-uid"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use meili_snap::{json_string, snapshot};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::common::Server;
|
||||
@ -182,15 +183,13 @@ async fn get_invalid_index_uid() {
|
||||
let index = server.index("this is not a valid index name");
|
||||
let (response, code) = index.get().await;
|
||||
|
||||
assert_eq!(code, 404);
|
||||
assert_eq!(
|
||||
response,
|
||||
json!(
|
||||
{
|
||||
"message": "Index `this is not a valid index name` not found.",
|
||||
"code": "index_not_found",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#index-not-found"
|
||||
})
|
||||
);
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`this is not a valid index name` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
|
||||
"code": "invalid_index_uid",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-index-uid"
|
||||
}
|
||||
"###);
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ async fn search_bad_q() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: Sequence `[\"doggo\"]`, expected a String at `.q`.",
|
||||
"message": "Invalid value type at `.q`: expected a string, but found an array: `[\"doggo\"]`",
|
||||
"code": "invalid_search_q",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-q"
|
||||
@ -64,18 +64,18 @@ async fn search_bad_offset() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Integer at `.offset`.",
|
||||
"message": "Invalid value type at `.offset`: expected a positive integer, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_search_offset",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-offset"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = index.search_get(json!({"offset": "doggo"})).await;
|
||||
let (response, code) = index.search_get("offset=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.offset`.",
|
||||
"message": "Invalid value in parameter `offset`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_search_offset",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-offset"
|
||||
@ -92,18 +92,18 @@ async fn search_bad_limit() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Integer at `.limit`.",
|
||||
"message": "Invalid value type at `.limit`: expected a positive integer, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_search_limit",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-limit"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = index.search_get(json!({"limit": "doggo"})).await;
|
||||
let (response, code) = index.search_get("limit=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.limit`.",
|
||||
"message": "Invalid value in parameter `limit`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_search_limit",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-limit"
|
||||
@ -120,18 +120,18 @@ async fn search_bad_page() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Integer at `.page`.",
|
||||
"message": "Invalid value type at `.page`: expected a positive integer, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_search_page",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-page"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = index.search_get(json!({"page": "doggo"})).await;
|
||||
let (response, code) = index.search_get("page=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.page`.",
|
||||
"message": "Invalid value in parameter `page`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_search_page",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-page"
|
||||
@ -148,18 +148,18 @@ async fn search_bad_hits_per_page() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Integer at `.hitsPerPage`.",
|
||||
"message": "Invalid value type at `.hitsPerPage`: expected a positive integer, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_search_hits_per_page",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-hits-per-page"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = index.search_get(json!({"hitsPerPage": "doggo"})).await;
|
||||
let (response, code) = index.search_get("hitsPerPage=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.hitsPerPage`.",
|
||||
"message": "Invalid value in parameter `hitsPerPage`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_search_hits_per_page",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-hits-per-page"
|
||||
@ -176,7 +176,7 @@ async fn search_bad_attributes_to_crop() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at `.attributesToCrop`.",
|
||||
"message": "Invalid value type at `.attributesToCrop`: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_search_attributes_to_crop",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-attributes-to-crop"
|
||||
@ -194,18 +194,18 @@ async fn search_bad_crop_length() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Integer at `.cropLength`.",
|
||||
"message": "Invalid value type at `.cropLength`: expected a positive integer, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_search_crop_length",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-crop-length"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = index.search_get(json!({"cropLength": "doggo"})).await;
|
||||
let (response, code) = index.search_get("cropLength=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.cropLength`.",
|
||||
"message": "Invalid value in parameter `cropLength`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_search_crop_length",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-crop-length"
|
||||
@ -222,7 +222,7 @@ async fn search_bad_attributes_to_highlight() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at `.attributesToHighlight`.",
|
||||
"message": "Invalid value type at `.attributesToHighlight`: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_search_attributes_to_highlight",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-attributes-to-highlight"
|
||||
@ -266,7 +266,7 @@ async fn search_bad_sort() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at `.sort`.",
|
||||
"message": "Invalid value type at `.sort`: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_search_sort",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-sort"
|
||||
@ -284,18 +284,18 @@ async fn search_bad_show_matches_position() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Boolean at `.showMatchesPosition`.",
|
||||
"message": "Invalid value type at `.showMatchesPosition`: expected a boolean, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_search_show_matches_position",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-show-matches-position"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = index.search_get(json!({"showMatchesPosition": "doggo"})).await;
|
||||
let (response, code) = index.search_get("showMatchesPosition=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "provided string was not `true` or `false` at `.showMatchesPosition`.",
|
||||
"message": "Invalid value in parameter `showMatchesPosition`: could not parse `doggo` as a boolean, expected either `true` or `false`",
|
||||
"code": "invalid_search_show_matches_position",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-show-matches-position"
|
||||
@ -312,7 +312,7 @@ async fn search_bad_facets() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at `.facets`.",
|
||||
"message": "Invalid value type at `.facets`: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_search_facets",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-facets"
|
||||
@ -330,7 +330,7 @@ async fn search_bad_highlight_pre_tag() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: Sequence `[\"doggo\"]`, expected a String at `.highlightPreTag`.",
|
||||
"message": "Invalid value type at `.highlightPreTag`: expected a string, but found an array: `[\"doggo\"]`",
|
||||
"code": "invalid_search_highlight_pre_tag",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-highlight-pre-tag"
|
||||
@ -348,7 +348,7 @@ async fn search_bad_highlight_post_tag() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: Sequence `[\"doggo\"]`, expected a String at `.highlightPostTag`.",
|
||||
"message": "Invalid value type at `.highlightPostTag`: expected a string, but found an array: `[\"doggo\"]`",
|
||||
"code": "invalid_search_highlight_post_tag",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-highlight-post-tag"
|
||||
@ -366,7 +366,7 @@ async fn search_bad_crop_marker() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: Sequence `[\"doggo\"]`, expected a String at `.cropMarker`.",
|
||||
"message": "Invalid value type at `.cropMarker`: expected a string, but found an array: `[\"doggo\"]`",
|
||||
"code": "invalid_search_crop_marker",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-crop-marker"
|
||||
@ -384,18 +384,18 @@ async fn search_bad_matching_strategy() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown value `doggo`, expected one of `last`, `all` at `.matchingStrategy`.",
|
||||
"message": "Unknown value `doggo` at `.matchingStrategy`: expected one of `last`, `all`",
|
||||
"code": "invalid_search_matching_strategy",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-matching-strategy"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = index.search_get(json!({"matchingStrategy": "doggo"})).await;
|
||||
let (response, code) = index.search_get("matchingStrategy=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown value `doggo`, expected one of `last`, `all` at `.matchingStrategy`.",
|
||||
"message": "Unknown value `doggo` for parameter `matchingStrategy`: expected one of `last`, `all`",
|
||||
"code": "invalid_search_matching_strategy",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-search-matching-strategy"
|
||||
|
@ -12,7 +12,7 @@ async fn settings_bad_displayed_attributes() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at `.displayedAttributes`.",
|
||||
"message": "Invalid value type at `.displayedAttributes`: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_displayed_attributes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-displayed-attributes"
|
||||
@ -23,7 +23,7 @@ async fn settings_bad_displayed_attributes() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at ``.",
|
||||
"message": "Invalid value type: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_displayed_attributes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-displayed-attributes"
|
||||
@ -40,7 +40,7 @@ async fn settings_bad_searchable_attributes() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at `.searchableAttributes`.",
|
||||
"message": "Invalid value type at `.searchableAttributes`: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_searchable_attributes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-searchable-attributes"
|
||||
@ -51,7 +51,7 @@ async fn settings_bad_searchable_attributes() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at ``.",
|
||||
"message": "Invalid value type: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_searchable_attributes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-searchable-attributes"
|
||||
@ -68,7 +68,7 @@ async fn settings_bad_filterable_attributes() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at `.filterableAttributes`.",
|
||||
"message": "Invalid value type at `.filterableAttributes`: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_filterable_attributes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-filterable-attributes"
|
||||
@ -79,7 +79,7 @@ async fn settings_bad_filterable_attributes() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at ``.",
|
||||
"message": "Invalid value type: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_filterable_attributes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-filterable-attributes"
|
||||
@ -96,7 +96,7 @@ async fn settings_bad_sortable_attributes() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at `.sortableAttributes`.",
|
||||
"message": "Invalid value type at `.sortableAttributes`: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_sortable_attributes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-sortable-attributes"
|
||||
@ -107,7 +107,7 @@ async fn settings_bad_sortable_attributes() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at ``.",
|
||||
"message": "Invalid value type: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_sortable_attributes",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-sortable-attributes"
|
||||
@ -124,7 +124,7 @@ async fn settings_bad_ranking_rules() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at `.rankingRules`.",
|
||||
"message": "Invalid value type at `.rankingRules`: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_ranking_rules",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-ranking-rules"
|
||||
@ -135,7 +135,7 @@ async fn settings_bad_ranking_rules() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at ``.",
|
||||
"message": "Invalid value type: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_ranking_rules",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-ranking-rules"
|
||||
@ -152,7 +152,7 @@ async fn settings_bad_stop_words() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at `.stopWords`.",
|
||||
"message": "Invalid value type at `.stopWords`: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_stop_words",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-stop-words"
|
||||
@ -163,7 +163,7 @@ async fn settings_bad_stop_words() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Sequence at ``.",
|
||||
"message": "Invalid value type: expected an array, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_stop_words",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-stop-words"
|
||||
@ -180,7 +180,7 @@ async fn settings_bad_synonyms() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Map at `.synonyms`.",
|
||||
"message": "Invalid value type at `.synonyms`: expected an object, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_synonyms",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-synonyms"
|
||||
@ -191,7 +191,7 @@ async fn settings_bad_synonyms() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Map at ``.",
|
||||
"message": "Invalid value type: expected an object, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_synonyms",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-synonyms"
|
||||
@ -208,7 +208,7 @@ async fn settings_bad_distinct_attribute() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: Sequence `[\"doggo\"]`, expected a String at `.distinctAttribute`.",
|
||||
"message": "Invalid value type at `.distinctAttribute`: expected a string, but found an array: `[\"doggo\"]`",
|
||||
"code": "invalid_settings_distinct_attribute",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-distinct-attribute"
|
||||
@ -219,7 +219,7 @@ async fn settings_bad_distinct_attribute() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: Sequence `[\"doggo\"]`, expected a String at ``.",
|
||||
"message": "Invalid value type: expected a string, but found an array: `[\"doggo\"]`",
|
||||
"code": "invalid_settings_distinct_attribute",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-distinct-attribute"
|
||||
@ -236,7 +236,7 @@ async fn settings_bad_typo_tolerance() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Map at `.typoTolerance`.",
|
||||
"message": "Invalid value type at `.typoTolerance`: expected an object, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_typo_tolerance",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-typo-tolerance"
|
||||
@ -247,7 +247,7 @@ async fn settings_bad_typo_tolerance() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Map at ``.",
|
||||
"message": "Invalid value type: expected an object, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_typo_tolerance",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-typo-tolerance"
|
||||
@ -264,7 +264,7 @@ async fn settings_bad_faceting() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Map at `.faceting`.",
|
||||
"message": "Invalid value type at `.faceting`: expected an object, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_faceting",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-faceting"
|
||||
@ -275,7 +275,7 @@ async fn settings_bad_faceting() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Map at ``.",
|
||||
"message": "Invalid value type: expected an object, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_faceting",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-faceting"
|
||||
@ -292,7 +292,7 @@ async fn settings_bad_pagination() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Map at `.pagination`.",
|
||||
"message": "Invalid value type at `.pagination`: expected an object, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_pagination",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-pagination"
|
||||
@ -303,7 +303,7 @@ async fn settings_bad_pagination() {
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid type: String `\"doggo\"`, expected a Map at ``.",
|
||||
"message": "Invalid value type: expected an object, but found a string: `\"doggo\"`",
|
||||
"code": "invalid_settings_pagination",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-pagination"
|
||||
|
@ -282,7 +282,7 @@ async fn error_set_invalid_ranking_rules() {
|
||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||
{
|
||||
"message": "`manyTheFish` ranking rule is invalid. Valid ranking rules are words, typo, sort, proximity, attribute, exactness and custom ranking rules. at `.rankingRules[0]`.",
|
||||
"message": "Invalid value at `.rankingRules[0]`: `manyTheFish` ranking rule is invalid. Valid ranking rules are words, typo, sort, proximity, attribute, exactness and custom ranking rules.",
|
||||
"code": "invalid_settings_ranking_rules",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-ranking-rules"
|
||||
|
@ -1,5 +1,4 @@
|
||||
use meili_snap::*;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::common::Server;
|
||||
|
||||
@ -7,33 +6,44 @@ use crate::common::Server;
|
||||
async fn task_bad_uids() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"uids": "doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("uids=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.uids`.",
|
||||
"message": "Invalid value in parameter `uids`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_task_uids",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-uids"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"uids": "doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("uids=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.uids`.",
|
||||
"message": "Invalid value in parameter `uids`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_task_uids",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-uids"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"uids": "doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("uids=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.uids`.",
|
||||
"message": "Invalid value in parameter `uids`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_task_uids",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-uids"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks("uids=1,dogo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "Invalid value in parameter `uids[1]`: could not parse `dogo` as a positive integer",
|
||||
"code": "invalid_task_uids",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-uids"
|
||||
@ -45,33 +55,33 @@ async fn task_bad_uids() {
|
||||
async fn task_bad_canceled_by() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"canceledBy": "doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("canceledBy=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.canceledBy`.",
|
||||
"message": "Invalid value in parameter `canceledBy`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_task_canceled_by",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-canceled-by"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"canceledBy": "doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("canceledBy=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.canceledBy`.",
|
||||
"message": "Invalid value in parameter `canceledBy`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_task_canceled_by",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-canceled-by"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"canceledBy": "doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("canceledBy=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.canceledBy`.",
|
||||
"message": "Invalid value in parameter `canceledBy`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_task_canceled_by",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-canceled-by"
|
||||
@ -83,33 +93,33 @@ async fn task_bad_canceled_by() {
|
||||
async fn task_bad_types() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"types": "doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("types=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is not a type. Available types are `documentAdditionOrUpdate`, `documentDeletion`, `settingsUpdate`, `indexCreation`, `indexDeletion`, `indexUpdate`, `indexSwap`, `taskCancelation`, `taskDeletion`, `dumpCreation`, `snapshotCreation`. at `.types`.",
|
||||
"message": "Invalid value in parameter `types`: `doggo` is not a valid task type. Available types are `documentAdditionOrUpdate`, `documentDeletion`, `settingsUpdate`, `indexCreation`, `indexDeletion`, `indexUpdate`, `indexSwap`, `taskCancelation`, `taskDeletion`, `dumpCreation`, `snapshotCreation`.",
|
||||
"code": "invalid_task_types",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-types"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"types": "doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("types=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is not a type. Available types are `documentAdditionOrUpdate`, `documentDeletion`, `settingsUpdate`, `indexCreation`, `indexDeletion`, `indexUpdate`, `indexSwap`, `taskCancelation`, `taskDeletion`, `dumpCreation`, `snapshotCreation`. at `.types`.",
|
||||
"message": "Invalid value in parameter `types`: `doggo` is not a valid task type. Available types are `documentAdditionOrUpdate`, `documentDeletion`, `settingsUpdate`, `indexCreation`, `indexDeletion`, `indexUpdate`, `indexSwap`, `taskCancelation`, `taskDeletion`, `dumpCreation`, `snapshotCreation`.",
|
||||
"code": "invalid_task_types",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-types"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"types": "doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("types=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is not a type. Available types are `documentAdditionOrUpdate`, `documentDeletion`, `settingsUpdate`, `indexCreation`, `indexDeletion`, `indexUpdate`, `indexSwap`, `taskCancelation`, `taskDeletion`, `dumpCreation`, `snapshotCreation`. at `.types`.",
|
||||
"message": "Invalid value in parameter `types`: `doggo` is not a valid task type. Available types are `documentAdditionOrUpdate`, `documentDeletion`, `settingsUpdate`, `indexCreation`, `indexDeletion`, `indexUpdate`, `indexSwap`, `taskCancelation`, `taskDeletion`, `dumpCreation`, `snapshotCreation`.",
|
||||
"code": "invalid_task_types",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-types"
|
||||
@ -121,33 +131,33 @@ async fn task_bad_types() {
|
||||
async fn task_bad_statuses() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"statuses": "doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("statuses=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is not a status. Available status are `enqueued`, `processing`, `succeeded`, `failed`, `canceled`. at `.statuses`.",
|
||||
"message": "Invalid value in parameter `statuses`: `doggo` is not a valid task status. Available statuses are `enqueued`, `processing`, `succeeded`, `failed`, `canceled`.",
|
||||
"code": "invalid_task_statuses",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-statuses"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"statuses": "doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("statuses=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is not a status. Available status are `enqueued`, `processing`, `succeeded`, `failed`, `canceled`. at `.statuses`.",
|
||||
"message": "Invalid value in parameter `statuses`: `doggo` is not a valid task status. Available statuses are `enqueued`, `processing`, `succeeded`, `failed`, `canceled`.",
|
||||
"code": "invalid_task_statuses",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-statuses"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"statuses": "doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("statuses=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is not a status. Available status are `enqueued`, `processing`, `succeeded`, `failed`, `canceled`. at `.statuses`.",
|
||||
"message": "Invalid value in parameter `statuses`: `doggo` is not a valid task status. Available statuses are `enqueued`, `processing`, `succeeded`, `failed`, `canceled`.",
|
||||
"code": "invalid_task_statuses",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-statuses"
|
||||
@ -159,33 +169,33 @@ async fn task_bad_statuses() {
|
||||
async fn task_bad_index_uids() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"indexUids": "the good doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("indexUids=the%20good%20doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`the good doggo` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_). at `.indexUids`.",
|
||||
"message": "Invalid value in parameter `indexUids`: `the good doggo` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
|
||||
"code": "invalid_index_uid",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-index-uid"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"indexUids": "the good doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("indexUids=the%20good%20doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`the good doggo` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_). at `.indexUids`.",
|
||||
"message": "Invalid value in parameter `indexUids`: `the good doggo` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
|
||||
"code": "invalid_index_uid",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-index-uid"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"indexUids": "the good doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("indexUids=the%20good%20doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`the good doggo` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_). at `.indexUids`.",
|
||||
"message": "Invalid value in parameter `indexUids`: `the good doggo` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_).",
|
||||
"code": "invalid_index_uid",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-index-uid"
|
||||
@ -197,33 +207,33 @@ async fn task_bad_index_uids() {
|
||||
async fn task_bad_limit() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"limit": "doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("limit=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.limit`.",
|
||||
"message": "Invalid value in parameter `limit`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_task_limit",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-limit"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"limit": "doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("limit=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown field `limit`, expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt` at ``.",
|
||||
"message": "Unknown parameter `limit`: expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`",
|
||||
"code": "bad_request",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"limit": "doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("limit=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown field `limit`, expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt` at ``.",
|
||||
"message": "Unknown parameter `limit`: expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`",
|
||||
"code": "bad_request",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||
@ -235,33 +245,33 @@ async fn task_bad_limit() {
|
||||
async fn task_bad_from() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"from": "doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("from=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.from`.",
|
||||
"message": "Invalid value in parameter `from`: could not parse `doggo` as a positive integer",
|
||||
"code": "invalid_task_from",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-from"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"from": "doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("from=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown field `from`, expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt` at ``.",
|
||||
"message": "Unknown parameter `from`: expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`",
|
||||
"code": "bad_request",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"from": "doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("from=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown field `from`, expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt` at ``.",
|
||||
"message": "Unknown parameter `from`: expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`",
|
||||
"code": "bad_request",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||
@ -273,33 +283,33 @@ async fn task_bad_from() {
|
||||
async fn task_bad_after_enqueued_at() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"afterEnqueuedAt": "doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("afterEnqueuedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.afterEnqueuedAt`.",
|
||||
"message": "Invalid value in parameter `afterEnqueuedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_after_enqueued_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-after-enqueued-at"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"afterEnqueuedAt": "doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("afterEnqueuedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.afterEnqueuedAt`.",
|
||||
"message": "Invalid value in parameter `afterEnqueuedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_after_enqueued_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-after-enqueued-at"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"afterEnqueuedAt": "doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("afterEnqueuedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.afterEnqueuedAt`.",
|
||||
"message": "Invalid value in parameter `afterEnqueuedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_after_enqueued_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-after-enqueued-at"
|
||||
@ -311,33 +321,33 @@ async fn task_bad_after_enqueued_at() {
|
||||
async fn task_bad_before_enqueued_at() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"beforeEnqueuedAt": "doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("beforeEnqueuedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeEnqueuedAt`.",
|
||||
"message": "Invalid value in parameter `beforeEnqueuedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_enqueued_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-enqueued-at"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"beforeEnqueuedAt": "doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("beforeEnqueuedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeEnqueuedAt`.",
|
||||
"message": "Invalid value in parameter `beforeEnqueuedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_enqueued_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-enqueued-at"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"beforeEnqueuedAt": "doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("beforeEnqueuedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeEnqueuedAt`.",
|
||||
"message": "Invalid value in parameter `beforeEnqueuedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_enqueued_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-enqueued-at"
|
||||
@ -349,33 +359,33 @@ async fn task_bad_before_enqueued_at() {
|
||||
async fn task_bad_after_started_at() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"afterStartedAt": "doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("afterStartedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.afterStartedAt`.",
|
||||
"message": "Invalid value in parameter `afterStartedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_after_started_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-after-started-at"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"afterStartedAt": "doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("afterStartedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.afterStartedAt`.",
|
||||
"message": "Invalid value in parameter `afterStartedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_after_started_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-after-started-at"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"afterStartedAt": "doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("afterStartedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.afterStartedAt`.",
|
||||
"message": "Invalid value in parameter `afterStartedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_after_started_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-after-started-at"
|
||||
@ -387,33 +397,33 @@ async fn task_bad_after_started_at() {
|
||||
async fn task_bad_before_started_at() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"beforeStartedAt": "doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("beforeStartedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeStartedAt`.",
|
||||
"message": "Invalid value in parameter `beforeStartedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_started_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-started-at"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"beforeStartedAt": "doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("beforeStartedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeStartedAt`.",
|
||||
"message": "Invalid value in parameter `beforeStartedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_started_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-started-at"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"beforeStartedAt": "doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("beforeStartedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeStartedAt`.",
|
||||
"message": "Invalid value in parameter `beforeStartedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_started_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-started-at"
|
||||
@ -425,33 +435,33 @@ async fn task_bad_before_started_at() {
|
||||
async fn task_bad_after_finished_at() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"afterFinishedAt": "doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("afterFinishedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.afterFinishedAt`.",
|
||||
"message": "Invalid value in parameter `afterFinishedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_after_finished_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-after-finished-at"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"afterFinishedAt": "doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("afterFinishedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.afterFinishedAt`.",
|
||||
"message": "Invalid value in parameter `afterFinishedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_after_finished_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-after-finished-at"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"afterFinishedAt": "doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("afterFinishedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.afterFinishedAt`.",
|
||||
"message": "Invalid value in parameter `afterFinishedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_after_finished_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-after-finished-at"
|
||||
@ -463,33 +473,33 @@ async fn task_bad_after_finished_at() {
|
||||
async fn task_bad_before_finished_at() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!({"beforeFinishedAt": "doggo"})).await;
|
||||
let (response, code) = server.tasks_filter("beforeFinishedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeFinishedAt`.",
|
||||
"message": "Invalid value in parameter `beforeFinishedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_finished_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-finished-at"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({"beforeFinishedAt": "doggo"})).await;
|
||||
let (response, code) = server.cancel_tasks("beforeFinishedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeFinishedAt`.",
|
||||
"message": "Invalid value in parameter `beforeFinishedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_finished_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-finished-at"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({"beforeFinishedAt": "doggo"})).await;
|
||||
let (response, code) = server.delete_tasks("beforeFinishedAt=doggo").await;
|
||||
snapshot!(code, @"400 Bad Request");
|
||||
snapshot!(json_string!(response), @r###"
|
||||
{
|
||||
"message": "`doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeFinishedAt`.",
|
||||
"message": "Invalid value in parameter `beforeFinishedAt`: `doggo` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_finished_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-finished-at"
|
||||
|
@ -179,44 +179,44 @@ async fn list_tasks_status_and_type_filtered() {
|
||||
async fn get_task_filter_error() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.tasks_filter(json!( { "lol": "pied" })).await;
|
||||
let (response, code) = server.tasks_filter("lol=pied").await;
|
||||
assert_eq!(code, 400, "{}", response);
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown field `lol`, expected one of `limit`, `from`, `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt` at ``.",
|
||||
"message": "Unknown parameter `lol`: expected one of `limit`, `from`, `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`",
|
||||
"code": "bad_request",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.tasks_filter(json!( { "uids": "pied" })).await;
|
||||
let (response, code) = server.tasks_filter("uids=pied").await;
|
||||
assert_eq!(code, 400, "{}", response);
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.uids`.",
|
||||
"message": "Invalid value in parameter `uids`: could not parse `pied` as a positive integer",
|
||||
"code": "invalid_task_uids",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-uids"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.tasks_filter(json!( { "from": "pied" })).await;
|
||||
let (response, code) = server.tasks_filter("from=pied").await;
|
||||
assert_eq!(code, 400, "{}", response);
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.from`.",
|
||||
"message": "Invalid value in parameter `from`: could not parse `pied` as a positive integer",
|
||||
"code": "invalid_task_from",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-from"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.tasks_filter(json!( { "beforeStartedAt": "pied" })).await;
|
||||
let (response, code) = server.tasks_filter("beforeStartedAt=pied").await;
|
||||
assert_eq!(code, 400, "{}", response);
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||
{
|
||||
"message": "`pied` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format. at `.beforeStartedAt`.",
|
||||
"message": "Invalid value in parameter `beforeStartedAt`: `pied` is an invalid date-time. It should follow the YYYY-MM-DD or RFC 3339 date-time format.",
|
||||
"code": "invalid_task_before_started_at",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-before-started-at"
|
||||
@ -228,7 +228,7 @@ async fn get_task_filter_error() {
|
||||
async fn delete_task_filter_error() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.delete_tasks(json!(null)).await;
|
||||
let (response, code) = server.delete_tasks("").await;
|
||||
assert_eq!(code, 400, "{}", response);
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||
{
|
||||
@ -239,22 +239,22 @@ async fn delete_task_filter_error() {
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({ "lol": "pied" })).await;
|
||||
let (response, code) = server.delete_tasks("lol=pied").await;
|
||||
assert_eq!(code, 400, "{}", response);
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown field `lol`, expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt` at ``.",
|
||||
"message": "Unknown parameter `lol`: expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`",
|
||||
"code": "bad_request",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.delete_tasks(json!({ "uids": "pied" })).await;
|
||||
let (response, code) = server.delete_tasks("uids=pied").await;
|
||||
assert_eq!(code, 400, "{}", response);
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.uids`.",
|
||||
"message": "Invalid value in parameter `uids`: could not parse `pied` as a positive integer",
|
||||
"code": "invalid_task_uids",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-uids"
|
||||
@ -266,7 +266,7 @@ async fn delete_task_filter_error() {
|
||||
async fn cancel_task_filter_error() {
|
||||
let server = Server::new().await;
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!(null)).await;
|
||||
let (response, code) = server.cancel_tasks("").await;
|
||||
assert_eq!(code, 400, "{}", response);
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||
{
|
||||
@ -277,22 +277,22 @@ async fn cancel_task_filter_error() {
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({ "lol": "pied" })).await;
|
||||
let (response, code) = server.cancel_tasks("lol=pied").await;
|
||||
assert_eq!(code, 400, "{}", response);
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||
{
|
||||
"message": "Json deserialize error: unknown field `lol`, expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt` at ``.",
|
||||
"message": "Unknown parameter `lol`: expected one of `uids`, `canceledBy`, `types`, `statuses`, `indexUids`, `afterEnqueuedAt`, `beforeEnqueuedAt`, `afterStartedAt`, `beforeStartedAt`, `afterFinishedAt`, `beforeFinishedAt`",
|
||||
"code": "bad_request",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#bad-request"
|
||||
}
|
||||
"###);
|
||||
|
||||
let (response, code) = server.cancel_tasks(json!({ "uids": "pied" })).await;
|
||||
let (response, code) = server.cancel_tasks("uids=pied").await;
|
||||
assert_eq!(code, 400, "{}", response);
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||
{
|
||||
"message": "invalid digit found in string at `.uids`.",
|
||||
"message": "Invalid value in parameter `uids`: could not parse `pied` as a positive integer",
|
||||
"code": "invalid_task_uids",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-task-uids"
|
||||
@ -523,7 +523,7 @@ async fn test_summarized_settings_update() {
|
||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||
{
|
||||
"message": "`custom` ranking rule is invalid. Valid ranking rules are words, typo, sort, proximity, attribute, exactness and custom ranking rules. at `.rankingRules[0]`.",
|
||||
"message": "Invalid value at `.rankingRules[0]`: `custom` ranking rule is invalid. Valid ranking rules are words, typo, sort, proximity, attribute, exactness and custom ranking rules.",
|
||||
"code": "invalid_settings_ranking_rules",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#invalid-settings-ranking-rules"
|
||||
@ -899,7 +899,7 @@ async fn test_summarized_task_cancelation() {
|
||||
// to avoid being flaky we're only going to cancel an already finished task :(
|
||||
index.create(None).await;
|
||||
index.wait_task(0).await;
|
||||
server.cancel_tasks(json!({ "uids": [0] })).await;
|
||||
server.cancel_tasks("uids=0").await;
|
||||
index.wait_task(1).await;
|
||||
let (task, _) = index.get_task(1).await;
|
||||
assert_json_snapshot!(task,
|
||||
@ -932,7 +932,7 @@ async fn test_summarized_task_deletion() {
|
||||
// to avoid being flaky we're only going to delete an already finished task :(
|
||||
index.create(None).await;
|
||||
index.wait_task(0).await;
|
||||
server.delete_tasks(json!({ "uids": [0] })).await;
|
||||
server.delete_tasks("uids=0").await;
|
||||
index.wait_task(1).await;
|
||||
let (task, _) = index.get_task(1).await;
|
||||
assert_json_snapshot!(task,
|
||||
|
Loading…
x
Reference in New Issue
Block a user