diff --git a/Cargo.lock b/Cargo.lock index 8d23438ed..eb36947e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -490,16 +490,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -660,17 +650,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "dyn-clone" version = "1.0.19" @@ -750,6 +729,18 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "faststr" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6503af7917fea18ffef8f7e8553fb8dff89e2e6837e94e09dd7fb069c82d62c" +dependencies = [ + "bytes", + "rkyv", + "serde", + "simdutf8", +] + [[package]] name = "fixedbitset" version = "0.5.7" @@ -768,21 +759,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -898,14 +874,17 @@ dependencies = [ "graphql-parser", "graphql-tools", "http", + "http-body-util", "hyper", "lazy_static", + "mimalloc", "moka", "query-plan-executor", "query-planner", "rand", "serde", "serde_json", + "sonic-rs", "thiserror 2.0.12", "tokio", "tower", @@ -941,17 +920,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.3.3" @@ -1138,45 +1106,12 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ - "base64", "bytes", "futures-channel", "futures-core", @@ -1184,16 +1119,12 @@ dependencies = [ "http", "http-body", "hyper", - "ipnet", "libc", - "percent-encoding", "pin-project-lite", "socket2", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -1220,119 +1151,12 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -1377,22 +1201,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "itertools" version = "0.10.5" @@ -1446,16 +1254,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] -name = "linux-raw-sys" -version = "0.9.4" +name = "libmimalloc-sys" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d" +dependencies = [ + "cc", + "libc", +] [[package]] -name = "litemap" -version = "0.8.0" +name = "linux-raw-sys" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lock_api" @@ -1507,6 +1319,15 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "mimalloc" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40" +dependencies = [ + "libmimalloc-sys", +] + [[package]] name = "mime" version = "0.3.17" @@ -1593,20 +1414,23 @@ dependencies = [ ] [[package]] -name = "native-tls" -version = "0.2.14" +name = "munge" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "9cce144fab80fbb74ec5b89d1ca9d41ddf6b644ab7e986f7d3ed0aab31625cb1" dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "574af9cd5b9971cbfdf535d6a8d533778481b241c447826d976101e0149392a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1664,50 +1488,6 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" -[[package]] -name = "openssl" -version = "0.10.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "overload" version = "0.1.1" @@ -1817,12 +1597,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - [[package]] name = "plotters" version = "0.3.7" @@ -1857,15 +1631,6 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" -[[package]] -name = "potential_utf" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" -dependencies = [ - "zerovec", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -1899,6 +1664,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ptr_meta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "qp-dev-cli" version = "0.0.1" @@ -1920,11 +1705,16 @@ dependencies = [ "futures", "graphql-parser", "graphql-tools", + "http", + "http-body-util", + "hyper", + "hyper-util", + "indexmap 2.10.0", "insta", "query-planner", - "reqwest", "serde", "serde_json", + "sonic-rs", "subgraphs", "tokio", "tokio-test", @@ -1978,6 +1768,15 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rancor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf5f7161924b9d1cea0e4cabc97c372cea92b5f927fc13c6bca67157a0ad947" +dependencies = [ + "ptr_meta", +] + [[package]] name = "rand" version = "0.9.2" @@ -2004,7 +1803,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom", ] [[package]] @@ -2101,57 +1900,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] -name = "reqwest" -version = "0.12.22" +name = "rend" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35e8a6bf28cd121053a66aa2e6a2e3eaffad4a60012179f0e864aa5ffeff215" + +[[package]] +name = "rkyv" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "1e147371c75553e1e2fcdb483944a8540b8438c31426279553b9a8182a9b7b65" dependencies = [ - "base64", "bytes", - "encoding_rs", - "futures-core", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", + "hashbrown 0.15.4", + "indexmap 2.10.0", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", ] [[package]] -name = "ring" -version = "0.17.14" +name = "rkyv_derive" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +checksum = "246b40ac189af6c675d124b802e8ef6d5246c53e17367ce9501f8f66a81abb7a" dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2188,39 +1968,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "rustls" -version = "0.23.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.21" @@ -2242,15 +1989,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "schemars" version = "0.9.0" @@ -2287,29 +2025,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.26" @@ -2342,7 +2057,6 @@ version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ - "indexmap 2.10.0", "itoa", "memchr", "ryu", @@ -2449,6 +2163,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "similar" version = "2.7.0" @@ -2478,16 +2198,48 @@ dependencies = [ ] [[package]] -name = "spin" -version = "0.9.8" +name = "sonic-number" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "a8a74044c092f4f43ca7a6cfd62854cf9fb5ac8502b131347c990bf22bef1dfe" +dependencies = [ + "cfg-if", +] [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "sonic-rs" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0275f9f2f07d47556fe60c2759da8bc4be6083b047b491b2d476aa0bfa558eb1" +dependencies = [ + "bumpalo", + "bytes", + "cfg-if", + "faststr", + "itoa", + "ref-cast", + "ryu", + "serde", + "simdutf8", + "sonic-number", + "sonic-simd", + "thiserror 2.0.12", +] + +[[package]] +name = "sonic-simd" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b421f7b6aa4a5de8f685aaf398dfaa828346ee639d2b1c1061ab43d40baa6223" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "static_assertions_next" @@ -2535,12 +2287,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "syn" version = "2.0.104" @@ -2557,41 +2303,6 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] [[package]] name = "tagptr" @@ -2606,7 +2317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2692,16 +2403,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinytemplate" version = "1.2.1" @@ -2712,6 +2413,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.46.1" @@ -2743,26 +2459,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" -dependencies = [ - "rustls", - "tokio", -] - [[package]] name = "tokio-stream" version = "0.1.17" @@ -2861,14 +2557,12 @@ dependencies = [ "http-body-util", "http-range-header", "httpdate", - "iri-string", "mime", "mime_guess", "percent-encoding", "pin-project-lite", "tokio", "tokio-util", - "tower", "tower-layer", "tower-service", "tracing", @@ -3043,42 +2737,19 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "uuid" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.3", + "getrandom", "js-sys", "wasm-bindgen", ] @@ -3089,12 +2760,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" @@ -3365,17 +3030,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -3576,36 +3230,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "zerocopy" version = "0.8.26" @@ -3625,63 +3249,3 @@ dependencies = [ "quote", "syn", ] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/bench/k6.js b/bench/k6.js index 14ea958d8..f7f45e6ba 100644 --- a/bench/k6.js +++ b/bench/k6.js @@ -5,11 +5,11 @@ import { githubComment } from "https://raw.githubusercontent.com/dotansimha/k6-g const endpoint = __ENV.GATEWAY_ENDPOINT || "http://0.0.0.0:4000/graphql"; const vus = __ENV.BENCH_VUS ? parseInt(__ENV.BENCH_VUS) : 50; -const time = __ENV.BENCH_OVER_TIME || "30s"; +const duration = __ENV.BENCH_OVER_TIME || "30s"; export const options = { - vus: vus, - duration: time, + vus, + duration, }; export function setup() { @@ -57,7 +57,7 @@ export function handleSummary(data) { }, }); } - return handleBenchmarkSummary(data, { vus, time }); + return handleBenchmarkSummary(data, { vus, duration }); } let printIdentifiersMap = {}; diff --git a/bin/dev-cli/Cargo.toml b/bin/dev-cli/Cargo.toml index 3e5f73875..7e4680a0a 100644 --- a/bin/dev-cli/Cargo.toml +++ b/bin/dev-cli/Cargo.toml @@ -12,4 +12,4 @@ query-planner = { path = "../../lib/query-planner" } graphql-parser = "0.4.1" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } tracing-tree = "0.4.0" -serde_json = { version = "1.0.120", features = ["preserve_order"] } +serde_json = "1.0.140" diff --git a/bin/gateway/Cargo.toml b/bin/gateway/Cargo.toml index 8a61841aa..80e6f465d 100644 --- a/bin/gateway/Cargo.toml +++ b/bin/gateway/Cargo.toml @@ -11,6 +11,8 @@ path = "src/main.rs" query-planner = { path = "../../lib/query-planner" } query-plan-executor = { path = "../../lib/query-plan-executor" } +mimalloc = { version = "0.1.47", features = ["override"] } + # Tokio runtime tokio = { version = "1.38.0", features = ["full"] } @@ -20,7 +22,8 @@ graphql-tools = "0.4.0" # Using version from original file # Serialization serde = { version = "1.0.203", features = ["derive"] } -serde_json = { version = "1.0.120", features = ["preserve_order"] } +serde_json = "1.0.140" +sonic-rs = "0.3" # HTTP client and caching moka = { version = "0.12.8", features = ["future"] } @@ -48,6 +51,7 @@ tower-http = { version = "0.6.6", features = [ "cors", "request-id", ] } +http-body-util = "0.1.3" # Utils thiserror = "2.0.12" diff --git a/bin/gateway/src/main.rs b/bin/gateway/src/main.rs index 3485df0a3..cd6ce2648 100644 --- a/bin/gateway/src/main.rs +++ b/bin/gateway/src/main.rs @@ -18,6 +18,7 @@ use axum::{ Router, }; use http::Request; +use mimalloc::MiMalloc; use tokio::signal; use axum::Extension; @@ -28,7 +29,6 @@ use crate::pipeline::{ coerce_variables_service::CoerceVariablesService, execution_service::ExecutionService, graphiql_service::GraphiQLResponderService, graphql_request_params::GraphQLRequestParamsExtractor, - http_request_params::HttpRequestParamsExtractor, normalize_service::GraphQLOperationNormalizationService, parser_service::GraphQLParserService, progressive_override_service::ProgressiveOverrideExtractor, query_plan_service::QueryPlanService, validation_service::GraphQLValidationService, @@ -43,6 +43,9 @@ use tower_http::{ }; use tracing::info; +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + #[tokio::main] async fn main() -> Result<(), Box> { let perfetto_file = env::var("PERFETTO_OUT").ok().is_some_and(|v| v == "1"); @@ -95,14 +98,13 @@ async fn main() -> Result<(), Box> { ) }), ) - .layer(HttpRequestParamsExtractor::new_layer()) .layer(GraphiQLResponderService::new_layer()) .layer(GraphQLRequestParamsExtractor::new_layer()) .layer(GraphQLParserService::new_layer()) + .layer(GraphQLValidationService::new_layer()) .layer(ProgressiveOverrideExtractor::new_layer()) .layer(GraphQLOperationNormalizationService::new_layer()) .layer(CoerceVariablesService::new_layer()) - .layer(GraphQLValidationService::new_layer()) .layer(QueryPlanService::new_layer()) .layer(PropagateRequestIdLayer::new(REQUEST_ID_HEADER_NAME.clone())) .service(ExecutionService::new(expose_query_plan)); diff --git a/bin/gateway/src/pipeline/coerce_variables_service.rs b/bin/gateway/src/pipeline/coerce_variables_service.rs index 68c1ba3d0..850a16b96 100644 --- a/bin/gateway/src/pipeline/coerce_variables_service.rs +++ b/bin/gateway/src/pipeline/coerce_variables_service.rs @@ -2,17 +2,17 @@ use std::collections::HashMap; use std::sync::Arc; use axum::body::Body; -use http::Request; +use http::{Method, Request}; use query_plan_executor::variables::collect_variables; -use query_plan_executor::ExecutionRequest; +use query_planner::state::supergraph_state::OperationKind; use serde_json::Value; -use tracing::{trace, warn}; +use tracing::{error, trace, warn}; -use crate::pipeline::error::{PipelineError, PipelineErrorVariant}; +use crate::pipeline::error::{PipelineError, PipelineErrorFromAcceptHeader, PipelineErrorVariant}; use crate::pipeline::gateway_layer::{ GatewayPipelineLayer, GatewayPipelineStepDecision, ProcessorLayer, }; -use crate::pipeline::http_request_params::HttpRequestParams; +use crate::pipeline::graphql_request_params::ExecutionRequest; use crate::pipeline::normalize_service::GraphQLNormalizationPayload; use crate::shared_state::GatewaySharedState; @@ -35,30 +35,44 @@ impl GatewayPipelineLayer for CoerceVariablesService { #[tracing::instrument(level = "trace", name = "CoerceVariablesService", skip_all)] async fn process( &self, - mut req: Request, - ) -> Result<(Request, GatewayPipelineStepDecision), PipelineError> { + req: &mut Request, + ) -> Result { let normalized_operation = req .extensions() - .get::() + .get::>() .ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("GraphQLNormalizationPayload is missing") + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "GraphQLNormalizationPayload is missing", + )) })?; - let http_payload = req.extensions().get::().ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("HttpRequestParams is missing") - })?; - let execution_params = req.extensions().get::().ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("ExecutionRequest is missing") + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "ExecutionRequest is missing", + )) })?; let app_state = req .extensions() .get::>() .ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("GatewaySharedState is missing") + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "GatewaySharedState is missing", + )) })?; + if req.method() == Method::GET { + if let Some(OperationKind::Mutation) = + normalized_operation.operation_for_plan.operation_kind + { + error!("Mutation is not allowed over GET, stopping"); + + return Err( + req.new_pipeline_error(PipelineErrorVariant::MutationNotAllowedOverHttpGet) + ); + } + } + match collect_variables( &normalized_operation.operation_for_plan, &execution_params.variables, @@ -74,18 +88,16 @@ impl GatewayPipelineLayer for CoerceVariablesService { variables_map: values, }); - Ok((req, GatewayPipelineStepDecision::Continue)) + Ok(GatewayPipelineStepDecision::Continue) } Err(err_msg) => { warn!( "failed to collect variables from incoming request: {}", err_msg ); - - return Err(PipelineError::new_with_accept_header( - PipelineErrorVariant::VariablesCoercionError(err_msg), - http_payload.accept_header.clone(), - )); + return Err( + req.new_pipeline_error(PipelineErrorVariant::VariablesCoercionError(err_msg)) + ); } } } diff --git a/bin/gateway/src/pipeline/error.rs b/bin/gateway/src/pipeline/error.rs index 1723d60a6..73ad19641 100644 --- a/bin/gateway/src/pipeline/error.rs +++ b/bin/gateway/src/pipeline/error.rs @@ -2,28 +2,27 @@ use std::{collections::HashMap, sync::Arc}; use axum::{body::Body, extract::rejection::QueryRejection, response::IntoResponse}; use graphql_tools::validation::utils::ValidationError; -use http::{Response, StatusCode}; +use http::{HeaderName, Method, Request, Response, StatusCode}; use query_plan_executor::{ExecutionResult, GraphQLError}; use query_planner::{ast::normalization::error::NormalizationError, planner::PlannerError}; use serde_json::Value; -use crate::pipeline::http_request_params::APPLICATION_JSON; +use crate::pipeline::header::{RequestAccepts, APPLICATION_GRAPHQL_RESPONSE_JSON_STR}; #[derive(Debug)] pub struct PipelineError { - pub accept_header: Option, + pub accept_ok: bool, pub error: PipelineErrorVariant, } -impl PipelineError { - pub fn new_with_accept_header( - error: PipelineErrorVariant, - accept_header_value: String, - ) -> Self { - Self { - accept_header: Some(accept_header_value), - error, - } +pub trait PipelineErrorFromAcceptHeader { + fn new_pipeline_error(&self, error: PipelineErrorVariant) -> PipelineError; +} + +impl PipelineErrorFromAcceptHeader for Request { + fn new_pipeline_error(&self, error: PipelineErrorVariant) -> PipelineError { + let accept_ok = !self.accepts_content_type(&APPLICATION_GRAPHQL_RESPONSE_JSON_STR); + PipelineError { accept_ok, error } } } @@ -35,9 +34,9 @@ pub enum PipelineErrorVariant { // HTTP-related errors #[error("Unsupported HTTP method: {0}")] - UnsupportedHttpMethod(String), + UnsupportedHttpMethod(Method), #[error("Header '{0}' has invalid value")] - InvalidHeaderValue(String), + InvalidHeaderValue(HeaderName), #[error("Failed to read body: {0}")] FailedToReadBodyBytes(axum::Error), #[error("Content-Type header is missing")] @@ -55,11 +54,11 @@ pub enum PipelineErrorVariant { // GraphQL-specific errors #[error("Failed to parse GraphQL request payload")] - FailedToParseBody(serde_json::Error), + FailedToParseBody(sonic_rs::Error), #[error("Failed to parse GraphQL variables JSON")] - FailedToParseVariables(serde_json::Error), + FailedToParseVariables(sonic_rs::Error), #[error("Failed to parse GraphQL extensions JSON")] - FailedToParseExtensions(serde_json::Error), + FailedToParseExtensions(sonic_rs::Error), #[error("Failed to parse GraphQL operation")] FailedToParseOperation(graphql_parser::query::ParseError), #[error("Failed to normalize GraphQL operation")] @@ -78,19 +77,27 @@ impl PipelineErrorVariant { Self::UnsupportedHttpMethod(_) => "METHOD_NOT_ALLOWED", Self::PlannerError(_) => "QUERY_PLAN_BUILD_FAILED", Self::InternalServiceError(_) => "INTERNAL_SERVER_ERROR", + Self::FailedToParseOperation(_) => "GRAPHQL_PARSE_FAILED", + Self::ValidationErrors(_) => "GRAPHQL_VALIDATION_FAILED", + Self::VariablesCoercionError(_) => "BAD_USER_INPUT", + Self::NormalizationError(NormalizationError::OperationNotFound) => { + "OPERATION_RESOLUTION_FAILURE" + } + Self::NormalizationError(NormalizationError::SpecifiedOperationNotFound { + operation_name: _, + }) => "OPERATION_RESOLUTION_FAILURE", + Self::NormalizationError(NormalizationError::MultipleMatchingOperationsFound) => { + "OPERATION_RESOLUTION_FAILURE" + } _ => "BAD_REQUEST", } } pub fn graphql_error_message(&self) -> String { match self { - Self::PlannerError(_) | Self::InternalServiceError(_) => { - return "Unexpected error".to_string() - } - _ => {} + Self::PlannerError(_) | Self::InternalServiceError(_) => "Unexpected error".to_string(), + _ => self.to_string(), } - - self.to_string() } pub fn default_status_code(&self, prefer_ok: bool) -> StatusCode { @@ -111,34 +118,17 @@ impl PipelineErrorVariant { (Self::VariablesCoercionError(_), false) => StatusCode::BAD_REQUEST, (Self::VariablesCoercionError(_), true) => StatusCode::OK, (Self::MutationNotAllowedOverHttpGet, _) => StatusCode::METHOD_NOT_ALLOWED, - (Self::ValidationErrors(_), _) => StatusCode::BAD_REQUEST, - (Self::MissingContentTypeHeader, _) => StatusCode::BAD_REQUEST, - (Self::UnsupportedContentType, _) => StatusCode::BAD_REQUEST, - } - } -} - -impl From for PipelineErrorVariant { - fn from(error: PipelineError) -> Self { - error.error - } -} - -impl From for PipelineError { - fn from(error: PipelineErrorVariant) -> Self { - Self { - error, - accept_header: None, + (Self::ValidationErrors(_), true) => StatusCode::OK, + (Self::ValidationErrors(_), false) => StatusCode::BAD_REQUEST, + (Self::MissingContentTypeHeader, _) => StatusCode::NOT_ACCEPTABLE, + (Self::UnsupportedContentType, _) => StatusCode::UNSUPPORTED_MEDIA_TYPE, } } } impl IntoResponse for PipelineError { fn into_response(self) -> Response { - let accept_ok = self - .accept_header - .is_some_and(|v| v.contains(APPLICATION_JSON.to_str().unwrap())); - let status = self.error.default_status_code(accept_ok); + let status = self.error.default_status_code(self.accept_ok); if let PipelineErrorVariant::ValidationErrors(validation_errors) = self.error { let validation_error_result = ExecutionResult { @@ -148,7 +138,7 @@ impl IntoResponse for PipelineError { }; return ( - StatusCode::OK, + status, serde_json::to_string(&validation_error_result).unwrap(), ) .into_response(); diff --git a/bin/gateway/src/pipeline/execution_service.rs b/bin/gateway/src/pipeline/execution_service.rs index e0cd3cc3d..26c1f6064 100644 --- a/bin/gateway/src/pipeline/execution_service.rs +++ b/bin/gateway/src/pipeline/execution_service.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::convert::Infallible; use std::future::Future; use std::pin::Pin; @@ -6,18 +5,18 @@ use std::sync::Arc; use std::task::{Context, Poll}; use crate::pipeline::coerce_variables_service::CoerceVariablesPayload; -use crate::pipeline::http_request_params::HttpRequestParams; +use crate::pipeline::header::{ + RequestAccepts, APPLICATION_GRAPHQL_RESPONSE_JSON, APPLICATION_GRAPHQL_RESPONSE_JSON_STR, + APPLICATION_JSON, +}; use crate::pipeline::normalize_service::GraphQLNormalizationPayload; use crate::pipeline::query_plan_service::QueryPlanPayload; use crate::shared_state::GatewaySharedState; use axum::body::Body; -use axum::response::IntoResponse; -use axum::Json; -use http::header::CONTENT_TYPE; -use http::{HeaderName, Request, Response}; -use query_plan_executor::{execute_query_plan, ExecutionResult}; -use serde_json::{json, to_value}; +use http::{HeaderName, HeaderValue, Request, Response}; +use query_plan_executor::{execute_query_plan, ExposeQueryPlanMode}; use tower::Service; +use tracing::trace; #[derive(Clone, Debug, Default)] pub struct ExecutionService { @@ -26,13 +25,6 @@ pub struct ExecutionService { static EXPOSE_QUERY_PLAN_HEADER: HeaderName = HeaderName::from_static("hive-expose-query-plan"); -#[derive(Clone, Debug, PartialEq, Eq)] -enum ExposeQueryPlanMode { - Yes, - No, - DryRun, -} - impl ExecutionService { pub fn new(expose_query_plan: bool) -> Self { Self { expose_query_plan } @@ -69,7 +61,7 @@ impl Service> for ExecutionService { Box::pin(async move { let normalized_payload = req .extensions() - .get::() + .get::>() .expect("GraphQLNormalizationPayload missing"); let query_plan_payload = req .extensions() @@ -85,47 +77,48 @@ impl Service> for ExecutionService { .get::() .expect("CoerceVariablesPayload missing"); - let http_request_params = req - .extensions() - .get::() - .expect("HttpRequestParams missing"); - - let mut execution_result = match expose_query_plan { - ExposeQueryPlanMode::DryRun => ExecutionResult { - data: Some(json!({})), - errors: None, - extensions: Some(HashMap::new()), - }, - _ => { - execute_query_plan( - &query_plan_payload.query_plan, - &app_state.subgraph_executor_map, - &variable_payload.variables_map, - &app_state.schema_metadata, - &normalized_payload.normalized_document.operation, - normalized_payload.has_introspection, - ) - .await - } - }; - - if expose_query_plan == ExposeQueryPlanMode::Yes - || expose_query_plan == ExposeQueryPlanMode::DryRun - { - let plan_value = to_value(query_plan_payload.query_plan.as_ref()).unwrap(); - - execution_result - .extensions - .get_or_insert_with(HashMap::new) - .insert("queryPlan".to_string(), plan_value); - } - - let mut response = Json(execution_result).into_response(); - response.headers_mut().insert( - CONTENT_TYPE, - http_request_params.response_content_type.clone(), + let execution_result = execute_query_plan( + &query_plan_payload.query_plan, + &app_state.subgraph_executor_map, + &variable_payload.variables_map, + &app_state.schema_metadata, + normalized_payload.root_type_name, + &normalized_payload.projection_plan, + normalized_payload.has_introspection, + expose_query_plan, + ) + .await + .unwrap_or_else(|err| { + tracing::error!("Failed to execute query plan: {}", err); + serde_json::to_vec(&serde_json::json!({ + "errors": [{ + "message": "Internal server error", + "extensions": { + "code": "INTERNAL_SERVER_ERROR" + } + }] + })) + .unwrap_or_default() + }); + + let mut response = Response::new(Body::from(execution_result)); + + let response_content_type: &'static HeaderValue = + if req.accepts_content_type(*APPLICATION_GRAPHQL_RESPONSE_JSON_STR) { + &APPLICATION_GRAPHQL_RESPONSE_JSON + } else { + &APPLICATION_JSON + }; + + trace!( + "Will use the following Content-Type header for response: {:?}", + response_content_type ); + response + .headers_mut() + .insert(http::header::CONTENT_TYPE, response_content_type.clone()); + Ok(response) }) } diff --git a/bin/gateway/src/pipeline/gateway_layer.rs b/bin/gateway/src/pipeline/gateway_layer.rs index 1e8c1c400..47293f476 100644 --- a/bin/gateway/src/pipeline/gateway_layer.rs +++ b/bin/gateway/src/pipeline/gateway_layer.rs @@ -2,6 +2,7 @@ use axum::body::Body; use axum::response::IntoResponse; use http::{Request, Response}; use std::convert::Infallible; +use std::sync::Arc; use std::{ future::Future, pin::Pin, @@ -16,8 +17,8 @@ use crate::pipeline::error::PipelineError; pub trait GatewayPipelineLayer: Clone + Send + Sync + 'static { async fn process( &self, - req: Request, - ) -> Result<(Request, GatewayPipelineStepDecision), PipelineError>; + req: &mut Request, + ) -> Result; } pub enum GatewayPipelineStepDecision { @@ -28,12 +29,15 @@ pub enum GatewayPipelineStepDecision { #[derive(Debug, Clone)] pub struct ProcessorService { inner: S, - processor: P, + processor_arc: Arc

, } impl ProcessorService { - pub fn new_layer(inner: S, processor: P) -> Self { - Self { inner, processor } + pub fn new_layer(inner: S, processor_arc: Arc

) -> Self { + Self { + inner, + processor_arc, + } } } @@ -42,6 +46,7 @@ where S: Service, Response = Response, Error = Infallible> + Clone + Send + + Sync + 'static, S::Future: Send + 'static, P: GatewayPipelineLayer, @@ -54,12 +59,12 @@ where self.inner.poll_ready(cx) } - fn call(&mut self, req: Request) -> Self::Future { + fn call(&mut self, mut req: Request) -> Self::Future { let mut inner = self.inner.clone(); - let processor = self.processor.clone(); + let processor_arc = self.processor_arc.clone(); Box::pin(async move { - let result = processor.process(req).await; + let result = processor_arc.process(&mut req).await; match result { Err(err) => { @@ -68,12 +73,12 @@ where Ok(err.into_response()) } - Ok((req, GatewayPipelineStepDecision::Continue)) => { + Ok(GatewayPipelineStepDecision::Continue) => { trace!("Pipeline step decision is to continue"); inner.call(req).await } - Ok((_req, GatewayPipelineStepDecision::RespondWith(response))) => { + Ok(GatewayPipelineStepDecision::RespondWith(response)) => { trace!("Pipeline step decision is to short circuit"); Ok(response) @@ -85,12 +90,14 @@ where #[derive(Debug, Clone)] pub struct ProcessorLayer

{ - processor: P, + processor_arc: Arc

, } impl

ProcessorLayer

{ pub fn new(processor: P) -> Self { - Self { processor } + Self { + processor_arc: Arc::new(processor), + } } } @@ -101,6 +108,7 @@ where type Service = ProcessorService; fn layer(&self, inner: S) -> Self::Service { - ProcessorService::new_layer(inner, self.processor.clone()) + let processor_arc = self.processor_arc.clone(); + ProcessorService::new_layer(inner, processor_arc) } } diff --git a/bin/gateway/src/pipeline/graphiql_service.rs b/bin/gateway/src/pipeline/graphiql_service.rs index ea350b5bf..29f9957a8 100644 --- a/bin/gateway/src/pipeline/graphiql_service.rs +++ b/bin/gateway/src/pipeline/graphiql_service.rs @@ -2,11 +2,11 @@ use axum::body::Body; use axum::response::IntoResponse; use http::{Method, Request}; -use crate::pipeline::error::{PipelineError, PipelineErrorVariant}; +use crate::pipeline::error::PipelineError; use crate::pipeline::gateway_layer::{ GatewayPipelineLayer, GatewayPipelineStepDecision, ProcessorLayer, }; -use crate::pipeline::http_request_params::HttpRequestParams; +use crate::pipeline::header::RequestAccepts; use axum::response::Html; @@ -19,21 +19,15 @@ pub struct GraphiQLResponderService; impl GatewayPipelineLayer for GraphiQLResponderService { async fn process( &self, - req: Request, - ) -> Result<(Request, GatewayPipelineStepDecision), PipelineError> { - let http_params = req.extensions().get::().ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("HttpRequestParams is missing") - })?; - - if http_params.http_method == Method::GET && http_params.accept_header.contains("text/html") - { - return Ok(( - req, - GatewayPipelineStepDecision::RespondWith(Html(GRAPHIQL_HTML).into_response()), + req: &mut Request, + ) -> Result { + if req.method() == Method::GET && req.accepts_content_type("text/html") { + return Ok(GatewayPipelineStepDecision::RespondWith( + Html(GRAPHIQL_HTML).into_response(), )); } - Ok((req, GatewayPipelineStepDecision::Continue)) + Ok(GatewayPipelineStepDecision::Continue) } } diff --git a/bin/gateway/src/pipeline/graphql_request_params.rs b/bin/gateway/src/pipeline/graphql_request_params.rs index 37009cd24..cf93cdd64 100644 --- a/bin/gateway/src/pipeline/graphql_request_params.rs +++ b/bin/gateway/src/pipeline/graphql_request_params.rs @@ -1,16 +1,18 @@ -use axum::body::{to_bytes, Body}; +use std::collections::HashMap; + +use axum::body::Body; use axum::extract::Query; use http::{Method, Request}; -use query_plan_executor::ExecutionRequest; +use http_body_util::BodyExt; +use serde::Deserialize; +use serde_json::Value; use tracing::{trace, warn}; -use crate::pipeline::error::{PipelineError, PipelineErrorVariant}; +use crate::pipeline::error::{PipelineError, PipelineErrorFromAcceptHeader, PipelineErrorVariant}; use crate::pipeline::gateway_layer::{ GatewayPipelineLayer, GatewayPipelineStepDecision, ProcessorLayer, }; -use crate::pipeline::http_request_params::{HttpRequestParams, APPLICATION_JSON}; - -static MAX_BODY_SIZE: usize = 2 * 1024 * 1024; // 2 MB in bytes, like Axum's default +use crate::pipeline::header::AssertRequestJson; #[derive(Clone, Debug, Default)] pub struct GraphQLRequestParamsExtractor; @@ -24,6 +26,17 @@ struct GETQueryParams { pub extensions: Option, } +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ExecutionRequest { + pub query: String, + pub operation_name: Option, + pub variables: Option>, + // TODO: We don't use extensions yet, but we definitely will in the future. + #[allow(dead_code)] + pub extensions: Option>, +} + impl TryInto for GETQueryParams { type Error = PipelineErrorVariant; @@ -34,7 +47,7 @@ impl TryInto for GETQueryParams { }; let variables = match self.variables.as_deref() { - Some(v_str) if !v_str.is_empty() => match serde_json::from_str(v_str) { + Some(v_str) if !v_str.is_empty() => match sonic_rs::from_str(v_str) { Ok(vars) => Some(vars), Err(e) => { return Err(PipelineErrorVariant::FailedToParseVariables(e)); @@ -44,7 +57,7 @@ impl TryInto for GETQueryParams { }; let extensions = match self.extensions.as_deref() { - Some(e_str) if !e_str.is_empty() => match serde_json::from_str(e_str) { + Some(e_str) if !e_str.is_empty() => match sonic_rs::from_str(e_str) { Ok(exts) => Some(exts), Err(e) => { return Err(PipelineErrorVariant::FailedToParseExtensions(e)); @@ -69,92 +82,64 @@ impl GatewayPipelineLayer for GraphQLRequestParamsExtractor { #[tracing::instrument(level = "trace", name = "GraphQLRequestParamsExtractor", skip_all)] async fn process( &self, - mut req: Request, - ) -> Result<(Request, GatewayPipelineStepDecision), PipelineError> { - let http_params = req.extensions().get::().ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("HttpRequestParams is missing") - })?; - - let accept_header = http_params.accept_header.clone(); - let execution_request: ExecutionRequest = match http_params.http_method { - Method::GET => { - trace!("processing GET GraphQL operation"); - - let query_params = Query::::try_from_uri(req.uri()) - .map_err(|qe| { - PipelineError::new_with_accept_header( - PipelineErrorVariant::GetInvalidQueryParams(qe), - accept_header.clone(), - ) - })? - .0; - - trace!("parsed GET query params: {:?}", query_params); - - query_params.try_into()? - } - Method::POST => { - trace!("Processing POST GraphQL request"); - - match &http_params.request_content_type { - None => { - trace!("POST without content type detected"); - - return Err(PipelineError::new_with_accept_header( - PipelineErrorVariant::MissingContentTypeHeader, - accept_header.clone(), - )); - } - Some(content_type) => { - if !content_type.contains(APPLICATION_JSON.to_str().unwrap()) { - warn!("Invalid content type on a POST request: {}", content_type); - - return Err(PipelineError::new_with_accept_header( - PipelineErrorVariant::UnsupportedContentType, - accept_header.clone(), - )); - } - } + req: &mut Request, + ) -> Result { + let http_method = req.method(); + let execution_request: ExecutionRequest = + match *http_method { + Method::GET => { + trace!("processing GET GraphQL operation"); + + let query_params = Query::::try_from_uri(req.uri()) + .map_err(|qe| { + req.new_pipeline_error(PipelineErrorVariant::GetInvalidQueryParams(qe)) + })? + .0; + + trace!("parsed GET query params: {:?}", query_params); + + query_params + .try_into() + .map_err(|err| req.new_pipeline_error(err))? + } + Method::POST => { + trace!("Processing POST GraphQL request"); + + req.assert_json_content_type()?; + + let body_bytes = req + .body_mut() + .collect() + .await + .map_err(|err| { + warn!("Failed to read body bytes: {}", err); + req.new_pipeline_error(PipelineErrorVariant::FailedToReadBodyBytes(err)) + })? + .to_bytes(); + + let execution_request = unsafe { + sonic_rs::from_slice_unchecked::(&body_bytes).map_err( + |e| { + warn!("Failed to parse body: {}", e); + req.new_pipeline_error(PipelineErrorVariant::FailedToParseBody(e)) + }, + )? + }; + + execution_request } + _ => { + warn!("unsupported HTTP method: {}", http_method); - let (parts, body) = req.into_parts(); - let body_bytes = to_bytes(body, MAX_BODY_SIZE).await.map_err(|err| { - warn!("Failed to read body bytes: {}", err); - - PipelineError::new_with_accept_header( - PipelineErrorVariant::FailedToReadBodyBytes(err), - accept_header.clone(), - ) - })?; - - let execution_request = serde_json::from_slice::(&body_bytes) - .map_err(|e| { - warn!("Failed to parse body: {}", e); - - PipelineError::new_with_accept_header( - PipelineErrorVariant::FailedToParseBody(e), - accept_header, - ) - })?; - - trace!("Body is parsed, will proceed"); - req = Request::from_parts(parts, Body::from(body_bytes)); - - execution_request - } - _ => { - warn!("unsupported HTTP method: {}", http_params.http_method); - - return Err(PipelineErrorVariant::UnsupportedHttpMethod( - http_params.http_method.to_string(), - ) - .into()); - } - }; + return Err(req.new_pipeline_error( + PipelineErrorVariant::UnsupportedHttpMethod(http_method.to_owned()), + )); + } + }; req.extensions_mut().insert(execution_request); - Ok((req, GatewayPipelineStepDecision::Continue)) + Ok(GatewayPipelineStepDecision::Continue) } } diff --git a/bin/gateway/src/pipeline/header.rs b/bin/gateway/src/pipeline/header.rs new file mode 100644 index 000000000..1dc620ce1 --- /dev/null +++ b/bin/gateway/src/pipeline/header.rs @@ -0,0 +1,65 @@ +use http::{ + header::{ACCEPT, CONTENT_TYPE}, + HeaderValue, +}; +use lazy_static::lazy_static; +use tracing::{trace, warn}; + +use crate::pipeline::error::{PipelineError, PipelineErrorFromAcceptHeader, PipelineErrorVariant}; + +lazy_static! { + pub static ref APPLICATION_JSON_STR: &'static str = "application/json"; + pub static ref APPLICATION_JSON: HeaderValue = HeaderValue::from_static(&APPLICATION_JSON_STR); + pub static ref APPLICATION_GRAPHQL_RESPONSE_JSON_STR: &'static str = + "application/graphql-response+json"; + pub static ref APPLICATION_GRAPHQL_RESPONSE_JSON: HeaderValue = + HeaderValue::from_static(&APPLICATION_GRAPHQL_RESPONSE_JSON_STR); +} + +pub trait RequestAccepts { + fn accepts_content_type(&self, content_type: &str) -> bool; +} + +impl RequestAccepts for http::Request { + fn accepts_content_type(&self, content_type: &str) -> bool { + let accept_header = self.headers().get(ACCEPT); + if let Some(value) = accept_header { + value + .to_str() + .map(|s| s.contains(content_type)) + .unwrap_or(false) + } else { + false + } + } +} + +pub trait AssertRequestJson { + fn assert_json_content_type(&self) -> Result<(), PipelineError>; +} + +impl AssertRequestJson for http::Request { + fn assert_json_content_type(&self) -> Result<(), PipelineError> { + match self.headers().get(CONTENT_TYPE) { + Some(value) => { + let content_type_str = value.to_str().map_err(|_| { + self.new_pipeline_error(PipelineErrorVariant::InvalidHeaderValue(CONTENT_TYPE)) + })?; + if !content_type_str.contains(*APPLICATION_JSON_STR) { + warn!( + "Invalid content type on a POST request: {}", + content_type_str + ); + return Err( + self.new_pipeline_error(PipelineErrorVariant::UnsupportedContentType) + ); + } + Ok(()) + } + None => { + trace!("POST without content type detected"); + Err(self.new_pipeline_error(PipelineErrorVariant::MissingContentTypeHeader)) + } + } + } +} diff --git a/bin/gateway/src/pipeline/http_request_params.rs b/bin/gateway/src/pipeline/http_request_params.rs deleted file mode 100644 index e6f57ada9..000000000 --- a/bin/gateway/src/pipeline/http_request_params.rs +++ /dev/null @@ -1,98 +0,0 @@ -use axum::body::Body; -use http::header::{ACCEPT, CONTENT_TYPE}; -use http::{HeaderValue, Method, Request}; -use lazy_static::lazy_static; -use tracing::trace; - -use crate::pipeline::error::{PipelineError, PipelineErrorVariant}; -use crate::pipeline::gateway_layer::{ - GatewayPipelineLayer, GatewayPipelineStepDecision, ProcessorLayer, -}; - -lazy_static! { - pub static ref APPLICATION_JSON: HeaderValue = HeaderValue::from_static("application/json"); - pub static ref APPLICATION_GRAPHQL_RESPONSE_JSON: HeaderValue = - HeaderValue::from_static("application/graphql-response+json"); -} - -#[derive(Debug, Clone)] -pub struct HttpRequestParams { - pub accept_header: String, - pub http_method: Method, - pub request_content_type: Option, - pub response_content_type: HeaderValue, -} - -#[derive(Clone, Debug, Default)] -pub struct HttpRequestParamsExtractor; - -impl HttpRequestParamsExtractor { - pub fn new_layer() -> ProcessorLayer { - ProcessorLayer::new(Self) - } -} - -#[async_trait::async_trait] -impl GatewayPipelineLayer for HttpRequestParamsExtractor { - #[tracing::instrument(level = "trace", name = "HttpRequestParamsExtractor", skip_all)] - async fn process( - &self, - mut req: Request, - ) -> Result<(Request, GatewayPipelineStepDecision), PipelineError> { - let http_method = req.method().to_owned(); - let accept_header = req - .headers() - .get(ACCEPT) - .unwrap_or(&APPLICATION_JSON) - .to_str() - .map_err(|_| PipelineErrorVariant::InvalidHeaderValue(ACCEPT.to_string()))? - .to_owned(); - - trace!( - "Using the following Accept header for request: {}", - &accept_header - ); - - let request_content_type: Option = match req.headers().get(CONTENT_TYPE) { - None => None, - Some(content_type) => { - let value = content_type - .to_str() - .map_err(|_| PipelineErrorVariant::InvalidHeaderValue(ACCEPT.to_string()))? - .to_owned(); - - Some(value) - } - }; - - trace!( - "Using the following Content-Type header for request: {:?}", - &request_content_type - ); - - let response_content_type = - if accept_header.contains(APPLICATION_GRAPHQL_RESPONSE_JSON.to_str().unwrap()) { - APPLICATION_GRAPHQL_RESPONSE_JSON.clone() - } else { - APPLICATION_JSON.clone() - }; - - trace!( - "Will use the following Content-Type header for response: {:?}", - &response_content_type - ); - - let extracted_params = HttpRequestParams { - http_method, - accept_header, - response_content_type, - request_content_type, - }; - - trace!("Extracted HTTP params: {:?}", extracted_params); - - req.extensions_mut().insert(extracted_params); - - Ok((req, GatewayPipelineStepDecision::Continue)) - } -} diff --git a/bin/gateway/src/pipeline/mod.rs b/bin/gateway/src/pipeline/mod.rs index 2b61cd5f2..d6c145017 100644 --- a/bin/gateway/src/pipeline/mod.rs +++ b/bin/gateway/src/pipeline/mod.rs @@ -4,7 +4,7 @@ pub mod execution_service; pub mod gateway_layer; pub mod graphiql_service; pub mod graphql_request_params; -pub mod http_request_params; +pub mod header; pub mod normalize_service; pub mod parser_service; pub mod progressive_override_service; diff --git a/bin/gateway/src/pipeline/normalize_service.rs b/bin/gateway/src/pipeline/normalize_service.rs index 0eb5c198e..08bdee1b5 100644 --- a/bin/gateway/src/pipeline/normalize_service.rs +++ b/bin/gateway/src/pipeline/normalize_service.rs @@ -1,28 +1,28 @@ +use std::hash::{DefaultHasher, Hash, Hasher}; use std::sync::Arc; use axum::body::Body; use http::Request; use query_plan_executor::introspection::filter_introspection_fields_in_operation; -use query_plan_executor::ExecutionRequest; -use query_planner::ast::document::NormalizedDocument; +use query_plan_executor::projection::FieldProjectionPlan; use query_planner::ast::normalization::normalize_operation; use query_planner::ast::operation::OperationDefinition; -use crate::pipeline::error::{PipelineError, PipelineErrorVariant}; +use crate::pipeline::error::{PipelineError, PipelineErrorFromAcceptHeader, PipelineErrorVariant}; use crate::pipeline::gateway_layer::{ GatewayPipelineLayer, GatewayPipelineStepDecision, ProcessorLayer, }; -use crate::pipeline::http_request_params::HttpRequestParams; +use crate::pipeline::graphql_request_params::ExecutionRequest; use crate::pipeline::parser_service::GraphQLParserPayload; use crate::shared_state::GatewaySharedState; use tracing::{error, trace}; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct GraphQLNormalizationPayload { - /// The raw, normalized GraphQL document. - pub normalized_document: NormalizedDocument, /// The operation to execute, without introspection fields. pub operation_for_plan: OperationDefinition, + pub root_type_name: &'static str, + pub projection_plan: Vec, pub has_introspection: bool, } @@ -44,68 +44,97 @@ impl GatewayPipelineLayer for GraphQLOperationNormalizationService { )] async fn process( &self, - mut req: Request, - ) -> Result<(Request, GatewayPipelineStepDecision), PipelineError> { + req: &mut Request, + ) -> Result { let parser_payload = req .extensions() .get::() .ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("GraphQLParserPayload is missing") + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "GraphQLParserPayload is missing", + )) })?; - let http_payload = req.extensions().get::().ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("HttpRequestParams is missing") - })?; let execution_params = req.extensions().get::().ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("ExecutionRequest is missing") + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "ExecutionRequest is missing", + )) })?; let app_state = req .extensions() .get::>() .ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("GatewaySharedState is missing") + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "GatewaySharedState is missing", + )) })?; - match normalize_operation( - &app_state.planner.supergraph, - &parser_payload.parsed_operation, - execution_params.operation_name.as_deref(), - ) { - Ok(doc) => { - trace!( - "Successfully normalized GraphQL operation (operation name={:?}): {}", - doc.operation_name, - doc.operation - ); - - let operation = &doc.operation; - let (has_introspection, filtered_operation_for_plan) = - filter_introspection_fields_in_operation(operation); + let cache_key = match &execution_params.operation_name { + Some(operation_name) => { + let mut hasher = DefaultHasher::new(); + execution_params.query.hash(&mut hasher); + operation_name.hash(&mut hasher); + hasher.finish() + } + None => parser_payload.cache_key, + }; + match app_state.normalize_cache.get(&cache_key).await { + Some(payload) => { trace!( - "Operation after removing introspection fields (introspection found={}): {}", - has_introspection, - filtered_operation_for_plan + "Found normalized GraphQL operation in cache (operation name={:?}): {}", + payload.operation_for_plan.name, + payload.operation_for_plan ); + req.extensions_mut().insert(payload); + Ok(GatewayPipelineStepDecision::Continue) + } + None => match normalize_operation( + &app_state.planner.supergraph, + &parser_payload.parsed_operation, + execution_params.operation_name.as_deref(), + ) { + Ok(doc) => { + trace!( + "Successfully normalized GraphQL operation (operation name={:?}): {}", + doc.operation_name, + doc.operation + ); - req.extensions_mut().insert(GraphQLNormalizationPayload { - normalized_document: doc, - operation_for_plan: filtered_operation_for_plan, - has_introspection, - }); + let operation = &doc.operation; + let (has_introspection, filtered_operation_for_plan) = + filter_introspection_fields_in_operation(operation); - Ok((req, GatewayPipelineStepDecision::Continue)) - } - Err(err) => { - error!("Failed to normalize GraphQL operation: {}", err); - trace!("{:?}", err); + trace!( + "Operation after removing introspection fields (introspection found={}): {}", + has_introspection, + filtered_operation_for_plan + ); - return Err(PipelineError::new_with_accept_header( - PipelineErrorVariant::NormalizationError(err), - http_payload.accept_header.clone(), - )); - } + let (root_type_name, projection_plan) = + FieldProjectionPlan::from_operation(operation, &app_state.schema_metadata); + let payload = GraphQLNormalizationPayload { + root_type_name, + projection_plan, + operation_for_plan: filtered_operation_for_plan, + has_introspection, + }; + let payload_arc = Arc::new(payload); + app_state + .normalize_cache + .insert(cache_key, payload_arc.clone()) + .await; + req.extensions_mut().insert(payload_arc); + Ok(GatewayPipelineStepDecision::Continue) + } + Err(err) => { + error!("Failed to normalize GraphQL operation: {}", err); + trace!("{:?}", err); + + Err(req.new_pipeline_error(PipelineErrorVariant::NormalizationError(err))) + } + }, } } } diff --git a/bin/gateway/src/pipeline/parser_service.rs b/bin/gateway/src/pipeline/parser_service.rs index 3fefa00bd..33cc9dc69 100644 --- a/bin/gateway/src/pipeline/parser_service.rs +++ b/bin/gateway/src/pipeline/parser_service.rs @@ -1,19 +1,23 @@ +use std::hash::{DefaultHasher, Hash, Hasher}; +use std::sync::Arc; + use axum::body::Body; use graphql_parser::query::Document; use http::Request; -use query_plan_executor::ExecutionRequest; use query_planner::utils::parsing::safe_parse_operation; -use crate::pipeline::error::{PipelineError, PipelineErrorVariant}; +use crate::pipeline::error::{PipelineError, PipelineErrorFromAcceptHeader, PipelineErrorVariant}; use crate::pipeline::gateway_layer::{ GatewayPipelineLayer, GatewayPipelineStepDecision, ProcessorLayer, }; -use crate::pipeline::http_request_params::HttpRequestParams; +use crate::pipeline::graphql_request_params::ExecutionRequest; +use crate::shared_state::GatewaySharedState; use tracing::{error, trace}; #[derive(Debug, Clone)] pub struct GraphQLParserPayload { - pub parsed_operation: Document<'static, String>, + pub parsed_operation: Arc>, + pub cache_key: u64, } #[derive(Clone, Debug, Default)] @@ -30,32 +34,51 @@ impl GatewayPipelineLayer for GraphQLParserService { #[tracing::instrument(level = "trace", name = "GraphQLParserService", skip_all)] async fn process( &self, - mut req: Request, - ) -> Result<(Request, GatewayPipelineStepDecision), PipelineError> { + req: &mut Request, + ) -> Result { let execution_params = req.extensions().get::().ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("ExecutionRequest is missing") - })?; - let http_params = req.extensions().get::().ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("HttpRequestParams is missing") + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "ExecutionRequest is missing", + )) })?; - match safe_parse_operation(&execution_params.query) { - Ok(parsed_operation) => { - trace!("sucessfully parsed GraphQL operation"); + let app_state = req + .extensions() + .get::>() + .ok_or_else(|| { + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "GatewaySharedState is missing", + )) + })?; - req.extensions_mut() - .insert(GraphQLParserPayload { parsed_operation }); + let cache_key = { + let mut hasher = DefaultHasher::new(); + execution_params.query.hash(&mut hasher); + hasher.finish() + }; - Ok((req, GatewayPipelineStepDecision::Continue)) - } - Err(err) => { + let parsed_operation = if let Some(cached) = app_state.parse_cache.get(&cache_key).await { + trace!("Found cached parsed operation for query"); + cached + } else { + let parsed = safe_parse_operation(&execution_params.query).map_err(|err| { error!("Failed to parse GraphQL operation: {}", err); + req.new_pipeline_error(PipelineErrorVariant::FailedToParseOperation(err)) + })?; + trace!("sucessfully parsed GraphQL operation"); + let parsed_arc = Arc::new(parsed); + app_state + .parse_cache + .insert(cache_key, parsed_arc.clone()) + .await; + parsed_arc + }; - Err(PipelineError::new_with_accept_header( - PipelineErrorVariant::FailedToParseOperation(err), - http_params.accept_header.clone(), - )) - } - } + req.extensions_mut().insert(GraphQLParserPayload { + parsed_operation, + cache_key, + }); + + Ok(GatewayPipelineStepDecision::Continue) } } diff --git a/bin/gateway/src/pipeline/progressive_override_service.rs b/bin/gateway/src/pipeline/progressive_override_service.rs index 6d1a9e813..76386a5d2 100644 --- a/bin/gateway/src/pipeline/progressive_override_service.rs +++ b/bin/gateway/src/pipeline/progressive_override_service.rs @@ -32,8 +32,8 @@ impl GatewayPipelineLayer for ProgressiveOverrideExtractor { #[tracing::instrument(level = "trace", name = "ProgressiveOverrideExtractor", skip_all)] async fn process( &self, - mut req: Request, - ) -> Result<(Request, GatewayPipelineStepDecision), PipelineError> { + req: &mut Request, + ) -> Result { // No active flags by default - until we implement it let active_flags = HashSet::new(); @@ -52,7 +52,7 @@ impl GatewayPipelineLayer for ProgressiveOverrideExtractor { req.extensions_mut().insert(override_context); - Ok((req, GatewayPipelineStepDecision::Continue)) + Ok(GatewayPipelineStepDecision::Continue) } } diff --git a/bin/gateway/src/pipeline/query_plan_service.rs b/bin/gateway/src/pipeline/query_plan_service.rs index 8108ce746..049d3c15a 100644 --- a/bin/gateway/src/pipeline/query_plan_service.rs +++ b/bin/gateway/src/pipeline/query_plan_service.rs @@ -3,7 +3,7 @@ use std::hash::DefaultHasher; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use crate::pipeline::error::{PipelineError, PipelineErrorVariant}; +use crate::pipeline::error::{PipelineError, PipelineErrorFromAcceptHeader, PipelineErrorVariant}; use crate::pipeline::gateway_layer::{ GatewayPipelineLayer, GatewayPipelineStepDecision, ProcessorLayer, }; @@ -68,26 +68,32 @@ impl GatewayPipelineLayer for QueryPlanService { #[tracing::instrument(level = "trace", name = "QueryPlanService", skip_all)] async fn process( &self, - mut req: Request, - ) -> Result<(Request, GatewayPipelineStepDecision), PipelineError> { + req: &mut Request, + ) -> Result { let normalized_operation = req .extensions() - .get::() + .get::>() .ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("GraphQLNormalizationPayload is missing") + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "GraphQLNormalizationPayload is missing", + )) })?; let app_state = req .extensions() .get::>() .ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("GatewaySharedState is missing") + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "GatewaySharedState is missing", + )) })?; let request_override_context = req .extensions() .get::() .ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("ProgressiveOverride is missing") + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "ProgressiveOverride is missing", + )) })?; let stable_override_context = @@ -124,7 +130,11 @@ impl GatewayPipelineLayer for QueryPlanService { request_override_context.into(), ) { Ok(p) => p, - Err(err) => return Err(PipelineErrorVariant::PlannerError(err).into()), + Err(err) => { + return Err( + req.new_pipeline_error(PipelineErrorVariant::PlannerError(err)) + ) + } } }; @@ -150,7 +160,7 @@ impl GatewayPipelineLayer for QueryPlanService { query_plan: query_plan_arc, }); - Ok((req, GatewayPipelineStepDecision::Continue)) + Ok(GatewayPipelineStepDecision::Continue) } } diff --git a/bin/gateway/src/pipeline/validation_service.rs b/bin/gateway/src/pipeline/validation_service.rs index 561b1f93c..cc2dd67f3 100644 --- a/bin/gateway/src/pipeline/validation_service.rs +++ b/bin/gateway/src/pipeline/validation_service.rs @@ -1,17 +1,14 @@ use std::sync::Arc; -use crate::pipeline::error::{PipelineError, PipelineErrorVariant}; +use crate::pipeline::error::{PipelineError, PipelineErrorFromAcceptHeader, PipelineErrorVariant}; use crate::pipeline::gateway_layer::{ GatewayPipelineLayer, GatewayPipelineStepDecision, ProcessorLayer, }; -use crate::pipeline::http_request_params::HttpRequestParams; -use crate::pipeline::normalize_service::GraphQLNormalizationPayload; use crate::pipeline::parser_service::GraphQLParserPayload; use crate::shared_state::GatewaySharedState; use axum::body::Body; use graphql_tools::validation::validate::validate; -use http::{Method, Request}; -use query_planner::state::supergraph_state::OperationKind; +use http::Request; use tracing::{error, trace}; #[derive(Clone, Debug, Default)] @@ -28,53 +25,37 @@ impl GatewayPipelineLayer for GraphQLValidationService { #[tracing::instrument(level = "trace", name = "GraphQLValidationService", skip_all)] async fn process( &self, - req: Request, - ) -> Result<(Request, GatewayPipelineStepDecision), PipelineError> { - let normalized_operation = req - .extensions() - .get::() - .ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("GraphQLNormalizationPayload is missing") - })?; - - let http_params = req.extensions().get::().ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("HttpRequestParams is missing") - })?; - + req: &mut Request, + ) -> Result { let parser_payload = req .extensions() .get::() .ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("GraphQLParserPayload is missing") + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "GraphQLParserPayload is missing", + )) })?; let app_state = req .extensions() .get::>() .ok_or_else(|| { - PipelineErrorVariant::InternalServiceError("GatewaySharedState is missing") + req.new_pipeline_error(PipelineErrorVariant::InternalServiceError( + "GatewaySharedState is missing", + )) })?; - if http_params.http_method == Method::GET { - if let Some(OperationKind::Mutation) = normalized_operation - .normalized_document - .operation - .operation_kind - { - error!("Mutation is not allowed over GET, stopping"); - - return Err(PipelineErrorVariant::MutationNotAllowedOverHttpGet.into()); - } - } - let consumer_schema_ast = &app_state.planner.consumer_schema.document; - let validation_cache_key = normalized_operation.normalized_document.operation.hash(); - let validation_result = match app_state.validate_cache.get(&validation_cache_key).await { + let validation_result = match app_state + .validate_cache + .get(&parser_payload.cache_key) + .await + { Some(cached_validation) => { trace!( "validation result of hash {} has been loaded from cache", - validation_cache_key + parser_payload.cache_key ); cached_validation @@ -82,7 +63,7 @@ impl GatewayPipelineLayer for GraphQLValidationService { None => { trace!( "validation result of hash {} does not exists in cache", - validation_cache_key + parser_payload.cache_key ); let res = validate( @@ -94,7 +75,7 @@ impl GatewayPipelineLayer for GraphQLValidationService { app_state .validate_cache - .insert(validation_cache_key, arc_res.clone()) + .insert(parser_payload.cache_key, arc_res.clone()) .await; arc_res } @@ -107,9 +88,11 @@ impl GatewayPipelineLayer for GraphQLValidationService { ); trace!("Validation errors: {:?}", validation_result); - return Err(PipelineErrorVariant::ValidationErrors(validation_result).into()); + return Err( + req.new_pipeline_error(PipelineErrorVariant::ValidationErrors(validation_result)) + ); } - Ok((req, GatewayPipelineStepDecision::Continue)) + Ok(GatewayPipelineStepDecision::Continue) } } diff --git a/bin/gateway/src/shared_state.rs b/bin/gateway/src/shared_state.rs index b7ff558f4..9227d3db2 100644 --- a/bin/gateway/src/shared_state.rs +++ b/bin/gateway/src/shared_state.rs @@ -10,6 +10,8 @@ use query_planner::{ state::supergraph_state::SupergraphState, }; +use crate::pipeline::normalize_service::GraphQLNormalizationPayload; + pub struct GatewaySharedState { pub schema_metadata: SchemaMetadata, pub planner: Planner, @@ -18,6 +20,8 @@ pub struct GatewaySharedState { pub subgraph_endpoint_map: HashMap, pub plan_cache: Cache>, pub validate_cache: Cache>>, + pub parse_cache: Cache>>, + pub normalize_cache: Cache>, } impl GatewaySharedState { @@ -39,6 +43,8 @@ impl GatewaySharedState { subgraph_endpoint_map, plan_cache: moka::future::Cache::new(1000), validate_cache: moka::future::Cache::new(1000), + parse_cache: moka::future::Cache::new(1000), + normalize_cache: moka::future::Cache::new(1000), }) } } diff --git a/lib/query-plan-executor/Cargo.toml b/lib/query-plan-executor/Cargo.toml index 1412c4ac7..87c92f655 100644 --- a/lib/query-plan-executor/Cargo.toml +++ b/lib/query-plan-executor/Cargo.toml @@ -8,15 +8,20 @@ edition = "2021" [dependencies] async-trait = "0.1" serde = "1.0.219" -serde_json = { version = "1.0.140", features = ["preserve_order"] } +serde_json = "1.0.140" futures = "0.3" -reqwest = { version = "0.12", features = ["json"] } query-planner = { path = "../query-planner" } graphql-parser = "0.4.1" graphql-tools = "0.4.0" tracing = "0.1.41" # TODO: Move this to dev-dependencies but currently benchmarks need it async-graphql = { version = "7.0.17" } +hyper = { version= "1.6.0", features = ["client"] } +hyper-util = { version= "0.1.15", features = ["client", "client-legacy", "http1", "http2", "tokio"] } +http-body-util = "0.1.3" +http = "1.3.1" +sonic-rs = "0.3" +indexmap = "2.10.0" [dev-dependencies] criterion = { version = "0.6", features = ["html_reports", "async_tokio"] } diff --git a/lib/query-plan-executor/benches/executor_benches.rs b/lib/query-plan-executor/benches/executor_benches.rs index ec3294991..6de38a6f5 100644 --- a/lib/query-plan-executor/benches/executor_benches.rs +++ b/lib/query-plan-executor/benches/executor_benches.rs @@ -11,9 +11,9 @@ use query_planner::graph::PlannerOverrideContext; use query_planner::planner::plan_nodes::FlattenNodePathSegment; use std::hint::black_box; -use query_plan_executor::execute_query_plan; -use query_plan_executor::schema_metadata::SchemaWithMetadata; +use query_plan_executor::schema_metadata::{SchemaMetadata, SchemaWithMetadata}; use query_plan_executor::ExecutableQueryPlan; +use query_plan_executor::{execute_query_plan, ExposeQueryPlanMode}; use query_planner::ast::normalization::normalize_operation; use query_planner::utils::parsing::parse_operation; use query_planner::utils::parsing::parse_schema; @@ -43,20 +43,28 @@ fn query_plan_executor_pipeline_via_http(c: &mut Criterion) { let subgraph_endpoint_map = planner.supergraph.subgraph_endpoint_map; let schema_metadata = planner.consumer_schema.schema_metadata(); let subgraph_executor_map = SubgraphExecutorMap::from_http_endpoint_map(subgraph_endpoint_map); + let (root_type_name, projection_selections) = + query_plan_executor::projection::FieldProjectionPlan::from_operation( + normalized_operation, + &schema_metadata, + ); c.bench_function("query_plan_executor_pipeline_via_http", |b| { b.to_async(&rt).iter(|| async { let query_plan = black_box(&query_plan); let schema_metadata = black_box(&schema_metadata); - let operation = black_box(&normalized_operation); let subgraph_executor_map = black_box(&subgraph_executor_map); + let projection_selections = black_box(&projection_selections); + let root_type_name = black_box(root_type_name); let has_introspection = false; let result = execute_query_plan( query_plan, subgraph_executor_map, &None, schema_metadata, - operation, + root_type_name, + projection_selections, has_introspection, + ExposeQueryPlanMode::No, ) .await; black_box(result) @@ -134,20 +142,29 @@ fn query_plan_executor_pipeline_locally(c: &mut Criterion) { subgraph_executor_map.insert_boxed_arc("products".to_string(), products.to_boxed_arc()); subgraph_executor_map.insert_boxed_arc("reviews".to_string(), reviews.to_boxed_arc()); + let (root_type_name, projection_selections) = + query_plan_executor::projection::FieldProjectionPlan::from_operation( + normalized_operation, + &schema_metadata, + ); + c.bench_function("query_plan_executor_pipeline_locally", |b| { b.to_async(&rt).iter(|| async { let query_plan = black_box(&query_plan); let schema_metadata = black_box(&schema_metadata); - let operation = black_box(&normalized_operation); let subgraph_executor_map = black_box(&subgraph_executor_map); + let projection_selections = black_box(&projection_selections); + let root_type_name = black_box(root_type_name); let has_introspection = false; let result = execute_query_plan( query_plan, subgraph_executor_map, &None, schema_metadata, - operation, + root_type_name, + projection_selections, has_introspection, + ExposeQueryPlanMode::No, ) .await; black_box(result) @@ -220,22 +237,32 @@ fn project_data_by_operation(c: &mut Criterion) { .expect("Failed to normalize operation"); let normalized_operation = normalized_document.executable_operation(); let schema_metadata = planner.consumer_schema.schema_metadata(); - let operation = black_box(&normalized_operation); + let (root_type_name, projection_selections) = + query_plan_executor::projection::FieldProjectionPlan::from_operation( + normalized_operation, + &schema_metadata, + ); c.bench_function("project_data_by_operation", |b| { b.iter(|| { let mut data = non_projected_result::get_result(); let data = black_box(&mut data); let mut errors = vec![]; let errors = black_box(&mut errors); - let operation = black_box(&operation); - let schema_metadata = black_box(&schema_metadata); - query_plan_executor::project_data_by_operation( + let extensions = HashMap::new(); + let extensions = black_box(&extensions); + let projection_selections = black_box(&projection_selections); + let root_type_name = black_box(root_type_name); + let mut writer = black_box(Vec::with_capacity(4096)); + let result = query_plan_executor::projection::project_by_operation( + &mut writer, data, errors, - operation, - schema_metadata, + extensions, + root_type_name, + projection_selections, &None, ); + result.unwrap_or_default(); black_box(()); }); }); @@ -256,13 +283,19 @@ fn traverse_and_collect(c: &mut Criterion) { FlattenNodePathSegment::Field("product".into()), ]; let mut result: Value = non_projected_result::get_result(); + let schema_metadata = SchemaMetadata::default(); c.bench_function("traverse_and_collect", |b| { b.iter(|| { let result = black_box(&mut result); + let schema_metadata = black_box(&schema_metadata); let data = result.get_mut("data").unwrap(); let path = black_box(&path); - let result = query_plan_executor::traverse_and_collect(data, path); - black_box(result); + let mut results = vec![]; + query_plan_executor::traverse_and_callback(data, path, schema_metadata, &mut |data| { + results.push(data); + }); + black_box(()); + black_box(results); }); }); } @@ -354,13 +387,16 @@ fn project_requires(c: &mut Criterion) { ]; let mut result: Value = non_projected_result::get_result(); let data = result.get_mut("data").unwrap(); - let representations = query_plan_executor::traverse_and_collect(data, &path); let supergraph_sdl = std::fs::read_to_string("../../bench/supergraph.graphql") .expect("Unable to read input file"); let parsed_schema = parse_schema(&supergraph_sdl); let planner = query_planner::planner::Planner::new_from_supergraph(&parsed_schema) .expect("Failed to create planner from supergraph"); let schema_metadata = &planner.consumer_schema.schema_metadata(); + let mut representations = vec![]; + query_plan_executor::traverse_and_callback(data, &path, schema_metadata, &mut |data| { + representations.push(data); + }); let subgraph_executor_map = SubgraphExecutorMap::from_http_endpoint_map(planner.supergraph.subgraph_endpoint_map); let execution_context = query_plan_executor::QueryPlanExecutionContext { @@ -424,11 +460,24 @@ fn project_requires(c: &mut Criterion) { c.bench_function("project_requires", |b| { b.iter(|| { let execution_context = black_box(&execution_context); + let mut buffer = Vec::with_capacity(1024); + let mut first = true; for representation in black_box(&representations) { - let requires = - execution_context.project_requires(&requires_selections, representation); + let requires = execution_context + .project_requires( + &requires_selections, + representation, + &mut buffer, + first, + None, + ) + .unwrap_or(false); + if requires { + first = false; + } black_box(requires); } + black_box(buffer) }); }); } @@ -470,16 +519,17 @@ fn deep_merge_with_simple(c: &mut Criterion) { } fn all_benchmarks(c: &mut Criterion) { - deep_merge_with_simple(c); - deep_merge_with_complex(c); - project_requires(c); - traverse_and_collect(c); - project_data_by_operation(c); query_plan_executor_without_projection_locally(c); query_plan_executor_pipeline_locally(c); query_plan_execution_without_projection_via_http(c); query_plan_executor_pipeline_via_http(c); + + deep_merge_with_simple(c); + deep_merge_with_complex(c); + project_requires(c); + traverse_and_collect(c); + project_data_by_operation(c); } criterion_group!(benches, all_benchmarks); diff --git a/lib/query-plan-executor/src/executors/async_graphql.rs b/lib/query-plan-executor/src/executors/async_graphql.rs index 07f3aab18..68af81fd9 100644 --- a/lib/query-plan-executor/src/executors/async_graphql.rs +++ b/lib/query-plan-executor/src/executors/async_graphql.rs @@ -4,8 +4,8 @@ use async_trait::async_trait; use serde_json::json; use crate::{ - executors::common::SubgraphExecutor, ExecutionRequest, ExecutionResult, GraphQLError, - GraphQLErrorLocation, + executors::common::SubgraphExecutor, ExecutionResult, GraphQLError, GraphQLErrorLocation, + SubgraphExecutionRequest, }; #[async_trait] @@ -13,18 +13,30 @@ impl SubgraphExecutor for Executor where Executor: async_graphql::Executor, { - async fn execute(&self, execution_request: ExecutionRequest) -> ExecutionResult { + async fn execute<'a>( + &self, + execution_request: SubgraphExecutionRequest<'a>, + ) -> ExecutionResult { let response: async_graphql::Response = self.execute(execution_request.into()).await; response.into() } } -impl From for async_graphql::Request { - fn from(exec_request: ExecutionRequest) -> Self { +impl<'a> From> for async_graphql::Request { + fn from(exec_request: SubgraphExecutionRequest) -> Self { let mut req = async_graphql::Request::new(exec_request.query); if let Some(variables) = exec_request.variables { req = req.variables(async_graphql::Variables::from_json(json!(variables))); } + if let Some(representations) = exec_request.representations { + req.variables.insert( + async_graphql::Name::new("representations"), + async_graphql::Value::from_json( + serde_json::from_slice(&representations).unwrap_or_default(), + ) + .unwrap(), + ); + } if let Some(operation_name) = exec_request.operation_name { req = req.operation_name(operation_name); } diff --git a/lib/query-plan-executor/src/executors/common.rs b/lib/query-plan-executor/src/executors/common.rs index dbb10e0b7..b6d8a2f55 100644 --- a/lib/query-plan-executor/src/executors/common.rs +++ b/lib/query-plan-executor/src/executors/common.rs @@ -2,11 +2,12 @@ use std::sync::Arc; use async_trait::async_trait; -use crate::{ExecutionRequest, ExecutionResult}; +use crate::{ExecutionResult, SubgraphExecutionRequest}; #[async_trait] pub trait SubgraphExecutor { - async fn execute(&self, execution_request: ExecutionRequest) -> ExecutionResult; + async fn execute<'a>(&self, execution_request: SubgraphExecutionRequest<'a>) + -> ExecutionResult; fn to_boxed_arc<'a>(self) -> Arc> where Self: Sized + Send + Sync + 'a, diff --git a/lib/query-plan-executor/src/executors/http.rs b/lib/query-plan-executor/src/executors/http.rs index 2d5923e12..9a6906eeb 100644 --- a/lib/query-plan-executor/src/executors/http.rs +++ b/lib/query-plan-executor/src/executors/http.rs @@ -1,49 +1,151 @@ +use std::io::Write; +use std::sync::Arc; + use async_trait::async_trait; +use http::HeaderMap; +use http::HeaderValue; +use http_body_util::BodyExt; +use http_body_util::Full; +use hyper::{body::Bytes, Version}; +use hyper_util::client::legacy::{connect::HttpConnector, Client}; use tracing::{error, instrument, trace}; -use crate::{executors::common::SubgraphExecutor, ExecutionRequest, ExecutionResult}; +use crate::{ + executors::common::SubgraphExecutor, json_writer::write_and_escape_string, ExecutionResult, + SubgraphExecutionRequest, +}; #[derive(Debug)] pub struct HTTPSubgraphExecutor { - pub endpoint: String, - pub http_client: reqwest::Client, + pub endpoint: http::Uri, + pub http_client: Arc>>, + pub header_map: HeaderMap, } +const FIRST_VARIABLE_STR: &[u8; 14] = b",\"variables\":{"; + impl HTTPSubgraphExecutor { - pub fn new(endpoint: String, http_client: reqwest::Client) -> Self { + pub fn new(endpoint: &str, http_client: Arc>>) -> Self { + let endpoint = endpoint + .parse::() + .expect("Failed to parse endpoint as URI"); + let mut header_map = HeaderMap::new(); + header_map.insert( + "Content-Type", + HeaderValue::from_static("application/json; charset=utf-8"), + ); HTTPSubgraphExecutor { endpoint, http_client, + header_map, + } + } + + fn write_body( + execution_request: &SubgraphExecutionRequest, + writer: &mut impl Write, + ) -> std::io::Result<()> { + writer.write_all(b"{\"query\":")?; + write_and_escape_string(writer, execution_request.query)?; + + let mut first_variable = true; + if let Some(variables) = &execution_request.variables { + for (variable_name, variable_value) in variables { + if first_variable { + writer.write_all(FIRST_VARIABLE_STR)?; + first_variable = false; + } else { + writer.write_all(b",")?; + } + writer.write_all(b"\"")?; + writer.write_all(variable_name.as_bytes())?; + writer.write_all(b"\":")?; + serde_json::to_writer(&mut *writer, variable_value)?; + } + } + if let Some(representations) = &execution_request.representations { + if first_variable { + writer.write_all(FIRST_VARIABLE_STR)?; + first_variable = false; + } else { + writer.write_all(b",")?; + } + writer.write_all(b"\"representations\":")?; + writer.write_all(representations)?; + } + // "first_variable" should be still true if there are no variables + if !first_variable { + writer.write_all(b"}")?; } + writer.write_all(b"}")?; + Ok(()) } - async fn _execute( + async fn _execute<'a>( &self, - execution_request: ExecutionRequest, - ) -> Result { + execution_request: SubgraphExecutionRequest<'a>, + ) -> Result { trace!("Executing HTTP request to subgraph at {}", self.endpoint); - self.http_client - .post(&self.endpoint) - .json(&execution_request) - .send() - .await? - .json::() + + // We may want to remove it, but let's see. + let mut body = Vec::with_capacity(4096); + Self::write_body(&execution_request, &mut body) + .map_err(|e| format!("Failed to write request body: {}", e))?; + + let mut req = hyper::Request::builder() + .method(http::Method::POST) + .uri(&self.endpoint) + .version(Version::HTTP_11) + .body(body.into()) + .map_err(|e| { + format!( + "Failed to build request to subgraph {}: {}", + self.endpoint, e + ) + })?; + + *req.headers_mut() = self.header_map.clone(); + + let res = self.http_client.request(req).await.map_err(|e| { + format!( + "Failed to send request to subgraph {}: {}", + self.endpoint, e + ) + })?; + + let bytes = res + .into_body() + .collect() .await + .map_err(|e| { + format!( + "Failed to parse response from subgraph {}: {}", + self.endpoint, e + ) + })? + .to_bytes(); + + unsafe { + sonic_rs::from_slice_unchecked(&bytes).map_err(|e| { + format!( + "Failed to parse response from subgraph {}: {}", + self.endpoint, e + ) + }) + } } } #[async_trait] impl SubgraphExecutor for HTTPSubgraphExecutor { #[instrument(level = "trace", skip(self), name = "http_subgraph_execute", fields(endpoint = %self.endpoint))] - async fn execute(&self, execution_request: ExecutionRequest) -> ExecutionResult { + async fn execute<'a>( + &self, + execution_request: SubgraphExecutionRequest<'a>, + ) -> ExecutionResult { self._execute(execution_request).await.unwrap_or_else(|e| { - error!("Failed to execute request to subgraph: {}", e); - trace!("network error: {:?}", e); - - ExecutionResult::from_error_message(format!( - "Error executing subgraph {}: {}", - self.endpoint, e - )) + error!(e); + ExecutionResult::from_error_message(e) }) } } diff --git a/lib/query-plan-executor/src/executors/map.rs b/lib/query-plan-executor/src/executors/map.rs index a27c8b14a..52c8eda8d 100644 --- a/lib/query-plan-executor/src/executors/map.rs +++ b/lib/query-plan-executor/src/executors/map.rs @@ -1,8 +1,15 @@ -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc, time::Duration}; +use hyper_util::{ + client::legacy::Client, + rt::{TokioExecutor, TokioTimer}, +}; use tracing::{instrument, warn}; -use crate::executors::common::{SubgraphExecutor, SubgraphExecutorBoxedArc}; +use crate::executors::{ + common::{SubgraphExecutor, SubgraphExecutorBoxedArc}, + http::HTTPSubgraphExecutor, +}; pub struct SubgraphExecutorMap { inner: HashMap, @@ -22,10 +29,10 @@ impl SubgraphExecutorMap { } #[instrument(level = "trace", name = "subgraph_execute", skip_all, fields(subgraph_name = %subgraph_name, execution_request = ?execution_request))] - pub async fn execute( + pub async fn execute<'a>( &self, subgraph_name: &str, - execution_request: crate::ExecutionRequest, + execution_request: crate::SubgraphExecutionRequest<'a>, ) -> crate::ExecutionResult { match self.inner.get(subgraph_name) { Some(executor) => executor.execute(execution_request).await, @@ -47,15 +54,18 @@ impl SubgraphExecutorMap { } pub fn from_http_endpoint_map(subgraph_endpoint_map: HashMap) -> Self { - let http_client = reqwest::Client::new(); + let mut builder = Client::builder(TokioExecutor::new()); + let builder_mut = builder + .pool_timer(TokioTimer::new()) + .pool_idle_timeout(Duration::from_secs(60 * 60)) + .pool_max_idle_per_host(usize::MAX); + let http_client = builder_mut.build_http(); + let http_client_arc = Arc::new(http_client); let executor_map = subgraph_endpoint_map .into_iter() .map(|(subgraph_name, endpoint)| { - let executor = crate::executors::http::HTTPSubgraphExecutor::new( - endpoint, - http_client.clone(), - ) - .to_boxed_arc(); + let executor = + HTTPSubgraphExecutor::new(&endpoint, http_client_arc.clone()).to_boxed_arc(); (subgraph_name, executor) }) .collect::>(); diff --git a/lib/query-plan-executor/src/json_writer.rs b/lib/query-plan-executor/src/json_writer.rs new file mode 100644 index 000000000..fe1d42a0d --- /dev/null +++ b/lib/query-plan-executor/src/json_writer.rs @@ -0,0 +1,72 @@ +//! I took it from https://github.com/zotta/json-writer-rs/blob/f45e2f25cede0e06be76a94f6e45608780a835d4/src/lib.rs#L853 + +const fn get_replacements() -> [u8; 256] { + // NOTE: Only characters smaller than 128 are allowed here. + // Trying to escape values above 128 would generate invalid utf-8 output + // ----- + // see https://www.json.org/json-en.html + let mut result = [0u8; 256]; + // Escape everything from 0 to 0x1F + let mut i = 0; + while i < 0x20 { + result[i] = b'u'; + i += 1; + } + result[b'\"' as usize] = b'"'; + result[b'\\' as usize] = b'\\'; + result[b'/' as usize] = b'/'; + result[8] = b'b'; + result[0xc] = b'f'; + result[b'\n' as usize] = b'n'; + result[b'\r' as usize] = b'r'; + result[b'\t' as usize] = b't'; + result[0] = b'u'; + + result +} + +static REPLACEMENTS: [u8; 256] = get_replacements(); +static HEX: [u8; 16] = *b"0123456789ABCDEF"; + +/// Escapes and append part of string +#[inline(always)] +pub fn write_and_escape_string( + writer: &mut impl std::io::Write, + input: &str, +) -> std::io::Result<()> { + writer.write_all(b"\"")?; + + let bytes = input.as_bytes(); + let mut last_write = 0; + + for (i, &byte) in bytes.iter().enumerate() { + let replacement = REPLACEMENTS[byte as usize]; + if replacement != 0 { + if last_write < i { + writer.write_all(&bytes[last_write..i])?; + } + + if replacement == b'u' { + let hex_bytes: [u8; 6] = [ + b'\\', + b'u', + b'0', + b'0', + HEX[((byte / 16) & 0xF) as usize], + HEX[(byte & 0xF) as usize], + ]; + writer.write_all(&hex_bytes)?; + } else { + let escaped_bytes: [u8; 2] = [b'\\', replacement]; + writer.write_all(&escaped_bytes)?; + } + last_write = i + 1; + } + } + + if last_write < bytes.len() { + writer.write_all(&bytes[last_write..])?; + } + + writer.write_all(b"\"") +} diff --git a/lib/query-plan-executor/src/lib.rs b/lib/query-plan-executor/src/lib.rs index b72d54a4e..708571494 100644 --- a/lib/query-plan-executor/src/lib.rs +++ b/lib/query-plan-executor/src/lib.rs @@ -1,28 +1,30 @@ use async_trait::async_trait; -use futures::future::BoxFuture; +use futures::{future::BoxFuture, stream::FuturesUnordered, FutureExt, StreamExt}; use query_planner::{ - ast::{ - operation::OperationDefinition, selection_item::SelectionItem, selection_set::SelectionSet, - }, + ast::selection_item::SelectionItem, planner::plan_nodes::{ - ConditionNode, FetchNode, FetchNodePathSegment, FetchRewrite, FlattenNode, FlattenNodePath, + ConditionNode, FetchNode, FetchNodePathSegment, FetchRewrite, FlattenNode, FlattenNodePathSegment, KeyRenamer, ParallelNode, PlanNode, QueryPlan, SequenceNode, ValueSetter, }, - state::supergraph_state::OperationKind, }; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; -use std::collections::HashMap; +use std::collections::BTreeSet; +use std::{collections::HashMap, vec}; use tracing::{instrument, trace, warn}; // For reading file in main use crate::{ - deep_merge::deep_merge_objects, executors::map::SubgraphExecutorMap, - schema_metadata::SchemaMetadata, + executors::map::SubgraphExecutorMap, + json_writer::write_and_escape_string, + projection::FieldProjectionPlan, + schema_metadata::{PossibleTypes, SchemaMetadata}, }; pub mod deep_merge; pub mod executors; pub mod introspection; +mod json_writer; +pub mod projection; pub mod schema_metadata; pub mod validation; mod value_from_ast; @@ -97,42 +99,9 @@ fn process_errors_and_extensions( execution_context.extensions.extend(extensions); } } - -#[instrument( - level = "debug", - skip_all - name = "process_representations_result", - fields( - representations_count = %result.entities.as_ref().map_or(0, |e| e.len()) - ), -)] -fn process_representations_result( - result: ExecuteForRepresentationsResult, - representations: &mut Vec<&mut Value>, - execution_context: &mut QueryPlanExecutionContext<'_>, -) { - if let Some(entities) = result.entities { - trace!( - "Processing representations result: {} entities", - entities.len() - ); - for (entity, index) in entities.into_iter().zip(result.indexes.into_iter()) { - if let Some(representation) = representations.get_mut(index) { - trace!( - "Merging entity into representation at index {}: {:?}", - index, - entity - ); - deep_merge::deep_merge(representation, entity); - } - } - } - process_errors_and_extensions(execution_context, result.errors, result.extensions); -} - struct ExecuteForRepresentationsResult { entities: Option>, - indexes: Vec, + indexes: BTreeSet, errors: Option>, extensions: Option>, } @@ -143,26 +112,17 @@ trait ExecutableFetchNode { &self, execution_context: &QueryPlanExecutionContext<'_>, ) -> ExecutionResult; - fn project_representations( - &self, - execution_context: &QueryPlanExecutionContext<'_>, - representations: &[&mut Value], - ) -> ProjectRepresentationsResult; async fn execute_for_projected_representations( &self, execution_context: &QueryPlanExecutionContext<'_>, - filtered_representations: Vec, - filtered_repr_indexes: Vec, + filtered_representations: Vec, + indexes: BTreeSet, ) -> ExecuteForRepresentationsResult; - fn apply_output_rewrites( - &self, - possible_types: &HashMap>, - data: &mut Value, - ); - fn prepare_variables_for_fetch_node( - &self, - variable_values: &Option>, - ) -> Option>; + fn apply_output_rewrites(&self, possible_types: &PossibleTypes, data: &mut Value); + fn prepare_variables_for_fetch_node<'a>( + &'a self, + variable_values: &'a Option>, + ) -> Option>; } #[async_trait] @@ -179,11 +139,6 @@ impl ExecutablePlanNode for FetchNode { } } -struct ProjectRepresentationsResult { - representations: Vec, - indexes: Vec, -} - #[async_trait] impl ExecutableFetchNode for FetchNode { #[instrument( @@ -202,11 +157,12 @@ impl ExecutableFetchNode for FetchNode { ) -> ExecutionResult { let variables = self.prepare_variables_for_fetch_node(execution_context.variable_values); - let execution_request = ExecutionRequest { - query: self.operation.document_str.clone(), - operation_name: self.operation_name.clone(), + let execution_request = SubgraphExecutionRequest { + query: &self.operation.document_str, + operation_name: self.operation_name.as_deref(), variables, extensions: None, + representations: None, }; let mut fetch_result = execution_context .subgraph_executor_map @@ -222,41 +178,6 @@ impl ExecutableFetchNode for FetchNode { fetch_result } - fn project_representations( - &self, - execution_context: &QueryPlanExecutionContext<'_>, - representations: &[&mut Value], - ) -> ProjectRepresentationsResult { - let mut filtered_repr_indexes = Vec::new(); - // 1. Filter representations based on requires (if present) - let mut filtered_representations: Vec = Vec::new(); - let requires_nodes = self.requires.as_ref().unwrap(); - for (index, entity) in representations.iter().enumerate() { - let entity_projected = - execution_context.project_requires(&requires_nodes.items, entity); - if !entity_projected.is_null() { - filtered_representations.push(entity_projected); - filtered_repr_indexes.push(index); - } - } - - if let Some(input_rewrites) = &self.input_rewrites { - for representation in filtered_representations.iter_mut() { - for rewrite in input_rewrites { - rewrite.apply( - &execution_context.schema_metadata.possible_types, - representation, - ); - } - } - } - - ProjectRepresentationsResult { - representations: filtered_representations, - indexes: filtered_repr_indexes, - } - } - #[instrument( level = "debug", skip_all, @@ -268,23 +189,16 @@ impl ExecutableFetchNode for FetchNode { async fn execute_for_projected_representations( &self, execution_context: &QueryPlanExecutionContext<'_>, - filtered_representations: Vec, - filtered_repr_indexes: Vec, + filtered_representations: Vec, + indexes: BTreeSet, ) -> ExecuteForRepresentationsResult { // 2. Prepare variables for fetch - let mut variables = self - .prepare_variables_for_fetch_node(execution_context.variable_values) - .unwrap_or_default(); - variables.insert( - "representations".to_string(), - Value::Array(filtered_representations), - ); - - let execution_request = ExecutionRequest { - query: self.operation.document_str.clone(), - operation_name: self.operation_name.clone(), - variables: Some(variables), + let execution_request = SubgraphExecutionRequest { + query: &self.operation.document_str, + operation_name: self.operation_name.as_deref(), + variables: self.prepare_variables_for_fetch_node(execution_context.variable_values), extensions: None, + representations: Some(filtered_representations), }; // 3. Execute the fetch operation @@ -300,10 +214,13 @@ impl ExecutableFetchNode for FetchNode { &mut data, ); match data { - Value::Object(mut obj) => match obj.remove("_entities") { - Some(Value::Array(arr)) => Some(arr), - _ => None, // If _entities is not found or not an array - }, + Value::Object(mut obj) => { + let entities = obj.get_mut("_entities").map(|v| v.take()); + match entities { + Some(Value::Array(arr)) => Some(arr), + _ => None, + } + } _ => None, // If data is not an object } } else { @@ -311,17 +228,13 @@ impl ExecutableFetchNode for FetchNode { }; ExecuteForRepresentationsResult { entities, - indexes: filtered_repr_indexes, + indexes, errors: fetch_result.errors, extensions: fetch_result.extensions, } } - fn apply_output_rewrites( - &self, - possible_types: &HashMap>, - data: &mut Value, - ) { + fn apply_output_rewrites(&self, possible_types: &PossibleTypes, data: &mut Value) { if let Some(output_rewrites) = &self.output_rewrites { for rewrite in output_rewrites { rewrite.apply(possible_types, data); @@ -334,10 +247,10 @@ impl ExecutableFetchNode for FetchNode { skip(self, variable_values), name = "prepare_variables_for_fetch_node" )] - fn prepare_variables_for_fetch_node( - &self, - variable_values: &Option>, - ) -> Option> { + fn prepare_variables_for_fetch_node<'a>( + &'a self, + variable_values: &'a Option>, + ) -> Option> { match (&self.variable_usages, variable_values) { (Some(ref variable_usages), Some(variable_values)) => { if variable_usages.is_empty() || variable_values.is_empty() { @@ -349,7 +262,7 @@ impl ExecutableFetchNode for FetchNode { .filter_map(|variable_name| { variable_values .get(variable_name) - .map(|v| (variable_name.to_string(), v.clone())) + .map(|v| (variable_name.as_str(), v)) }) .collect(), ) @@ -361,17 +274,17 @@ impl ExecutableFetchNode for FetchNode { } trait ApplyFetchRewrite { - fn apply(&self, possible_types: &HashMap>, value: &mut Value); + fn apply(&self, possible_types: &PossibleTypes, value: &mut Value); fn apply_path( &self, - possible_types: &HashMap>, + possible_types: &PossibleTypes, value: &mut Value, path: &[FetchNodePathSegment], ); } impl ApplyFetchRewrite for FetchRewrite { - fn apply(&self, possible_types: &HashMap>, value: &mut Value) { + fn apply(&self, possible_types: &PossibleTypes, value: &mut Value) { match self { FetchRewrite::KeyRenamer(renamer) => renamer.apply(possible_types, value), FetchRewrite::ValueSetter(setter) => setter.apply(possible_types, value), @@ -379,7 +292,7 @@ impl ApplyFetchRewrite for FetchRewrite { } fn apply_path( &self, - possible_types: &HashMap>, + possible_types: &PossibleTypes, value: &mut Value, path: &[FetchNodePathSegment], ) { @@ -391,13 +304,13 @@ impl ApplyFetchRewrite for FetchRewrite { } impl ApplyFetchRewrite for KeyRenamer { - fn apply(&self, possible_types: &HashMap>, value: &mut Value) { + fn apply(&self, possible_types: &PossibleTypes, value: &mut Value) { self.apply_path(possible_types, value, &self.path) } // Applies key rename operation on a Value (mutably) fn apply_path( &self, - possible_types: &HashMap>, + possible_types: &PossibleTypes, value: &mut Value, path: &[FetchNodePathSegment], ) { @@ -417,11 +330,8 @@ impl ApplyFetchRewrite for KeyRenamer { Some(Value::String(type_name)) => type_name, _ => type_condition, // Default to type_condition if not found }; - if entity_satisfies_type_condition( - possible_types, - type_name, - type_condition, - ) { + if possible_types.entity_satisfies_type_condition(type_name, type_condition) + { self.apply_path(possible_types, value, remaining_path) } } @@ -444,14 +354,14 @@ impl ApplyFetchRewrite for KeyRenamer { } impl ApplyFetchRewrite for ValueSetter { - fn apply(&self, possible_types: &HashMap>, data: &mut Value) { + fn apply(&self, possible_types: &PossibleTypes, data: &mut Value) { self.apply_path(possible_types, data, &self.path) } // Applies value setting on a Value (returns a new Value) fn apply_path( &self, - possible_types: &HashMap>, + possible_types: &PossibleTypes, data: &mut Value, path: &[FetchNodePathSegment], ) { @@ -477,11 +387,8 @@ impl ApplyFetchRewrite for ValueSetter { Some(Value::String(type_name)) => type_name, _ => type_condition, // Default to type_condition if not found }; - if entity_satisfies_type_condition( - possible_types, - type_name, - type_condition, - ) { + if possible_types.entity_satisfies_type_condition(type_name, type_condition) + { self.apply_path(possible_types, data, remaining_path) } } @@ -545,86 +452,12 @@ fn process_root_result( ); } -type ExecutionStepJob<'a, T> = BoxFuture<'a, T>; - -#[derive(Default)] -struct ExecutionStep<'a> { - fetch_job: Option>, - flatten_job: Option>, - flatten_path: Option<&'a FlattenNodePath>, -} - -fn create_execution_step<'a>( - node: &'a PlanNode, - execution_context: &'a QueryPlanExecutionContext<'a>, - data: &mut Value, -) -> ExecutionStep<'a> { - match node { - PlanNode::Fetch(fetch_node) => ExecutionStep { - fetch_job: Some(fetch_node.execute_for_root(execution_context)), - ..Default::default() - }, - PlanNode::Flatten(flatten_node) => { - let PlanNode::Fetch(fetch_node) = flatten_node.node.as_ref() else { - warn!( - "FlattenNode can only execute FetchNode as child node, found: {:?}", - flatten_node.node - ); - return ExecutionStep::default(); - }; - - let collected_representations = - traverse_and_collect(data, flatten_node.path.as_slice()); - - if collected_representations.is_empty() { - // No representations collected, skip execution - return ExecutionStep::default(); - } - - let project_result = - fetch_node.project_representations(execution_context, &collected_representations); - - if project_result.representations.is_empty() { - // No representations collected, skip execution - return ExecutionStep::default(); - } - - let job = fetch_node.execute_for_projected_representations( - execution_context, - project_result.representations, - project_result.indexes, - ); - - ExecutionStep { - flatten_job: Some(job), - flatten_path: Some(&flatten_node.path), - ..Default::default() - } - } - PlanNode::Condition(node) => { - let condition_value = execution_context - .variable_values - .as_ref() - .and_then(|vars| vars.get(&node.condition)) - .is_some_and(|val| match val { - Value::Bool(b) => *b, - _ => false, - }); - - let clause = if condition_value { - node.if_clause.as_deref() - } else { - node.else_clause.as_deref() - }; - - if let Some(clause) = clause { - create_execution_step(clause, execution_context, data) - } else { - ExecutionStep::default() - } - } - _ => ExecutionStep::default(), - } +enum ParallelJob<'a> { + Root(ExecutionResult), + Flatten( + ExecuteForRepresentationsResult, + &'a [FlattenNodePathSegment], + ), } #[async_trait] @@ -637,96 +470,163 @@ impl ExecutablePlanNode for ParallelNode { execution_context: &mut QueryPlanExecutionContext<'_>, data: &mut Value, ) { - // Here we call fetch nodes in parallel and non-fetch nodes sequentially. - let mut fetch_jobs = vec![]; - let mut flatten_jobs = vec![]; - let mut flatten_paths = vec![]; - - // Collect Fetch node results and non-fetch nodes for sequential execution - let now = std::time::Instant::now(); - for node in &self.nodes { - let res = create_execution_step(node, execution_context, data); - - if let Some(fetch_job) = res.fetch_job { - fetch_jobs.push(fetch_job); - } - if let Some(flatten_job) = res.flatten_job { - flatten_jobs.push(flatten_job); - } - if let Some(flatten_path) = res.flatten_path { - flatten_paths.push(flatten_path.as_slice()); - } - } - trace!( - "Prepared {} fetch jobs and {} flatten jobs in {:?}", - fetch_jobs.len(), - flatten_jobs.len(), - now.elapsed() - ); - let mut all_errors = vec![]; let mut all_extensions = vec![]; - let now = std::time::Instant::now(); - - let flatten_results = futures::future::join_all(flatten_jobs).await; - let flatten_results_len = flatten_results.len(); - - trace!( - "Executed {} flatten jobs in {:?}", - flatten_results_len, - now.elapsed() - ); - - let now = std::time::Instant::now(); - for (result, path) in flatten_results.into_iter().zip(flatten_paths) { - // Process FlattenNode results - if let Some(entities) = result.entities { - let mut collected_representations = traverse_and_collect(data, path); - for (entity, index) in entities.into_iter().zip(result.indexes.into_iter()) { - if let Some(representation) = collected_representations.get_mut(index) { - // Merge the entity into the representation - deep_merge::deep_merge(representation, entity); + { + let mut jobs: FuturesUnordered> = FuturesUnordered::new(); + + // Collect Fetch node results and flatten nodes for parallel execution + let now = std::time::Instant::now(); + for node in &self.nodes { + let node = if let PlanNode::Condition(condition_node) = node { + // If the node is a ConditionNode, we need to check the condition + if let Some(inner_node) = + condition_node.inner_node_by_variables(execution_context.variable_values) + { + inner_node + } else { + continue; // Skip this node if the condition is not met + } + } else { + node + }; + match node { + PlanNode::Fetch(fetch_node) => { + let job = fetch_node.execute_for_root(execution_context); + jobs.push(Box::pin(job.map(ParallelJob::Root))); + } + PlanNode::Flatten(flatten_node) => { + let mut filtered_representations = Vec::with_capacity(1024); + let fetch_node = match flatten_node.node.as_ref() { + PlanNode::Fetch(fetch_node) => fetch_node, + _ => { + warn!( + "FlattenNode can only execute FetchNode as child node, found: {:?}", + flatten_node.node + ); + continue; // Skip if the child node is not a FetchNode + } + }; + let requires_nodes = fetch_node.requires.as_ref().unwrap(); + let mut index = 0; + let mut indexes = BTreeSet::new(); + let normalized_path = flatten_node.path.as_slice(); + filtered_representations.push(b'['); + traverse_and_callback( + data, + normalized_path, + execution_context.schema_metadata, + &mut |entity| { + let is_projected = + if let Some(input_rewrites) = &fetch_node.input_rewrites { + // We need to own the value and not modify the original entity + let mut entity_owned = entity.to_owned(); + for input_rewrite in input_rewrites { + input_rewrite.apply( + &execution_context.schema_metadata.possible_types, + &mut entity_owned, + ); + } + execution_context + .project_requires( + &requires_nodes.items, + &entity_owned, + &mut filtered_representations, + indexes.is_empty(), + None, + ) + .unwrap_or(false) + } else { + execution_context + .project_requires( + &requires_nodes.items, + entity, + &mut filtered_representations, + indexes.is_empty(), + None, + ) + .unwrap_or(false) + }; + if is_projected { + indexes.insert(index); + } + index += 1; + }, + ); + filtered_representations.push(b']'); + let job = fetch_node.execute_for_projected_representations( + execution_context, + filtered_representations, + indexes, + ); + jobs.push(Box::pin( + job.map(|r| ParallelJob::Flatten(r, normalized_path)), + )); } + _ => {} } } - // Extend errors and extensions from the result - if let Some(errors) = result.errors { - all_errors.extend(errors); - } - if let Some(extensions) = result.extensions { - all_extensions.push(extensions); + trace!("Prepared {} jobs in {:?}", jobs.len(), now.elapsed()); + + let now = std::time::Instant::now(); + while let Some(result) = jobs.next().await { + match result { + ParallelJob::Root(fetch_result) => { + // Process root FetchNode results + if let Some(new_data) = fetch_result.data { + if data.is_null() { + *data = new_data; // Initialize with new_data + } else { + deep_merge::deep_merge(data, new_data); + } + } + // Process errors and extensions + if let Some(errors) = fetch_result.errors { + all_errors.extend(errors); + } + if let Some(extensions) = fetch_result.extensions { + all_extensions.push(extensions); + } + } + ParallelJob::Flatten(result, path) => { + if let Some(mut entities) = result.entities { + let mut index_of_traverse = 0; + let mut index_of_entities = 0; + traverse_and_callback( + data, + path, + execution_context.schema_metadata, + &mut |target| { + if result.indexes.contains(&index_of_traverse) { + let entity = + entities.get_mut(index_of_entities).unwrap().take(); + // Merge the entity into the target + deep_merge::deep_merge(target, entity); + index_of_entities += 1; + } + index_of_traverse += 1; + }, + ); + } + // Process errors and extensions + if let Some(errors) = result.errors { + all_errors.extend(errors); + } + if let Some(extensions) = result.extensions { + all_extensions.push(extensions); + } + } + } } - } - - trace!( - "Processed {} flatten results in {:?}", - flatten_results_len, - now.elapsed() - ); - let now = std::time::Instant::now(); - let fetch_results = futures::future::join_all(fetch_jobs).await; - let fetch_results_len = fetch_results.len(); - trace!( - "Executed {} fetch jobs in {:?}", - fetch_results_len, - now.elapsed() - ); - - let now = std::time::Instant::now(); - // Process results from FetchNode executions - for fetch_result in fetch_results { - process_root_result(fetch_result, execution_context, data); + trace!( + "Processed {} parallel jobs in {:?}", + jobs.len(), + now.elapsed() + ); } - - trace!( - "Processed {} fetch results in {:?}", - fetch_results_len, - now.elapsed() - ); - - // Process errors and extensions from FlattenNode results + // 6. Process errors and extensions if !all_errors.is_empty() { execution_context.errors.extend(all_errors); } @@ -751,79 +651,102 @@ impl ExecutablePlanNode for FlattenNode { // Execute the child node. `execution_context` can be borrowed mutably // because `collected_representations` borrows `data_for_flatten`, not `execution_context.data`. let now = std::time::Instant::now(); - let mut representations = traverse_and_collect(data, self.path.as_slice()); + let mut representations = vec![]; + let mut filtered_representations = Vec::with_capacity(1024); + let fetch_node = match self.node.as_ref() { + PlanNode::Fetch(fetch_node) => fetch_node, + _ => { + warn!( + "FlattenNode can only execute FetchNode as child node, found: {:?}", + self.node + ); + return; // Skip if the child node is not a FetchNode + } + }; + let requires_nodes = fetch_node.requires.as_ref().unwrap(); + filtered_representations.push(b'['); + let mut first = true; + traverse_and_callback( + data, + self.path.as_slice(), + execution_context.schema_metadata, + &mut |entity| { + let is_projected = if let Some(input_rewrites) = &fetch_node.input_rewrites { + // We need to own the value and not modify the original entity + let mut entity_owned = entity.to_owned(); + for input_rewrite in input_rewrites { + input_rewrite.apply( + &execution_context.schema_metadata.possible_types, + &mut entity_owned, + ); + } + execution_context + .project_requires( + &requires_nodes.items, + &entity_owned, + &mut filtered_representations, + first, + None, + ) + .unwrap_or(false) + } else { + execution_context + .project_requires( + &requires_nodes.items, + entity, + &mut filtered_representations, + first, + None, + ) + .unwrap_or(false) + }; + if is_projected { + representations.push(entity); + first = false; + } + }, + ); + filtered_representations.push(b']'); trace!( "traversed and collected representations: {:?} in {:#?}", representations.len(), now.elapsed() ); - - if representations.is_empty() { - // If there are no representations, - // return early without executing the child node. + if first { + // No representations collected, so we skip the fetch execution return; } - - match self.node.as_ref() { - PlanNode::Fetch(fetch_node) => { - let now = std::time::Instant::now(); - let ProjectRepresentationsResult { - representations: filtered_representations, - indexes: filtered_repr_indexes, - } = fetch_node.project_representations(execution_context, &representations); - trace!( - "projected representations: {:?} in {:#?}", - representations.len(), - now.elapsed() - ); - - if filtered_representations.is_empty() { - // If there are no filtered representations, - // return early without executing the child node. - return; - } - - let now = std::time::Instant::now(); - let result = fetch_node - .execute_for_projected_representations( - execution_context, - filtered_representations, - filtered_repr_indexes, - ) - .await; - trace!( - "executed projected representations: {:?} in {:?}", - representations.len(), - now.elapsed() - ); - // Process the result - process_representations_result(result, &mut representations, execution_context); - trace!( - "processed projected representations: {:?} in {:?}", - representations.len(), - now.elapsed() - ); - } - _ => { - unimplemented!( - "FlattenNode can only execute FetchNode as child node, found: {:?}", - self.node - ); + let result = fetch_node + .execute_for_projected_representations( + execution_context, + filtered_representations, + BTreeSet::new(), + ) + .await; + if let Some(entities) = result.entities { + for (entity, target) in entities.into_iter().zip(representations.iter_mut()) { + // Merge the entity into the representation + deep_merge::deep_merge(target, entity); } } + + process_errors_and_extensions(execution_context, result.errors, result.extensions); } } -#[async_trait] -impl ExecutablePlanNode for ConditionNode { - #[instrument(level = "trace", skip_all, name = "ConditionNode::execute")] - async fn execute( +trait GetInnerNodeByVariables { + fn inner_node_by_variables( &self, - execution_context: &mut QueryPlanExecutionContext<'_>, - data: &mut Value, - ) { - let condition_value = execution_context - .variable_values + variables: &Option>, + ) -> Option<&PlanNode>; +} + +impl GetInnerNodeByVariables for ConditionNode { + fn inner_node_by_variables( + &self, + variables: &Option>, + ) -> Option<&PlanNode> { + let condition_value = variables .as_ref() .and_then(|vars| vars.get(&self.condition)) .is_some_and(|val| match val { @@ -832,10 +755,33 @@ impl ExecutablePlanNode for ConditionNode { }); if condition_value { if let Some(if_clause) = &self.if_clause { - return if_clause.execute(execution_context, data).await; + Some(if_clause) + } else { + None } } else if let Some(else_clause) = &self.else_clause { - return else_clause.execute(execution_context, data).await; + Some(else_clause) + } else { + None + } + } +} + +#[async_trait] +impl ExecutablePlanNode for ConditionNode { + #[instrument(level = "trace", skip_all, name = "ConditionNode::execute")] + async fn execute( + &self, + execution_context: &mut QueryPlanExecutionContext<'_>, + data: &mut Value, + ) { + let inner_node = self.inner_node_by_variables(execution_context.variable_values); + if let Some(node) = inner_node { + // Execute the inner node if it exists + node.execute(execution_context, data).await; + } else { + // If no inner node, we do nothing + trace!("ConditionNode condition not met, skipping execution."); } } } @@ -896,16 +842,13 @@ pub struct GraphQLErrorLocation { pub column: usize, } -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ExecutionRequest { - pub query: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub operation_name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub variables: Option>, - #[serde(skip_serializing_if = "Option::is_none")] +#[derive(Debug, Clone)] +pub struct SubgraphExecutionRequest<'a> { + pub query: &'a str, + pub operation_name: Option<&'a str>, + pub variables: Option>, pub extensions: Option>, + pub representations: Option>, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -934,457 +877,282 @@ pub struct QueryPlanExecutionContext<'a> { } impl QueryPlanExecutionContext<'_> { - #[instrument( - level = "trace", - skip_all, - fields( - requires_selections = ?requires_selections.iter().map(|s| s.to_string()).collect::>(), - entity = ?entity - ) - )] pub fn project_requires( &self, requires_selections: &Vec, entity: &Value, - ) -> Value { - if requires_selections.is_empty() { - return entity.clone(); // No selections to project, return the entity as is - } + writer: &mut impl std::io::Write, + first: bool, + response_key: Option<&str>, + ) -> std::io::Result { match entity { - Value::Null => Value::Null, - Value::Array(entity_array) => Value::Array( - entity_array - .iter() - .map(|item| self.project_requires(requires_selections, item)) - .collect(), - ), - Value::Object(entity_obj) => { - let mut result_map = Map::new(); - for requires_selection in requires_selections { - match &requires_selection { - SelectionItem::Field(requires_selection) => { - let field_name = &requires_selection.name; - let response_key = requires_selection.selection_identifier(); - let original = entity_obj - .get(field_name) - .unwrap_or(entity_obj.get(response_key).unwrap_or(&Value::Null)); - let projected_value: Value = self - .project_requires(&requires_selection.selections.items, original); - if !projected_value.is_null() { - result_map.insert(response_key.to_string(), projected_value); - } - } - SelectionItem::InlineFragment(requires_selection) => { - let type_name = match entity_obj.get(TYPENAME_FIELD) { - Some(Value::String(type_name)) => type_name, - _ => requires_selection.type_condition.as_str(), - }; - if entity_satisfies_type_condition( - &self.schema_metadata.possible_types, - type_name, - &requires_selection.type_condition, - ) { - let projected = self - .project_requires(&requires_selection.selections.items, entity); - // Merge the projected value into the result - if let Value::Object(projected_map) = projected { - deep_merge::deep_merge_objects(&mut result_map, projected_map); - } - // If the projected value is not an object, it will be ignored - } - } - SelectionItem::FragmentSpread(_name_ref) => { - // We only minify the queries to subgraphs, so we never have fragment spreads here - unreachable!( - "Fragment spreads should not exist in FetchNode::requires." - ); - } - } + Value::Null => { + return Ok(false); + } + Value::Bool(b) => { + if !first { + writer.write_all(b",")?; } - if (result_map.is_empty()) - || (result_map.len() == 1 && result_map.contains_key(TYPENAME_FIELD)) - { - Value::Null + if let Some(response_key) = response_key { + writer.write_all(b"\"")?; + writer.write_all(response_key.as_bytes())?; + writer.write_all(b"\"")?; + writer.write_all(b":")?; + if *b { + writer.write_all(b"true")?; + } else { + writer.write_all(b"false")?; + } + } else if *b { + writer.write_all(b"true")?; } else { - Value::Object(result_map) + writer.write_all(b"false")?; } } - Value::Bool(bool) => Value::Bool(*bool), - Value::Number(num) => Value::Number(num.to_owned()), - Value::String(string) => Value::String(string.to_string()), - } - } -} - -#[instrument( - level = "trace", - skip_all, - name = "entity_satisfies_type_condition", - fields( - type_name = %type_name, - type_condition = %type_condition, - ) -)] -fn entity_satisfies_type_condition( - possible_types: &HashMap>, - type_name: &str, - type_condition: &str, -) -> bool { - if type_name == type_condition { - true - } else { - let possible_types_for_type_condition = possible_types.get(type_condition); - match possible_types_for_type_condition { - Some(possible_types_for_type_condition) => { - possible_types_for_type_condition.contains(&type_name.to_string()) - } - None => { - // If no possible types are found, return false - false - } - } - } -} - -// --- Helper Function for Flatten --- - -/// Recursively traverses the data according to the path segments, -/// handling '@' for array iteration, and collects the final values.current_data.to_vec() -#[instrument(level = "trace", skip_all, fields( - current_type = ?current_data, - remaining_path = ?remaining_path -))] -pub fn traverse_and_collect<'a>( - current_data: &'a mut Value, - remaining_path: &[FlattenNodePathSegment], -) -> Vec<&'a mut Value> { - // If the path is empty, we're done traversing. - let Some((segment, remaining_path)) = remaining_path.split_first() else { - return match current_data { - // If the final result is an array, return all its items. - Value::Array(arr) => arr.iter_mut().collect(), - // Otherwise, return the value itself in a vector. - _ => vec![current_data], - }; - }; + Value::Number(n) => { + if !first { + writer.write_all(b",")?; + } + if let Some(response_key) = response_key { + writer.write_all(b"\"")?; + writer.write_all(response_key.as_bytes())?; + writer.write_all(b"\":")?; + } - match segment { - FlattenNodePathSegment::Field(field) => { - // Attempt to access a field on an object - match current_data.get_mut(field) { - Some(next_value) => traverse_and_collect(next_value, remaining_path), - // Either the field doesn't exist, or it's is not an object - None => vec![], + std::io::Write::write_fmt(writer, format_args!("{}", n))?; } - } - - FlattenNodePathSegment::List => { - match current_data { - Value::Array(arr) => arr - .iter_mut() - .flat_map(|item| traverse_and_collect(item, remaining_path)) - .collect(), - // List is only valid for arrays - _ => vec![], + Value::String(s) => { + if !first { + writer.write_all(b",")?; + } + if let Some(response_key) = response_key { + writer.write_all(b"\"")?; + writer.write_all(response_key.as_bytes())?; + writer.write_all(b"\":")?; + } + write_and_escape_string(writer, s)?; } - } - - FlattenNodePathSegment::Cast(type_name) => { - match current_data { - Value::Object(_) => { - // For a single object, a missing `__typename` is a pass-through - if contains_typename(current_data, type_name, true) { - traverse_and_collect(current_data, remaining_path) - } else { - vec![] + Value::Array(entity_array) => { + if !first { + writer.write_all(b",")?; + } + if let Some(response_key) = response_key { + writer.write_all(b"\"")?; + writer.write_all(response_key.as_bytes())?; + writer.write_all(b"\":[")?; + } else { + writer.write_all(b"[")?; + } + let mut first = true; + for entity_item in entity_array { + let projected = self.project_requires( + requires_selections, + entity_item, + writer, + first, + None, + )?; + if projected { + // Only update `first` if we actually write something + first = false; } } - Value::Array(arr) => { - // Filter an array based on matching `__typename` - arr.iter_mut() - .filter(|item| contains_typename(item, type_name, false)) - .flat_map(|item| traverse_and_collect(item, remaining_path)) - .collect() + writer.write_all(b"]")?; + } + Value::Object(entity_obj) => { + if requires_selections.is_empty() { + // It is probably a scalar with an object value, so we write it directly + serde_json::to_writer(writer, entity_obj)?; + return Ok(true); + } + if entity_obj.is_empty() { + return Ok(false); + } + + let parent_first = first; + let mut first = true; + self.project_requires_map_mut( + requires_selections, + entity_obj, + writer, + &mut first, + response_key, + parent_first, + )?; + if first { + // If no fields were projected, "first" is still true, + // so we skip writing the closing brace + return Ok(false); + } else { + writer.write_all(b"}")?; } - // Cast is only valid for objects and arrays - _ => vec![], } - } + }; + Ok(true) } -} -/// Checks if a serde_json::Value has a `__typename` that matches the given `type_name`. -fn contains_typename(value: &Value, type_name: &str, default_for_missing: bool) -> bool { - value - .as_object() - .and_then(|obj| obj.get("__typename")) - .and_then(Value::as_str) - .map_or(default_for_missing, |s| s == type_name) -} - -// --- Helper Functions --- + fn project_requires_map_mut( + &self, + requires_selections: &Vec, + entity_obj: &Map, + writer: &mut impl std::io::Write, + first: &mut bool, + parent_response_key: Option<&str>, + parent_first: bool, + ) -> std::io::Result<()> { + for requires_selection in requires_selections { + match &requires_selection { + SelectionItem::Field(requires_selection) => { + let field_name = &requires_selection.name; + let response_key = requires_selection.selection_identifier(); + if response_key == TYPENAME_FIELD { + // Skip __typename field, it is handled separately + continue; + } -// --- Main Function (for testing) --- + let original = entity_obj + .get(field_name) + .unwrap_or(entity_obj.get(response_key).unwrap_or(&Value::Null)); -#[instrument( - level = "trace", - skip_all, - fields( - type_name = %type_name, - selection_set = ?selection_set.items.iter().map(|s| s.to_string()).collect::>(), - obj = ?obj - ) -)] -fn project_selection_set_with_map( - obj: &mut Map, - errors: &mut Vec, - selection_set: &SelectionSet, - type_name: &str, - schema_metadata: &SchemaMetadata, - variable_values: &Option>, -) -> Option> { - let type_name = match obj.get(TYPENAME_FIELD) { - Some(Value::String(type_name)) => type_name, - _ => type_name, - } - .to_string(); - let field_map = schema_metadata.type_fields.get(&type_name)?; - let mut new_obj = Map::new(); - for selection in &selection_set.items { - match selection { - SelectionItem::Field(field) => { - // Get the type fields for the current type - // Type is not found in the schema - if let Some(ref skip_variable) = field.skip_if { - let variable_value = variable_values - .as_ref() - .and_then(|vars| vars.get(skip_variable)); - if variable_value == Some(&Value::Bool(true)) { - continue; // Skip this field if the variable is true - } - } - if let Some(ref include_variable) = field.include_if { - let variable_value = variable_values - .as_ref() - .and_then(|vars| vars.get(include_variable)); - if variable_value != Some(&Value::Bool(true)) { - continue; // Skip this field if the variable is not true + if original.is_null() { + continue; } - } - let response_key = field.alias.as_ref().unwrap_or(&field.name).to_string(); - if field.name == TYPENAME_FIELD { - new_obj.insert(response_key, Value::String(type_name.to_string())); - continue; - } - let field_type = field_map.get(&field.name); - if field.name == "__schema" && type_name == "Query" { - obj.insert( - response_key.to_string(), - schema_metadata.introspection_schema_root_json.clone(), - ); - } - let field_val = obj.get_mut(&response_key); - match (field_type, field_val) { - (Some(field_type), Some(field_val)) => { - match field_val { - Value::Object(field_val_map) => { - let new_field_val_map = project_selection_set_with_map( - field_val_map, - errors, - &field.selections, - field_type, - schema_metadata, - variable_values, - ); - match new_field_val_map { - Some(new_field_val_map) => { - // If the field is an object, merge the projected values - new_obj - .insert(response_key, Value::Object(new_field_val_map)); - } - None => { - new_obj.insert(response_key, Value::Null); - } - } - } - field_val => { - project_selection_set( - field_val, - errors, - &field.selections, - field_type, - schema_metadata, - variable_values, - ); - new_obj.insert( - response_key, - field_val.clone(), // Clone the value to insert - ); - } + + if *first { + if !parent_first { + writer.write_all(b",")?; } - } - (Some(_field_type), None) => { - // If the field is not found in the object, set it to Null - new_obj.insert(response_key, Value::Null); - } - (None, _) => { - // It won't reach here already, as the selection should be validated before projection - warn!( - "Field {} not found in type {}. Skipping projection.", - field.name, type_name - ); - } - } - } - SelectionItem::InlineFragment(inline_fragment) => { - if entity_satisfies_type_condition( - &schema_metadata.possible_types, - &type_name, - &inline_fragment.type_condition, - ) { - if let Some(ref skip_variable) = inline_fragment.skip_if { - let variable_value = variable_values - .as_ref() - .and_then(|vars| vars.get(skip_variable)); - if variable_value == Some(&Value::Bool(true)) { - continue; // Skip this selection set if the variable is not true + if let Some(parent_response_key) = parent_response_key { + writer.write_all(b"\"")?; + writer.write_all(parent_response_key.as_bytes())?; + writer.write_all(b"\":")?; } - } - if let Some(ref include_variable) = inline_fragment.include_if { - let variable_value = variable_values - .as_ref() - .and_then(|vars| vars.get(include_variable)); - if variable_value != Some(&Value::Bool(true)) { - continue; // Skip this selection set if the variable is not true + writer.write_all(b"{")?; + // Write __typename only if the object has other fields + if let Some(Value::String(type_name)) = entity_obj.get(TYPENAME_FIELD) { + writer.write_all(b"\"__typename\":")?; + write_and_escape_string(writer, type_name)?; + writer.write_all(b",")?; } } - let sub_new_obj = project_selection_set_with_map( - obj, - errors, - &inline_fragment.selections, - &type_name, - schema_metadata, - variable_values, - ); - if let Some(sub_new_obj) = sub_new_obj { - // If the inline fragment projection returns a new object, merge it - deep_merge_objects(&mut new_obj, sub_new_obj) - } else { - // If the inline fragment projection returns None, skip it - continue; + // To avoid writing empty fields, we write to a temporary buffer first + self.project_requires( + &requires_selection.selections.items, + original, + writer, + *first, + Some(response_key), + )?; + *first = false; + } + SelectionItem::InlineFragment(requires_selection) => { + let type_condition = &requires_selection.type_condition; + + let type_name = match entity_obj.get(TYPENAME_FIELD) { + Some(Value::String(type_name)) => type_name, + _ => type_condition, + }; + // For projection, both sides of the condition are valid + if self + .schema_metadata + .possible_types + .entity_satisfies_type_condition(type_name, type_condition) + || self + .schema_metadata + .possible_types + .entity_satisfies_type_condition(type_condition, type_name) + { + self.project_requires_map_mut( + &requires_selection.selections.items, + entity_obj, + writer, + first, + parent_response_key, + parent_first, + )?; } } - } - SelectionItem::FragmentSpread(_name_ref) => { - // We only minify the queries to subgraphs, so we never have fragment spreads here. - // In this projection, we expect only inline fragments and fields - // as it's the query produced by the ast normalization process. - unreachable!("Fragment spreads should not exist in the final response projection."); + SelectionItem::FragmentSpread(_name_ref) => { + // We only minify the queries to subgraphs, so we never have fragment spreads here + unreachable!("Fragment spreads should not exist in FetchNode::requires."); + } } } + Ok(()) } - Some(new_obj) } -#[instrument( - level = "trace", - skip_all, - fields( - type_name = %type_name, - selection_set = ?selection_set.items.iter().map(|s| s.to_string()).collect::>(), - data = ?data - ) -)] -fn project_selection_set( - data: &mut Value, - errors: &mut Vec, - selection_set: &SelectionSet, - type_name: &str, +pub fn traverse_and_callback<'a, Callback>( + current_data: &'a mut Value, + remaining_path: &[FlattenNodePathSegment], schema_metadata: &SchemaMetadata, - variable_values: &Option>, -) { - match data { - Value::Null => { - // If data is Null, no need to project further + callback: &mut Callback, +) where + Callback: FnMut(&'a mut Value), +{ + if remaining_path.is_empty() { + if let Value::Array(arr) = current_data { + // If the path is empty, we call the callback on each item in the array + // We iterate because we want the entity objects directly + for item in arr.iter_mut() { + callback(item); + } + } else { + // If the path is empty and current_data is not an array, just call the callback + callback(current_data); } - Value::String(value) => { - if let Some(enum_values) = schema_metadata.enum_values.get(type_name) { - if !enum_values.contains(value) { - // If the value is not a valid enum value, add an error - // and set data to Null - *data = Value::Null; // Set data to Null if the value is not valid - errors.push(GraphQLError { - message: format!( - "Value is not a valid enum value for type '{}'", - type_name - ), - locations: None, - path: None, - extensions: None, - }); + return; + } + + match &remaining_path[0] { + FlattenNodePathSegment::List => { + // If the key is List, we expect current_data to be an array + if let Value::Array(arr) = current_data { + let rest_of_path = &remaining_path[1..]; + for item in arr.iter_mut() { + traverse_and_callback(item, rest_of_path, schema_metadata, callback); } - } // No further processing needed for strings + } } - Value::Array(arr) => { - // If data is an array, project each item in the array - for item in arr { - project_selection_set( - item, - errors, - selection_set, - type_name, - schema_metadata, - variable_values, - ); - } // No further processing needed for arrays + FlattenNodePathSegment::Field(field_name) => { + // If the key is Field, we expect current_data to be an object + if let Value::Object(map) = current_data { + if let Some(next_data) = map.get_mut(field_name) { + let rest_of_path = &remaining_path[1..]; + traverse_and_callback(next_data, rest_of_path, schema_metadata, callback); + } + } } - Value::Object(obj) => { - match project_selection_set_with_map( - obj, - errors, - selection_set, - type_name, - schema_metadata, - variable_values, - ) { - Some(new_obj) => { - // If the projection returns a new object, replace the old one - *obj = new_obj; + FlattenNodePathSegment::Cast(type_condition) => { + // If the key is Cast, we expect current_data to be an object or an array + if let Value::Object(obj) = current_data { + let type_name = match obj.get(TYPENAME_FIELD) { + Some(Value::String(type_name)) => type_name, + _ => type_condition, // Default to type_condition if not found + }; + if schema_metadata + .possible_types + .entity_satisfies_type_condition(type_name, type_condition) + { + let rest_of_path = &remaining_path[1..]; + traverse_and_callback(current_data, rest_of_path, schema_metadata, callback); } - None => { - // If the projection returns None, set data to Null - *data = Value::Null; + } else if let Value::Array(arr) = current_data { + // If the current data is an array, we need to check each item + for item in arr.iter_mut() { + traverse_and_callback(item, remaining_path, schema_metadata, callback); } } } - _ => {} } } -#[instrument(level = "trace", skip_all)] -pub fn project_data_by_operation( - data: &mut Value, - errors: &mut Vec, - operation: &OperationDefinition, - schema_metadata: &SchemaMetadata, - variable_values: &Option>, -) { - let root_type_name = match operation.operation_kind { - Some(OperationKind::Query) => "Query", - Some(OperationKind::Mutation) => "Mutation", - Some(OperationKind::Subscription) => "Subscription", - None => "Query", - }; - // Project the data based on the selection set - project_selection_set( - data, - errors, - &operation.selection_set, - root_type_name, - schema_metadata, - variable_values, - ) +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ExposeQueryPlanMode { + Yes, + No, + DryRun, } #[instrument( @@ -1393,21 +1161,35 @@ pub fn project_data_by_operation( fields( query_plan = ?query_plan, variable_values = ?variable_values, - operation = ?operation.to_string(), ) )] +#[allow(clippy::too_many_arguments)] pub async fn execute_query_plan( query_plan: &QueryPlan, subgraph_executor_map: &SubgraphExecutorMap, variable_values: &Option>, schema_metadata: &SchemaMetadata, - operation: &OperationDefinition, + operation_type_name: &str, + selections: &Vec, has_introspection: bool, -) -> ExecutionResult { - let mut result_data = Value::Null; // Initialize data as Null + expose_query_plan: ExposeQueryPlanMode, +) -> std::io::Result> { + let mut result_data = if has_introspection { + schema_metadata.introspection_query_json.clone() + } else { + Value::Null + }; let mut result_errors = vec![]; // Initial errors are empty - #[allow(unused_mut)] - let mut result_extensions = HashMap::new(); // Initial extensions are empty + let mut result_extensions = if expose_query_plan == ExposeQueryPlanMode::Yes + || expose_query_plan == ExposeQueryPlanMode::DryRun + { + HashMap::from_iter([( + "queryPlan".to_string(), + serde_json::to_value(query_plan).unwrap(), + )]) + } else { + HashMap::new() + }; let mut execution_context = QueryPlanExecutionContext { variable_values, subgraph_executor_map, @@ -1415,41 +1197,25 @@ pub async fn execute_query_plan( errors: result_errors, extensions: result_extensions, }; - query_plan - .execute(&mut execution_context, &mut result_data) - .await; + if expose_query_plan != ExposeQueryPlanMode::DryRun { + query_plan + .execute(&mut execution_context, &mut result_data) + .await; + } result_errors = execution_context.errors; // Get the final errors from the execution context result_extensions = execution_context.extensions; // Get the final extensions from the execution context - if !result_data.is_null() || has_introspection { - if result_data.is_null() { - result_data = Value::Object(serde_json::Map::new()); // Initialize as empty object if Null - } - project_data_by_operation( - &mut result_data, - &mut result_errors, - operation, - schema_metadata, - variable_values, - ); - } + let mut writer = Vec::with_capacity(4096); + projection::project_by_operation( + &mut writer, + &result_data, + &mut result_errors, + &result_extensions, + operation_type_name, + selections, + variable_values, + )?; - ExecutionResult { - data: if result_data.is_null() { - None - } else { - Some(result_data) - }, - errors: if result_errors.is_empty() { - None - } else { - Some(result_errors) - }, - extensions: if result_extensions.is_empty() { - None - } else { - Some(result_extensions) - }, - } + Ok(writer) } #[cfg(test)] diff --git a/lib/query-plan-executor/src/projection.rs b/lib/query-plan-executor/src/projection.rs new file mode 100644 index 000000000..748738498 --- /dev/null +++ b/lib/query-plan-executor/src/projection.rs @@ -0,0 +1,587 @@ +use std::{ + collections::{HashMap, HashSet}, + io::Write, +}; + +use indexmap::IndexMap; +use query_planner::{ + ast::{ + operation::OperationDefinition, selection_item::SelectionItem, selection_set::SelectionSet, + }, + state::supergraph_state::OperationKind, +}; +use serde_json::{Map, Value}; +use tracing::{instrument, warn}; + +use crate::{ + json_writer::write_and_escape_string, schema_metadata::SchemaMetadata, GraphQLError, + TYPENAME_FIELD, +}; + +#[derive(Debug)] +pub struct FieldProjectionPlan { + field_name: String, + field_type: String, + response_key: String, + conditions: FieldProjectionCondition, + selections: Option>, +} + +#[derive(Debug, Clone)] +pub enum FieldProjectionCondition { + IncludeIfVariable(String), + SkipIfVariable(String), + ParentTypeCondition(HashSet), + FieldTypeCondition(HashSet), + EnumValuesCondition(HashSet), + Or(Box, Box), + And(Box, Box), +} + +pub enum FieldProjectionConditionError { + InvalidParentType, + InvalidFieldType, + Skip, + InvalidEnumValue, +} + +impl FieldProjectionCondition { + pub fn check( + &self, + parent_type_name: &str, + field_type_name: &str, + field_value: &Option<&Value>, + variable_values: &Option>, + ) -> Result<(), FieldProjectionConditionError> { + match self { + FieldProjectionCondition::And(condition_a, condition_b) => condition_a + .check( + parent_type_name, + field_type_name, + field_value, + variable_values, + ) + .and(condition_b.check( + parent_type_name, + field_type_name, + field_value, + variable_values, + )), + FieldProjectionCondition::Or(condition_a, condition_b) => condition_a + .check( + parent_type_name, + field_type_name, + field_value, + variable_values, + ) + .or(condition_b.check( + parent_type_name, + field_type_name, + field_value, + variable_values, + )), + FieldProjectionCondition::IncludeIfVariable(variable_name) => { + if let Some(values) = variable_values { + if values + .get(variable_name) + .is_some_and(|v| v.as_bool().unwrap_or(false)) + { + Ok(()) + } else { + Err(FieldProjectionConditionError::Skip) + } + } else { + Err(FieldProjectionConditionError::Skip) + } + } + FieldProjectionCondition::SkipIfVariable(variable_name) => { + if let Some(values) = variable_values { + if values + .get(variable_name) + .is_some_and(|v| v.as_bool().unwrap_or(false)) + { + return Err(FieldProjectionConditionError::Skip); + } + } + Ok(()) + } + FieldProjectionCondition::ParentTypeCondition(possible_types) => { + if possible_types.contains(parent_type_name) { + Ok(()) + } else { + Err(FieldProjectionConditionError::InvalidParentType) + } + } + FieldProjectionCondition::FieldTypeCondition(possible_types) => { + let field_type_name = field_value + .and_then(|value| value.get(TYPENAME_FIELD)) + .and_then(|v| v.as_str()) + .unwrap_or(field_type_name); + if possible_types.contains(field_type_name) { + Ok(()) + } else { + Err(FieldProjectionConditionError::InvalidFieldType) + } + } + FieldProjectionCondition::EnumValuesCondition(enum_values) => { + if let Some(Value::String(string_value)) = field_value { + if enum_values.contains(string_value) { + Ok(()) + } else { + Err(FieldProjectionConditionError::InvalidEnumValue) + } + } else { + Ok(()) + } + } + } + } +} + +impl FieldProjectionPlan { + pub fn from_selection_set( + selection_set: &SelectionSet, + schema_metadata: &SchemaMetadata, + type_name: &str, + condition: &FieldProjectionCondition, + ) -> Option> { + let mut field_selections: IndexMap = IndexMap::new(); + for selection_item in &selection_set.items { + match selection_item { + SelectionItem::Field(field) => { + let field_name = &field.name; + let response_key = field.alias.as_ref().unwrap_or(field_name); + let field_type = if field_name == TYPENAME_FIELD { + "String" + } else { + let field_map = match schema_metadata.type_fields.get(type_name) { + Some(fields) => fields, + None => { + warn!("No fields found for type {} in schema metadata.", type_name); + return None; + } + }; + match field_map.get(field_name) { + Some(field_type) => field_type, + None => { + warn!( + "Field {} not found in type {} in schema metadata.", + field_name, type_name + ); + continue; + } + } + }; + + let possible_types_for_field = schema_metadata + .possible_types + .get_possible_types(field_type); + let mut conditions_for_selections = + FieldProjectionCondition::ParentTypeCondition( + possible_types_for_field.clone(), + ); + if let Some(include_if) = &field.include_if { + conditions_for_selections = FieldProjectionCondition::And( + Box::new(conditions_for_selections), + Box::new(FieldProjectionCondition::IncludeIfVariable( + include_if.clone(), + )), + ); + } + if let Some(skip_if) = &field.skip_if { + conditions_for_selections = FieldProjectionCondition::And( + Box::new(conditions_for_selections), + Box::new(FieldProjectionCondition::SkipIfVariable(skip_if.clone())), + ); + } + + let mut condition_for_field: FieldProjectionCondition = condition.clone(); + condition_for_field = FieldProjectionCondition::And( + Box::new(condition_for_field), + Box::new(FieldProjectionCondition::FieldTypeCondition( + possible_types_for_field, + )), + ); + if let Some(include_if) = &field.include_if { + condition_for_field = FieldProjectionCondition::And( + Box::new(condition_for_field), + Box::new(FieldProjectionCondition::IncludeIfVariable( + include_if.clone(), + )), + ); + } + if let Some(skip_if) = &field.skip_if { + condition_for_field = FieldProjectionCondition::And( + Box::new(condition_for_field), + Box::new(FieldProjectionCondition::SkipIfVariable(skip_if.clone())), + ); + } + if let Some(enum_values) = schema_metadata.enum_values.get(field_type) { + condition_for_field = FieldProjectionCondition::And( + Box::new(condition_for_field), + Box::new(FieldProjectionCondition::EnumValuesCondition( + enum_values.clone(), + )), + ); + } + + if let Some(existing_field) = field_selections.get_mut(response_key) { + existing_field.conditions = FieldProjectionCondition::Or( + Box::new(existing_field.conditions.clone()), + Box::new(condition_for_field), + ); + + if let Some(new_selections) = { + FieldProjectionPlan::from_selection_set( + &field.selections, + schema_metadata, + field_type, + &conditions_for_selections, + ) + } { + match existing_field.selections { + Some(ref mut selections) => { + selections.extend(new_selections); + } + None => { + existing_field.selections = Some(new_selections); + } + } + } + } else { + let new_plan = FieldProjectionPlan { + field_name: field_name.to_string(), + field_type: field_type.to_string(), + response_key: response_key.to_string(), + conditions: condition_for_field, + selections: FieldProjectionPlan::from_selection_set( + &field.selections, + schema_metadata, + field_type, + &conditions_for_selections, + ), + }; + field_selections.insert(response_key.to_string(), new_plan); + } + } + SelectionItem::InlineFragment(inline_fragment) => { + let inline_fragment_type = &inline_fragment.type_condition; + let mut condition_for_inline_fragment = condition.clone(); + let possible_types_for_inline_fragment = schema_metadata + .possible_types + .get_possible_types(inline_fragment_type); + condition_for_inline_fragment = FieldProjectionCondition::And( + Box::new(condition_for_inline_fragment), + Box::new(FieldProjectionCondition::ParentTypeCondition( + possible_types_for_inline_fragment, + )), + ); + if let Some(skip_if) = &inline_fragment.skip_if { + condition_for_inline_fragment = FieldProjectionCondition::And( + Box::new(condition_for_inline_fragment), + Box::new(FieldProjectionCondition::SkipIfVariable(skip_if.clone())), + ); + } + if let Some(include_if) = &inline_fragment.include_if { + condition_for_inline_fragment = FieldProjectionCondition::And( + Box::new(condition_for_inline_fragment), + Box::new(FieldProjectionCondition::IncludeIfVariable( + include_if.clone(), + )), + ); + } + if let Some(inline_fragment_selections) = + FieldProjectionPlan::from_selection_set( + &inline_fragment.selections, + schema_metadata, + inline_fragment_type, + &condition_for_inline_fragment, + ) + { + for selection in inline_fragment_selections { + if let Some(existing_field) = + field_selections.get_mut(&selection.response_key) + { + existing_field.conditions = FieldProjectionCondition::Or( + Box::new(existing_field.conditions.clone()), + Box::new(selection.conditions), + ); + if let Some(new_selections) = selection.selections { + match existing_field.selections { + Some(ref mut selections) => { + selections.extend(new_selections); + } + None => { + existing_field.selections = Some(new_selections); + } + } + } + } else { + field_selections + .insert(selection.response_key.to_string(), selection); + } + } + } + } + SelectionItem::FragmentSpread(_name_ref) => { + // Fragment spreads should not exist in the final response projection. + unreachable!( + "Fragment spreads should not exist in the final response projection." + ); + } + } + } + + if field_selections.is_empty() { + None + } else { + Some( + field_selections + .into_iter() + .map(|(_, selection)| selection) + .collect::>(), + ) + } + } + pub fn from_operation( + operation: &OperationDefinition, + schema_metadata: &SchemaMetadata, + ) -> (&'static str, Vec) { + let root_type_name = match operation.operation_kind { + Some(OperationKind::Query) => "Query", + Some(OperationKind::Mutation) => "Mutation", + Some(OperationKind::Subscription) => "Subscription", + None => "Query", + }; + + let field_type_conditions = schema_metadata + .possible_types + .get_possible_types(root_type_name); + let conditions = FieldProjectionCondition::ParentTypeCondition(field_type_conditions); + ( + root_type_name, + FieldProjectionPlan::from_selection_set( + &operation.selection_set, + schema_metadata, + root_type_name, + &conditions, + ) + .unwrap_or_default(), + ) + } +} + +#[instrument(level = "trace", skip_all)] +pub fn project_by_operation( + writer: &mut impl Write, + data: &Value, + errors: &mut Vec, + extensions: &HashMap, + operation_type_name: &str, + selections: &Vec, + variable_values: &Option>, +) -> std::io::Result<()> { + writer.write_all(b"{")?; + writer.write_all(b"\"")?; + writer.write_all(b"data")?; + writer.write_all(b"\"")?; + writer.write_all(b":")?; + + if let Some(data_map) = data.as_object() { + let mut first = true; + project_selection_set_with_map( + data_map, + errors, + selections, + variable_values, + operation_type_name, + writer, + &mut first, // Start with first as true to add the opening brace + )?; + if !first { + writer.write_all(b"}")?; + } else { + // If no selections were made, we should return an empty object + writer.write_all(b"{}")?; + } + } + + if !errors.is_empty() { + writer.write_all(b",\"errors\":")?; + serde_json::to_writer(&mut *writer, errors)?; + } + if !extensions.is_empty() { + writer.write_all(b",\"extensions\":")?; + serde_json::to_writer(&mut *writer, extensions)?; + } + + writer.write_all(b"}")?; + + Ok(()) +} + +#[instrument( + level = "trace", + skip_all, + fields( + data = ?data + ) +)] +fn project_selection_set( + data: &Value, + errors: &mut Vec, + selection: &FieldProjectionPlan, + variable_values: &Option>, + writer: &mut impl std::io::Write, +) -> std::io::Result<()> { + match data { + Value::Null => writer.write_all(b"null"), + Value::Bool(true) => writer.write_all(b"true"), + Value::Bool(false) => writer.write_all(b"false"), + Value::Number(num) => writer.write_all(num.to_string().as_bytes()), + Value::String(value) => { + // Assuming write_and_escape_string is modified to take Vec + write_and_escape_string(writer, value) + } + Value::Array(arr) => { + writer.write_all(b"[")?; + let mut first = true; + for item in arr.iter() { + if !first { + writer.write_all(b",")?; + } + project_selection_set(item, errors, selection, variable_values, writer)?; + first = false; + } + writer.write_all(b"]") + } + Value::Object(obj) => { + match selection.selections.as_ref() { + Some(selections) => { + let mut first = true; + let type_name = obj + .get(TYPENAME_FIELD) + .and_then(Value::as_str) + .unwrap_or(&selection.field_type); + project_selection_set_with_map( + obj, + errors, + selections, + variable_values, + type_name, + writer, + &mut first, + )?; + if !first { + writer.write_all(b"}") + } else { + // If no selections were made, we should return an empty object + writer.write_all(b"{}") + } + } + None => { + // If the selection set is not projected, we should return null + writer.write_all(b"null") + } + } + } + }?; + Ok(()) +} + +#[instrument( + level = "trace", + skip_all, + fields( + obj = ?obj + ) +)] +// TODO: simplfy args +#[allow(clippy::too_many_arguments)] +fn project_selection_set_with_map( + obj: &Map, + errors: &mut Vec, + selections: &Vec, + variable_values: &Option>, + parent_type_name: &str, + writer: &mut impl std::io::Write, + first: &mut bool, +) -> std::io::Result<()> { + for selection in selections { + let field_val = obj + .get(&selection.field_name) + .or_else(|| obj.get(&selection.response_key)); + match selection.conditions.check( + parent_type_name, + &selection.field_type, + &field_val, + variable_values, + ) { + Ok(_) => { + if *first { + writer.write_all(b"{")?; + } else { + writer.write_all(b",")?; + } + *first = false; + + writer.write_all(b"\"")?; + writer.write_all(selection.response_key.as_bytes())?; + writer.write_all(b"\":")?; + + if let Some(field_val) = field_val { + project_selection_set(field_val, errors, selection, variable_values, writer)?; + } else if selection.field_name == TYPENAME_FIELD { + // If the field is TYPENAME_FIELD, we should set it to the parent type name + writer.write_all(b"\"")?; + writer.write_all(parent_type_name.as_bytes())?; + writer.write_all(b"\"")?; + } else { + // If the field is not found in the object, set it to Null + writer.write_all(b"null")?; + } + } + Err(FieldProjectionConditionError::Skip) => { + // Skip this field + continue; + } + Err(FieldProjectionConditionError::InvalidParentType) => { + // Skip this field as the parent type does not match + continue; + } + Err(FieldProjectionConditionError::InvalidEnumValue) => { + println!("Invalid enum value for field: {}", selection.field_name); + if *first { + writer.write_all(b"{")?; + } else { + writer.write_all(b",")?; + } + *first = false; + + writer.write_all(b"\"")?; + writer.write_all(selection.response_key.as_bytes())?; + writer.write_all(b"\":null")?; + errors.push(GraphQLError { + message: "Value is not a valid enum value".to_string(), + locations: None, + path: None, + extensions: None, + }); + } + Err(FieldProjectionConditionError::InvalidFieldType) => { + if *first { + writer.write_all(b"{")?; + } else { + writer.write_all(b",")?; + } + *first = false; + + // Skip this field as the field type does not match + writer.write_all(b"\"")?; + writer.write_all(selection.response_key.as_bytes())?; + writer.write_all(b"\":null")?; + } + } + } + Ok(()) +} diff --git a/lib/query-plan-executor/src/schema_metadata.rs b/lib/query-plan-executor/src/schema_metadata.rs index 73bcab668..40691db3f 100644 --- a/lib/query-plan-executor/src/schema_metadata.rs +++ b/lib/query-plan-executor/src/schema_metadata.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use graphql_parser::{ query::Type, @@ -7,12 +7,34 @@ use graphql_parser::{ use query_planner::consumer_schema::ConsumerSchema; use serde_json::{json, Value}; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct SchemaMetadata { - pub possible_types: HashMap>, - pub enum_values: HashMap>, + pub possible_types: PossibleTypes, + pub enum_values: HashMap>, pub type_fields: HashMap>, - pub introspection_schema_root_json: Value, + pub introspection_query_json: Value, +} + +#[derive(Debug, Default)] +pub struct PossibleTypes { + map: HashMap>, +} + +impl PossibleTypes { + pub fn entity_satisfies_type_condition(&self, type_name: &str, type_condition: &str) -> bool { + if type_name == type_condition { + true + } else if let Some(possible_types_of_type) = self.map.get(type_condition) { + possible_types_of_type.contains(type_name) + } else { + false + } + } + pub fn get_possible_types(&self, type_name: &str) -> HashSet { + let mut possible_types = self.map.get(type_name).cloned().unwrap_or_default(); + possible_types.insert(type_name.to_string()); + possible_types + } } pub trait SchemaWithMetadata { @@ -23,15 +45,15 @@ impl SchemaWithMetadata for ConsumerSchema { fn schema_metadata(&self) -> SchemaMetadata { let mut first_possible_types: HashMap> = HashMap::new(); let mut type_fields: HashMap> = HashMap::new(); - let mut enum_values: HashMap> = HashMap::new(); + let mut enum_values: HashMap> = HashMap::new(); for definition in &self.document.definitions { match definition { Definition::TypeDefinition(TypeDefinition::Enum(enum_type)) => { let name = enum_type.name.to_string(); - let mut values = vec![]; + let mut values = HashSet::new(); for enum_value in &enum_type.values { - values.push(enum_value.name.to_string()); + values.insert(enum_value.name.to_string()); } enum_values.insert(name, values); } @@ -77,16 +99,16 @@ impl SchemaWithMetadata for ConsumerSchema { } } - let mut final_possible_types: HashMap> = HashMap::new(); + let mut final_possible_types: HashMap> = HashMap::new(); // Re-iterate over the possible_types for (definition_name_of_x, first_possible_types_of_x) in &first_possible_types { - let mut possible_types_of_x: Vec = Vec::new(); + let mut possible_types_of_x: HashSet = HashSet::new(); for definition_name_of_y in first_possible_types_of_x { - possible_types_of_x.push(definition_name_of_y.to_string()); + possible_types_of_x.insert(definition_name_of_y.to_string()); let possible_types_of_y = first_possible_types.get(definition_name_of_y); if let Some(possible_types_of_y) = possible_types_of_y { for definition_name_of_z in possible_types_of_y { - possible_types_of_x.push(definition_name_of_z.to_string()); + possible_types_of_x.insert(definition_name_of_z.to_string()); } } } @@ -95,13 +117,15 @@ impl SchemaWithMetadata for ConsumerSchema { let introspection_query = crate::introspection::introspection_query_from_ast(&self.document); - let introspection_schema_root_json = json!(introspection_query.__schema); + let introspection_query_json = json!(introspection_query); SchemaMetadata { - possible_types: final_possible_types, + possible_types: PossibleTypes { + map: final_possible_types, + }, enum_values, type_fields, - introspection_schema_root_json, + introspection_query_json, } } } diff --git a/lib/query-plan-executor/src/tests/mod.rs b/lib/query-plan-executor/src/tests/mod.rs index 86ca64bd0..5cde7d2c8 100644 --- a/lib/query-plan-executor/src/tests/mod.rs +++ b/lib/query-plan-executor/src/tests/mod.rs @@ -1,9 +1,12 @@ use query_planner::graph::PlannerOverrideContext; use subgraphs::accounts; -use crate::executors::{common::SubgraphExecutor, map::SubgraphExecutorMap}; +use crate::{ + executors::{common::SubgraphExecutor, map::SubgraphExecutorMap}, + projection, +}; -mod traverse_and_collect; +mod traverse_and_callback; #[test] fn query_executor_pipeline_locally() { @@ -39,18 +42,19 @@ fn query_executor_pipeline_locally() { subgraph_executor_map.insert_boxed_arc("inventory".to_string(), inventory.to_boxed_arc()); subgraph_executor_map.insert_boxed_arc("products".to_string(), products.to_boxed_arc()); subgraph_executor_map.insert_boxed_arc("reviews".to_string(), reviews.to_boxed_arc()); + let (root_type_name, projection_selections) = + projection::FieldProjectionPlan::from_operation(normalized_operation, &schema_metadata); let result = crate::execute_query_plan( &query_plan, &subgraph_executor_map, &None, &schema_metadata, - normalized_operation, + root_type_name, + &projection_selections, false, + crate::ExposeQueryPlanMode::No, ) .await; - insta::assert_snapshot!(format!( - "{}", - serde_json::to_string_pretty(&result).unwrap_or_default() - )); + insta::assert_snapshot!(String::from_utf8(result.unwrap()).unwrap(),); }); } diff --git a/lib/query-plan-executor/src/tests/snapshots/query_plan_executor__tests__query_executor_pipeline_locally.snap b/lib/query-plan-executor/src/tests/snapshots/query_plan_executor__tests__query_executor_pipeline_locally.snap index f36cb38a0..b81cfdd0a 100644 --- a/lib/query-plan-executor/src/tests/snapshots/query_plan_executor__tests__query_executor_pipeline_locally.snap +++ b/lib/query-plan-executor/src/tests/snapshots/query_plan_executor__tests__query_executor_pipeline_locally.snap @@ -1,2336 +1,5 @@ --- source: lib/query-plan-executor/src/tests/mod.rs -expression: "format!(\"{}\", serde_json::to_string_pretty(&result).unwrap_or_default())" +expression: result --- -{ - "data": { - "users": [ - { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - } - } - ] - }, - { - "id": "2", - "username": "dotansimha", - "name": "Dotan Simha", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - } - } - ] - }, - { - "id": "3", - "username": "kamilkisiela", - "name": "Kamil Kisiela", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - } - } - ] - }, - { - "id": "4", - "username": "ardatan", - "name": "Arda Tanrikulu", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - } - } - ] - }, - { - "id": "5", - "username": "gilgardosh", - "name": "Gil Gardosh", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - } - } - ] - }, - { - "id": "6", - "username": "laurin", - "name": "Laurin Quast", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - } - } - ] - } - ], - "topProducts": [ - { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100, - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "3", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "4", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - }, - { - "inStock": false, - "name": "Couch", - "price": 1299, - "shippingEstimate": null, - "upc": "2", - "weight": 1000, - "reviews": [ - { - "id": "5", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "6", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "7", - "body": "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "8", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - }, - { - "inStock": false, - "name": "Glass", - "price": 15, - "shippingEstimate": null, - "upc": "3", - "weight": 20, - "reviews": [ - { - "id": "9", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - }, - { - "inStock": false, - "name": "Chair", - "price": 499, - "shippingEstimate": null, - "upc": "4", - "weight": 100, - "reviews": [ - { - "id": "10", - "body": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - }, - { - "id": "11", - "body": "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.", - "author": { - "id": "1", - "username": "urigo", - "name": "Uri Goldshtein", - "reviews": [ - { - "id": "1", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - }, - { - "id": "2", - "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi", - "product": { - "inStock": true, - "name": "Table", - "price": 899, - "shippingEstimate": null, - "upc": "1", - "weight": 100 - } - } - ] - } - } - ] - }, - { - "inStock": true, - "name": "TV", - "price": 1299, - "shippingEstimate": null, - "upc": "5", - "weight": 1000, - "reviews": [] - } - ] - } -} +{"data":{"users":[{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]}}]},{"id":"2","username":"dotansimha","name":"Dotan Simha","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]}}]},{"id":"3","username":"kamilkisiela","name":"Kamil Kisiela","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]}}]},{"id":"4","username":"ardatan","name":"Arda Tanrikulu","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]}}]},{"id":"5","username":"gilgardosh","name":"Gil Gardosh","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]}}]},{"id":"6","username":"laurin","name":"Laurin Quast","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]}}]}],"topProducts":[{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100,"reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"3","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"4","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]},{"inStock":false,"name":"Couch","price":1299,"shippingEstimate":null,"upc":"2","weight":1000,"reviews":[{"id":"5","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"6","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"7","body":"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"8","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]},{"inStock":false,"name":"Glass","price":15,"shippingEstimate":null,"upc":"3","weight":20,"reviews":[{"id":"9","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]},{"inStock":false,"name":"Chair","price":499,"shippingEstimate":null,"upc":"4","weight":100,"reviews":[{"id":"10","body":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}},{"id":"11","body":"At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.","author":{"id":"1","username":"urigo","name":"Uri Goldshtein","reviews":[{"id":"1","body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}},{"id":"2","body":"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugi","product":{"inStock":true,"name":"Table","price":899,"shippingEstimate":null,"upc":"1","weight":100}}]}}]},{"inStock":true,"name":"TV","price":1299,"shippingEstimate":null,"upc":"5","weight":1000,"reviews":[]}]}} diff --git a/lib/query-plan-executor/src/tests/traverse_and_collect.rs b/lib/query-plan-executor/src/tests/traverse_and_callback.rs similarity index 73% rename from lib/query-plan-executor/src/tests/traverse_and_collect.rs rename to lib/query-plan-executor/src/tests/traverse_and_callback.rs index f42854775..a41e46c17 100644 --- a/lib/query-plan-executor/src/tests/traverse_and_collect.rs +++ b/lib/query-plan-executor/src/tests/traverse_and_callback.rs @@ -1,10 +1,10 @@ use query_planner::planner::plan_nodes::FlattenNodePathSegment; use serde_json::json; -use crate::traverse_and_collect; +use crate::{schema_metadata::SchemaMetadata, traverse_and_callback}; #[test] -fn array_cast_test() -> () { +fn array_cast_test() { let path = [ FlattenNodePathSegment::Field("magazine".into()), FlattenNodePathSegment::List, @@ -35,7 +35,10 @@ fn array_cast_test() -> () { ] }); - let result = traverse_and_collect(&mut data, &path); + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); let expected = json!([{ "id": "p4", "__typename": "Magazine", @@ -56,7 +59,11 @@ fn array_cast_test() -> () { fn simple_field_access() { let path = [FlattenNodePathSegment::Field("a".into())]; let mut data = json!({"a": 1, "b": 2}); - let result = traverse_and_collect(&mut data, &path); + + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); assert_eq!( serde_json::to_string_pretty(&result).unwrap_or_default(), serde_json::to_string_pretty(&json!([1])).unwrap_or_default() @@ -70,7 +77,11 @@ fn nested_field_access() { FlattenNodePathSegment::Field("b".into()), ]; let mut data = json!({"a": {"b": 3}}); - let result = traverse_and_collect(&mut data, &path); + + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); assert_eq!( serde_json::to_string_pretty(&result).unwrap_or_default(), serde_json::to_string_pretty(&json!([3])).unwrap_or_default() @@ -84,7 +95,11 @@ fn simple_list_access() { FlattenNodePathSegment::List, ]; let mut data = json!({"a": [1, 2, 3]}); - let result = traverse_and_collect(&mut data, &path); + + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); assert_eq!( serde_json::to_string_pretty(&result).unwrap_or_default(), serde_json::to_string_pretty(&json!([1, 2, 3])).unwrap_or_default() @@ -99,7 +114,11 @@ fn field_access_in_list() { FlattenNodePathSegment::Field("b".into()), ]; let mut data = json!({"a": [{"b": 1}, {"b": 2}]}); - let result = traverse_and_collect(&mut data, &path); + + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); assert_eq!( serde_json::to_string_pretty(&result).unwrap_or_default(), serde_json::to_string_pretty(&json!([1, 2])).unwrap_or_default() @@ -119,7 +138,11 @@ fn cast_in_list_with_field_access() { {"__typename": "TypeB", "b": 2}, {"__typename": "TypeA", "b": 3} ]}); - let result = traverse_and_collect(&mut data, &path); + + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); assert_eq!( serde_json::to_string_pretty(&result).unwrap_or_default(), serde_json::to_string_pretty(&json!([1, 3])).unwrap_or_default() @@ -146,7 +169,11 @@ fn filter_list_by_cast() { } ] }); - let result = traverse_and_collect(&mut data, &path); + + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); let expected = json!([ { "__typename": "Movie", @@ -163,7 +190,11 @@ fn filter_list_by_cast() { fn invalid_field() { let path = [FlattenNodePathSegment::Field("c".into())]; let mut data = json!({"a": 1}); - let result = traverse_and_collect(&mut data, &path); + + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); assert_eq!( serde_json::to_string_pretty(&result).unwrap_or_default(), "[]" @@ -177,7 +208,11 @@ fn invalid_nested_field() { FlattenNodePathSegment::Field("c".into()), ]; let mut data = json!({"a": {"b": 1}}); - let result = traverse_and_collect(&mut data, &path); + + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); assert_eq!( serde_json::to_string_pretty(&result).unwrap_or_default(), "[]" @@ -188,7 +223,11 @@ fn invalid_nested_field() { fn initial_data_is_array() { let path = [FlattenNodePathSegment::List]; let mut data = json!([1, 2, 3]); - let result = traverse_and_collect(&mut data, &path); + + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); assert_eq!( serde_json::to_string_pretty(&result).unwrap_or_default(), serde_json::to_string_pretty(&json!([1, 2, 3])).unwrap_or_default() @@ -202,7 +241,11 @@ fn initial_data_is_array_with_field_access() { FlattenNodePathSegment::Field("a".into()), ]; let mut data = json!([{"a": 1}, {"a": 2}]); - let result = traverse_and_collect(&mut data, &path); + + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); assert_eq!( serde_json::to_string_pretty(&result).unwrap_or_default(), serde_json::to_string_pretty(&json!([1, 2])).unwrap_or_default() @@ -216,7 +259,11 @@ fn cast_on_object_without_typename() { FlattenNodePathSegment::Field("a".into()), ]; let mut data = json!({"a": 1}); - let result = traverse_and_collect(&mut data, &path); + + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); assert_eq!( serde_json::to_string_pretty(&result).unwrap_or_default(), serde_json::to_string_pretty(&json!([1])).unwrap_or_default() @@ -234,7 +281,11 @@ fn no_match_on_cast() { {"__typename": "TypeA", "b": 1}, {"__typename": "TypeB", "b": 2} ]}); - let result = traverse_and_collect(&mut data, &path); + + let mut result = vec![]; + traverse_and_callback(&mut data, &path, &SchemaMetadata::default(), &mut |value| { + result.push(value); + }); assert_eq!( serde_json::to_string_pretty(&result).unwrap_or_default(), "[]" diff --git a/lib/query-planner/Cargo.toml b/lib/query-planner/Cargo.toml index f8a74500b..115567ae9 100644 --- a/lib/query-planner/Cargo.toml +++ b/lib/query-planner/Cargo.toml @@ -11,7 +11,7 @@ graphql-tools = "0.4.0" graphql-parser = "0.4.1" # Serialization serde = "1.0.219" -serde_json = { version = "1.0.140", features = ["preserve_order"] } +serde_json = "1.0.140" # Tracing tracing = { version = "0.1.41" } # Data Structures @@ -22,7 +22,6 @@ lazy-init = "0.5.1" thiserror = "2.0.12" rustc-hash = "2.1.1" - [dev-dependencies] # Testing insta = { version = "1.42.1" }