From f787734ad34d58c5c1207a8a842cdf5c961e40b1 Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Tue, 12 Dec 2023 16:28:23 -0600 Subject: [PATCH 1/4] WebSocket client module The KittyCAD Rust API client lets users connect to our WebSocket API. But OpenAPI can't tell users what the server expects to receive over that WebSocket, or what it intends to send over the WebSocket either. This library solves the problem. It encapsulates the WebSocket connection from our API client, and adds methods to send and receive the correct types. This way users will always send the right types of requests and always receive the right types of responses. Right now, this API sends over WebSocket text messages (JSON) to make it easier to debug with standard web debugging tools. But in the future, we can offer WebSocket binary messages (BSON), for faster de/encoding. --- Cargo.lock | 1288 +++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + modeling-cmds/src/traits.rs | 2 +- modeling-session/Cargo.toml | 24 + modeling-session/src/lib.rs | 178 +++++ 5 files changed, 1437 insertions(+), 56 deletions(-) create mode 100644 modeling-session/Cargo.toml create mode 100644 modeling-session/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 8fbff084..da057a70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,9 +141,15 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + [[package]] name = "atomic-waker" version = "1.1.2" @@ -200,6 +206,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits 0.2.17", + "serde", ] [[package]] @@ -258,6 +265,9 @@ name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +dependencies = [ + "serde", +] [[package]] name = "cbc" @@ -317,8 +327,9 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits 0.2.17", + "serde", "wasm-bindgen", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -331,12 +342,32 @@ dependencies = [ "inout", ] +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "const-oid" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[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.6" @@ -367,6 +398,25 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -423,7 +473,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -452,7 +502,7 @@ checksum = "587663dd5fb3d10932c8aecfe7c844db1bcf0aee93eeab08fac13dc1212c2e7f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -526,7 +576,7 @@ dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -535,7 +585,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -564,7 +614,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -587,6 +637,12 @@ dependencies = [ "spki", ] +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -608,6 +664,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + [[package]] name = "enum-iterator" version = "1.4.1" @@ -625,7 +690,23 @@ checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", ] [[package]] @@ -638,6 +719,12 @@ dependencies = [ "cgmath", ] +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + [[package]] name = "ff" version = "0.13.0" @@ -660,6 +747,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[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" @@ -669,6 +771,19 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "format_serde_error" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5837b8e6a4001f99fe4746767fb7379e8510c508a843caa136cc12ed9c0bad0" +dependencies = [ + "colored", + "serde", + "serde_json", + "serde_yaml", + "unicode-segmentation", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -731,7 +846,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -782,8 +897,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -813,6 +930,43 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.11", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.3" @@ -854,6 +1008,91 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.11", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.11", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.11", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -887,6 +1126,26 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + [[package]] name = "inflections" version = "1.1.1" @@ -903,6 +1162,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "interceptor" version = "0.10.0" @@ -928,11 +1199,29 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" @@ -943,6 +1232,43 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kittycad" +version = "0.2.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff968a1b90af9a16f162c7bb57d7725ee8d4eeae939a998c82b48129802a2804" +dependencies = [ + "anyhow", + "async-trait", + "base64", + "bigdecimal", + "bytes", + "chrono", + "data-encoding", + "format_serde_error", + "futures", + "http 0.2.11", + "itertools 0.10.5", + "log", + "parse-display", + "phonenumber", + "rand 0.8.5", + "reqwest", + "reqwest-conditional-middleware", + "reqwest-middleware", + "reqwest-retry", + "reqwest-tracing", + "schemars", + "serde", + "serde_bytes", + "serde_json", + "serde_urlencoded", + "thiserror", + "tracing", + "url", + "uuid", +] + [[package]] name = "kittycad-execution-plan" version = "0.1.0" @@ -967,7 +1293,7 @@ dependencies = [ "enum-iterator", "enum-iterator-derive", "euler", - "http", + "http 0.2.11", "kittycad-unit-conversion-derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "measurements", "parse-display", @@ -994,7 +1320,7 @@ dependencies = [ "enum-iterator", "enum-iterator-derive", "euler", - "http", + "http 0.2.11", "kittycad-unit-conversion-derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "measurements", "parse-display", @@ -1005,6 +1331,20 @@ dependencies = [ "uuid", ] +[[package]] +name = "kittycad-modeling-session" +version = "0.1.0" +dependencies = [ + "futures", + "kittycad", + "kittycad-modeling-cmds 0.1.7", + "reqwest", + "serde_json", + "thiserror", + "tokio-tungstenite", + "uuid", +] + [[package]] name = "kittycad-unit-conversion-derive" version = "0.1.0" @@ -1036,9 +1376,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libm" @@ -1055,6 +1395,18 @@ dependencies = [ "cc", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + [[package]] name = "lock_api" version = "0.4.11" @@ -1070,6 +1422,24 @@ name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +dependencies = [ + "serde", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "md-5" @@ -1105,6 +1475,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1134,7 +1520,7 @@ checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1147,6 +1533,24 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.26.4" @@ -1239,9 +1643,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oncemutex" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2" [[package]] name = "opaque-debug" @@ -1250,8 +1660,71 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "p256" -version = "0.13.2" +name = "openssl" +version = "0.10.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45" +dependencies = [ + "bitflags 2.4.1", + "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 2.0.40", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "opentelemetry" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "js-sys", + "lazy_static", + "percent-encoding", + "pin-project", + "rand 0.8.5", + "thiserror", +] + +[[package]] +name = "p256" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ @@ -1273,6 +1746,17 @@ dependencies = [ "sha2", ] +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1280,7 +1764,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -1291,9 +1789,9 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1319,7 +1817,7 @@ dependencies = [ "regex", "regex-syntax 0.7.5", "structmeta", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -1347,6 +1845,47 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phonenumber" +version = "0.3.3+8.13.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "635f3e6288e4f01c049d89332a031bd74f25d64b6fb94703ca966e819488cd06" +dependencies = [ + "bincode", + "either", + "fnv", + "itertools 0.11.0", + "lazy_static", + "nom", + "quick-xml", + "regex", + "regex-cache", + "serde", + "serde_derive", + "strum", + "thiserror", +] + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.40", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -1433,6 +1972,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.33" @@ -1449,7 +1997,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" dependencies = [ "log", - "parking_lot", + "parking_lot 0.12.1", "scheduled-thread-pool", ] @@ -1533,6 +2081,15 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1565,6 +2122,24 @@ dependencies = [ "regex-syntax 0.8.2", ] +[[package]] +name = "regex-cache" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f7b62d69743b8b94f353b6b7c3deb4c5582828328bcb8d5fedf214373808793" +dependencies = [ + "lru-cache", + "oncemutex", + "regex", + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.7.5" @@ -1577,6 +2152,129 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "reqwest" +version = "0.11.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.11", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "reqwest-conditional-middleware" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e50a2e70970896c99d1b8f20ddc30a70b30d3ac6e619a03a8353b64a49b277" +dependencies = [ + "async-trait", + "reqwest", + "reqwest-middleware", + "task-local-extensions", +] + +[[package]] +name = "reqwest-middleware" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a3e86aa6053e59030e7ce2d2a3b258dd08fc2d337d52f73f6cb480f5858690" +dependencies = [ + "anyhow", + "async-trait", + "http 0.2.11", + "reqwest", + "serde", + "task-local-extensions", + "thiserror", +] + +[[package]] +name = "reqwest-retry" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6a11c05102e5bec712c0619b8c7b7eda8b21a558a0bd981ceee15c38df8be4" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "futures", + "getrandom", + "http 0.2.11", + "hyper", + "parking_lot 0.11.2", + "reqwest", + "reqwest-middleware", + "retry-policies", + "task-local-extensions", + "tokio", + "tracing", + "wasm-timer", +] + +[[package]] +name = "reqwest-tracing" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b1e66540e0cac90acadaf7109bf99c90d95abcc94b4c096bfa16a2d7aa7a71" +dependencies = [ + "anyhow", + "async-trait", + "getrandom", + "matchit", + "opentelemetry", + "reqwest", + "reqwest-middleware", + "task-local-extensions", + "tracing", + "tracing-opentelemetry", +] + +[[package]] +name = "retry-policies" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e09bbcb5003282bcb688f0bae741b278e9c7e8f378f561522c9806c58e075d9b" +dependencies = [ + "anyhow", + "chrono", + "rand 0.8.5", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -1613,7 +2311,7 @@ dependencies = [ "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1664,6 +2362,19 @@ dependencies = [ "nom", ] +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" version = "0.21.10" @@ -1676,6 +2387,15 @@ dependencies = [ "sct", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -1686,11 +2406,26 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] [[package]] name = "scheduled-thread-pool" @@ -1698,7 +2433,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" dependencies = [ - "parking_lot", + "parking_lot 0.12.1", ] [[package]] @@ -1708,6 +2443,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" dependencies = [ "bigdecimal", + "bytes", "chrono", "dyn-clone", "schemars_derive", @@ -1771,6 +2507,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.20" @@ -1803,7 +2562,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -1828,6 +2587,30 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +dependencies = [ + "indexmap 1.9.3", + "ryu", + "serde", + "yaml-rust", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1850,6 +2633,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1899,6 +2691,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.5" @@ -1906,7 +2708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1940,7 +2742,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -1951,7 +2753,29 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", +] + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", ] [[package]] @@ -2001,9 +2825,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "13fa70a4ee923979ffb522cacce59d34421ebdea5625e1073c4326ef9d2dd42e" dependencies = [ "proc-macro2", "quote", @@ -2022,6 +2846,49 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "task-local-extensions" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba323866e5d033818e3240feeb9f7db2c4296674e4d9e16b97b7bf8f490434e8" +dependencies = [ + "pin-utils", +] + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.4.1", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "thiserror" version = "1.0.50" @@ -2039,7 +2906,17 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", ] [[package]] @@ -2097,12 +2974,12 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.5", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2113,7 +2990,152 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", +] + +[[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.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.40", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbbe89715c1dbbb790059e2565353978564924ee85017b5fff365c872ff6721f" +dependencies = [ + "once_cell", + "opentelemetry", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.0.0", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", ] [[package]] @@ -2141,11 +3163,20 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" @@ -2162,6 +3193,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-xid" version = "0.2.4" @@ -2199,18 +3236,32 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "uuid" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ + "atomic", "getrandom", "serde", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2232,6 +3283,15 @@ dependencies = [ "atomic-waker", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2259,10 +3319,22 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.89" @@ -2281,7 +3353,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2292,6 +3364,21 @@ version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.66" @@ -2302,6 +3389,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + [[package]] name = "webrtc" version = "0.9.0" @@ -2426,7 +3519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bebbd40e7f8b630a0f1a74783dbfff1edfc0ccaae891c4689891156a8c4d8c" dependencies = [ "log", - "socket2", + "socket2 0.5.5", "thiserror", "tokio", "webrtc-util", @@ -2534,7 +3627,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -2543,7 +3636,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -2552,13 +3654,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -2567,42 +3684,94 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "x25519-dalek" version = "2.0.0" @@ -2633,6 +3802,15 @@ dependencies = [ "time", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yansi" version = "0.5.1" @@ -2665,5 +3843,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] diff --git a/Cargo.toml b/Cargo.toml index 7b532464..17a08aab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "modeling-cmds", "execution-plan", + "modeling-session", "unit-conversion-derive", ] diff --git a/modeling-cmds/src/traits.rs b/modeling-cmds/src/traits.rs index 6a177971..3cc77ce0 100644 --- a/modeling-cmds/src/traits.rs +++ b/modeling-cmds/src/traits.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::ModelingCmd; /// Some modeling command executed on the KittyCAD engine. -pub trait ModelingCmdVariant<'de> { +pub trait ModelingCmdVariant<'de>: Serialize { /// What the command responds with type Output: ModelingCmdOutput<'de>; /// Take this specific enum variant, and create the general enum. diff --git a/modeling-session/Cargo.toml b/modeling-session/Cargo.toml new file mode 100644 index 00000000..559ee4ad --- /dev/null +++ b/modeling-session/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "kittycad-modeling-session" +version = "0.1.0" +edition = "2021" +authors = ["KittyCAD, Inc."] +description = "Start a session with the KittyCAD Modeling API" +rust-version = "1.74" +repository = "https://github.com/KittyCAD/modeling-api" +keywords = ["kittycad"] +license = "MIT" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +futures = "0.3.29" +kittycad = "0.2.44" +kittycad-modeling-cmds = { path = "../modeling-cmds", features = ["websocket"] } +reqwest = "0.11.22" +serde_json = "1.0.108" +thiserror = "1.0.50" +tokio-tungstenite = "0.21.0" +uuid = { version = "1.6.1", features = ["v4"] } + +[lints] +workspace = true diff --git a/modeling-session/src/lib.rs b/modeling-session/src/lib.rs new file mode 100644 index 00000000..d59a07a5 --- /dev/null +++ b/modeling-session/src/lib.rs @@ -0,0 +1,178 @@ +//! Establish a modeling session with the KittyCAD API. + +use futures::{ + stream::{SplitSink, SplitStream}, + SinkExt, StreamExt, +}; +use kittycad::{types::error::Error as ApiError, Client}; +use kittycad_modeling_cmds::{ + ok_response::OkModelingCmdResponse, + websocket::{ + FailureWebSocketResponse, ModelingCmdReq, OkWebSocketResponseData, WebSocketRequest, WebSocketResponse, + }, + ModelingCmd, +}; +use reqwest::Upgraded; +use tokio_tungstenite::{tungstenite::Message as WsMsg, WebSocketStream}; +use uuid::Uuid; + +/// Parameters for starting a session with the KittyCAD Modeling API. +pub struct SessionBuilder { + /// Client to the KittyCAD API. + pub client: Client, + /// Frames per second of the video feed. + pub fps: Option, + /// If true, engine will render video frames as fast as it can. + pub unlocked_framerate: Option, + /// Height of the video feed. Must be a multiple of 4. + pub video_res_height: Option, + /// Width of the video feed. Must be a multiple of 4. + pub video_res_width: Option, +} + +/// An active session with the KittyCAD Modeling API. +/// TODO: This needs some sort of buffering. It should allow users to send many requests in a row and then wait for the responses. +pub struct Session { + write_to_ws: SplitSink, WsMsg>, + read_from_ws: SplitStream>, +} + +impl Session { + /// Start a session. + pub async fn start( + SessionBuilder { + client, + fps, + unlocked_framerate, + video_res_height, + video_res_width, + }: SessionBuilder, + ) -> Result { + // TODO: establish WebRTC connections for the user. + let webrtc = Some(false); + let ws = client + .modeling() + .commands_ws(fps, unlocked_framerate, video_res_height, video_res_width, webrtc) + .await?; + // Now that we have a WebSocket connection, we can split it into two ends: + // one for writing to and one for reading from. + let (write_to_ws, read_from_ws) = tokio_tungstenite::WebSocketStream::from_raw_socket( + ws, + tokio_tungstenite::tungstenite::protocol::Role::Client, + None, + ) + .await + .split(); + Ok(Self { + write_to_ws, + read_from_ws, + }) + } + + /// Send a modeling command and wait for its response. + pub async fn run_command<'de, Cmd>(&mut self, cmd: Cmd) -> Result + where + Cmd: kittycad_modeling_cmds::ModelingCmdVariant<'de>, + { + // All messages to the KittyCAD Modeling API will be sent over the WebSocket as Text. + // The text will contain JSON representing a `ModelingCmdReq`. + // This takes in a command and its ID, and makes a WebSocket message containing that command. + let cmd_id = kittycad_modeling_cmds::id::ModelingCmdId(Uuid::new_v4()); + let cmd = ModelingCmd::from(cmd); + let ws_msg = WsMsg::Text( + serde_json::to_string(&WebSocketRequest::ModelingCmdReq(ModelingCmdReq { cmd, cmd_id })).unwrap(), + ); + self.write_to_ws + .send(ws_msg) + .await + .map_err(RunCommandError::WebSocketSend)?; + while let Some(msg) = self.read_from_ws.next().await { + // We're looking for a WebSocket response with text. + // Ignore any other type of WebSocket messages. + let Some(resp) = text_from_ws(msg.map_err(RunCommandError::WebSocketRecv)?) else { + continue; + }; + // What did the WebSocket response contain? + // It should either match the KittyCAD successful response schema, or the failed response schema. + match decode_websocket_text(&resp, cmd_id.into())? { + // Success! + Ok(OkWebSocketResponseData::Modeling { modeling_response }) => { + return Ok(modeling_response); + } + // Success, but not a modeling response + Ok(_) => {} + // Failure + Err(e) => { + return Err(RunCommandError::ModelingApiFailure { + request_id: e.request_id, + errors: e.errors, + }) + } + } + } + Err(RunCommandError::WebSocketClosed) + } +} + +/// Given the text from a WebSocket, deserialize its JSON. +/// Returns OK if the WebSocket's JSON represents a successful response. +/// Returns an error if the WebSocket's JSON represented a failure response. +fn decode_websocket_text( + text: &str, + request_id: Uuid, +) -> Result, RunCommandError> { + let resp: WebSocketResponse = serde_json::from_str(text)?; + match resp { + WebSocketResponse::Success(s) => { + if s.request_id == Some(request_id) { + assert!(s.success); + Ok(Ok(s.resp)) + } else { + Err(RunCommandError::WrongId) + } + } + WebSocketResponse::Failure(f) => { + assert!(!f.success); + Ok(Err(f)) + } + } +} + +/// Find the text in a WebSocket message, if there's any. +fn text_from_ws(msg: WsMsg) -> Option { + match msg { + WsMsg::Text(text) => Some(text), + _ => None, + } +} + +/// Errors from running a modeling command. +#[derive(thiserror::Error, Debug)] +pub enum RunCommandError { + /// Error from the KittyCAD API client. + #[error("error from KittyCAD API client: {0}")] + ApiError(#[from] ApiError), + /// Request body could not be serialized. + #[error("the given body couldn't be serialized: {0}")] + InvalidRequestBody(#[from] serde_json::Error), + /// Could not send message via WebSocket. + #[error("could not send via WebSocket: {0}")] + WebSocketSend(tokio_tungstenite::tungstenite::Error), + /// Could not receive message via WebSocket. + #[error("could not receive via WebSocket: {0}")] + WebSocketRecv(tokio_tungstenite::tungstenite::Error), + /// Modeling API request failed. + #[error("modeling API returned an error on request {request_id:?}: {errors:?}")] + ModelingApiFailure { + /// ID of the failed request. + request_id: Option, + /// Errors that caused the request to fail. + errors: Vec, + }, + /// WebSocket closed unexpectedly. + #[error("WebSocket closed unexpectedly")] + WebSocketClosed, + /// Received a response for an unexpected request ID. + #[error("Received a response for an unexpected request ID")] + WrongId, +} From ccfa3b9245f16a4d6446fa258a6076e8d17346d4 Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Wed, 13 Dec 2023 15:33:35 -0600 Subject: [PATCH 2/4] Better design: now you can send a lot of commands and await their response later. --- Cargo.lock | 319 +++++++++++++++++++++++++- modeling-cmds/src/websocket.rs | 8 + modeling-session/Cargo.toml | 6 + modeling-session/examples/cube_png.rs | 152 ++++++++++++ modeling-session/src/actor.rs | 113 +++++++++ modeling-session/src/lib.rs | 124 ++++------ 6 files changed, 639 insertions(+), 83 deletions(-) create mode 100644 modeling-session/examples/cube_png.rs create mode 100644 modeling-session/src/actor.rs diff --git a/Cargo.lock b/Cargo.lock index da057a70..aa341bfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,6 +218,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -254,6 +260,12 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + [[package]] name = "byteorder" version = "1.5.0" @@ -342,6 +354,39 @@ dependencies = [ "inout", ] +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colored" version = "2.1.0" @@ -398,6 +443,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -408,15 +462,44 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset 0.9.0", +] + [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -719,12 +802,47 @@ dependencies = [ "cgmath", ] +[[package]] +name = "exr" +version = "1.71.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "eyre" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fdeflate" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868" +dependencies = [ + "simd-adler32", +] + [[package]] name = "ff" version = "0.13.0" @@ -741,6 +859,25 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -913,6 +1050,16 @@ dependencies = [ "polyval", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.28.1" @@ -949,6 +1096,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1126,6 +1282,31 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits 0.2.17", + "png", + "qoi", + "tiff", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.9.3" @@ -1223,6 +1404,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.66" @@ -1335,12 +1525,15 @@ dependencies = [ name = "kittycad-modeling-session" version = "0.1.0" dependencies = [ + "color-eyre", "futures", + "image", "kittycad", "kittycad-modeling-cmds 0.1.7", "reqwest", "serde_json", "thiserror", + "tokio", "tokio-tungstenite", "uuid", ] @@ -1374,6 +1567,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.151" @@ -1475,6 +1674,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -1504,6 +1712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -1560,7 +1769,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.7.1", "pin-utils", ] @@ -1595,6 +1804,17 @@ dependencies = [ "num-traits 0.2.17", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits 0.2.17", +] + [[package]] name = "num-traits" version = "0.1.43" @@ -1722,6 +1942,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "p256" version = "0.13.2" @@ -1920,6 +2146,19 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" +[[package]] +name = "png" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polyval" version = "0.6.1" @@ -1972,6 +2211,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quick-xml" version = "0.28.2" @@ -2059,6 +2307,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "rcgen" version = "0.11.3" @@ -2661,6 +2929,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.9" @@ -2722,6 +2996,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spki" @@ -2919,6 +3196,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.30" @@ -3077,6 +3365,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + [[package]] name = "tracing-log" version = "0.1.4" @@ -3599,6 +3897,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "winapi" version = "0.3.9" @@ -3845,3 +4149,12 @@ dependencies = [ "quote", "syn 2.0.40", ] + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/modeling-cmds/src/websocket.rs b/modeling-cmds/src/websocket.rs index 2caa7fba..f300d9b8 100644 --- a/modeling-cmds/src/websocket.rs +++ b/modeling-cmds/src/websocket.rs @@ -218,6 +218,14 @@ impl WebSocketResponse { pub fn is_failure(&self) -> bool { matches!(self, Self::Failure(_)) } + + /// Get the ID of whichever request this response is for. + pub fn request_id(&self) -> Option { + match self { + WebSocketResponse::Success(x) => x.request_id, + WebSocketResponse::Failure(x) => x.request_id, + } + } } /// A raw file with unencoded contents to be passed over binary websockets. diff --git a/modeling-session/Cargo.toml b/modeling-session/Cargo.toml index 559ee4ad..e104e9d3 100644 --- a/modeling-session/Cargo.toml +++ b/modeling-session/Cargo.toml @@ -19,6 +19,12 @@ serde_json = "1.0.108" thiserror = "1.0.50" tokio-tungstenite = "0.21.0" uuid = { version = "1.6.1", features = ["v4"] } +tokio = { version = "1", features = ["sync"] } + +[dev-dependencies] +color-eyre = "0.6" +image = "0.24.7" +tokio = { version = "1", features = ["rt", "macros"] } [lints] workspace = true diff --git a/modeling-session/examples/cube_png.rs b/modeling-session/examples/cube_png.rs new file mode 100644 index 00000000..932310c3 --- /dev/null +++ b/modeling-session/examples/cube_png.rs @@ -0,0 +1,152 @@ +//! Use the KittyCAD modeling API to draw a cube and save it to a PNG. +use std::{env, io::Cursor, time::Duration}; + +use color_eyre::{ + eyre::{bail, Context, Error}, + Result, +}; +use futures::{ + stream::{SplitSink, SplitStream}, + SinkExt, StreamExt, +}; +use kittycad::types::{ + FailureWebSocketResponse, ModelingCmd, OkModelingCmdResponse, OkWebSocketResponseData, PathSegment, Point3D, + SuccessWebSocketResponse, WebSocketRequest, +}; +use kittycad_modeling_session::{Session, SessionBuilder}; +use reqwest::Upgraded; +use tokio::time::timeout; +use tokio_tungstenite::{tungstenite::Message as WsMsg, WebSocketStream}; +use uuid::Uuid; + +#[tokio::main] +async fn main() -> Result<()> { + // Set up the API client. + let kittycad_api_token = env::var("KITTYCAD_API_TOKEN").context("You must set $KITTYCAD_API_TOKEN")?; + let kittycad_api_client = kittycad::Client::new(kittycad_api_token); + + // Where should the final PNG be saved? + let img_output_path = env::var("IMAGE_OUTPUT_PATH").unwrap_or_else(|_| "model.png".to_owned()); + + let session_builder = SessionBuilder { + client: kittycad_api_client, + fps: Some(10), + unlocked_framerate: Some(false), + video_res_height: Some(720), + video_res_width: Some(1280), + }; + let session = Session::start(session_builder).await?; + + // First, send all commands to the API, to draw a cube. + // Then, read all responses from the API, to download the cube as a PNG. + // draw_cube(write, 10.0).await?; + // export_png(read, img_output_path).await + Ok(()) +} + +/// Send modeling commands to the KittyCAD API. +/// We're going to draw a cube and export it as a PNG. +async fn draw_cube(mut write_to_ws: SplitSink, WsMsg>, width: f64) -> Result<()> { + // All messages to the KittyCAD Modeling API will be sent over the WebSocket as Text. + // The text will contain JSON representing a `ModelingCmdReq`. + // This takes in a command and its ID, and makes a WebSocket message containing that command. + fn to_msg(cmd: ModelingCmd, cmd_id: Uuid) -> WsMsg { + WsMsg::Text(serde_json::to_string(&WebSocketRequest::ModelingCmdReq { cmd, cmd_id }).unwrap()) + } + + // Now the WebSocket is set up and ready to use! + // We can start sending commands. + + // Create a new empty path. + let path_id = Uuid::new_v4(); + write_to_ws.send(to_msg(ModelingCmd::StartPath {}, path_id)).await?; + + // Add four lines to the path, + // in the shape of a square. + // First, start the path at the first corner. + let start = Point3D { + x: -width, + y: -width, + z: -width, + }; + write_to_ws + .send(to_msg( + ModelingCmd::MovePathPen { + path: path_id, + to: start.clone(), + }, + Uuid::new_v4(), + )) + .await?; + + // Now extend the path to each corner, and back to the start. + let points = [ + Point3D { + x: width, + y: -width, + z: -width, + }, + Point3D { + x: width, + y: width, + z: -width, + }, + Point3D { + x: -width, + y: width, + z: -width, + }, + start, + ]; + for point in points { + write_to_ws + .send(to_msg( + ModelingCmd::ExtendPath { + path: path_id, + segment: PathSegment::Line { + end: point, + relative: false, + }, + }, + Uuid::new_v4(), + )) + .await?; + } + + // Extrude the square into a cube. + write_to_ws + .send(to_msg(ModelingCmd::ClosePath { path_id }, Uuid::new_v4())) + .await?; + write_to_ws + .send(to_msg( + ModelingCmd::Extrude { + cap: true, + distance: width * 2.0, + target: path_id, + }, + Uuid::new_v4(), + )) + .await?; + + // Export the model as a PNG. + write_to_ws + .send(to_msg( + ModelingCmd::TakeSnapshot { + format: kittycad::types::ImageFormat::Png, + }, + Uuid::new_v4(), + )) + .await?; + + // Finish sending + drop(write_to_ws); + Ok(()) +} + +fn save_image(contents: Vec, output_path: &str) -> Result<()> { + let mut img = image::io::Reader::new(Cursor::new(contents)); + img.set_format(image::ImageFormat::Png); + let img = img.decode()?; + img.save(output_path)?; + Ok(()) +} diff --git a/modeling-session/src/actor.rs b/modeling-session/src/actor.rs new file mode 100644 index 00000000..ddcba841 --- /dev/null +++ b/modeling-session/src/actor.rs @@ -0,0 +1,113 @@ +use std::{collections::HashMap, time::Duration}; + +use futures::{ + stream::{SplitSink, SplitStream}, + SinkExt, StreamExt, +}; +use kittycad_modeling_cmds::{ + id::ModelingCmdId, + ok_response::OkModelingCmdResponse, + websocket::{ModelingCmdReq, OkWebSocketResponseData, WebSocketRequest, WebSocketResponse}, +}; +use reqwest::Upgraded; +use tokio::{ + sync::{mpsc, oneshot}, + time::Instant, +}; +use tokio_tungstenite::{tungstenite::Message as WsMsg, WebSocketStream}; + +use crate::RunCommandError; + +type Result = std::result::Result; + +pub enum Request { + SendModelingCmd(ModelingCmdReq, oneshot::Sender>), + GetResponse(ModelingCmdId, oneshot::Sender>), +} + +pub async fn start( + mut incoming: mpsc::Receiver, + mut write_to_ws: SplitSink, WsMsg>, + mut read_from_ws: SplitStream>, + timeout: Duration, +) { + let mut responses: HashMap = HashMap::new(); + 'next_request: while let Some(req) = incoming.recv().await { + match req { + Request::SendModelingCmd(cmd, responder) => { + let ws_msg = WsMsg::Text(serde_json::to_string(&WebSocketRequest::ModelingCmdReq(cmd)).unwrap()); + let resp = write_to_ws.send(ws_msg).await.map_err(RunCommandError::WebSocketSend); + let _ = responder.send(resp); + } + Request::GetResponse(cmd_id, responder) => { + let start = Instant::now(); + while start.elapsed() < timeout { + // Check the response map. + // If we've already got the response for this ID, then send it back to the user! + if let Some(resp) = responses.remove(&cmd_id) { + let send_this_to_user = match resp { + WebSocketResponse::Success(s) => { + let resp = s.resp; + match resp { + OkWebSocketResponseData::Modeling { modeling_response } => Ok(modeling_response), + _ => { + // This request ID should be for a modeling request. Something's gone very wrong. + Err(RunCommandError::ServerSentWrongType) + } + } + } + WebSocketResponse::Failure(e) => Err(RunCommandError::ModelingApiFailure { + request_id: Some(cmd_id.into()), + errors: e.errors, + }), + }; + let _ = responder.send(send_this_to_user); + // Finished this request! Actor is ready for the next request. + continue 'next_request; + } + // If not, get a response from the WebSocket. + // If we can't get any response, the WebSocket must have been closed. + let Some(msg) = read_from_ws.next().await else { + let _ = responder.send(Err(RunCommandError::WebSocketClosed)); + // Probably no point getting another request, but may as well try. + continue 'next_request; + }; + // Couldn't read from WebSocket? Try again. + let Ok(msg) = msg else { + continue; + }; + // WebSocket response wasn't text? Try again. + let Some(resp_text) = text_from_ws(msg) else { + continue; + }; + // Couldn't decode the response? Try again. + let Ok(resp) = decode_websocket_text(&resp_text) else { + continue; + }; + if let Some(id) = resp.request_id() { + responses.insert(id.into(), resp); + } else { + continue; + } + } + let _ = responder.send(Err(RunCommandError::TimeOutWaitingForResponse)); + } + } + } +} + +/// Given the text from a WebSocket, deserialize its JSON. +/// Returns OK if the WebSocket's JSON represents a successful response. +/// Returns an error if the WebSocket's JSON represented a failure response. +fn decode_websocket_text(text: &str) -> Result { + let resp = serde_json::from_str(text)?; + Ok(resp) +} + +/// Find the text in a WebSocket message, if there's any. +fn text_from_ws(msg: WsMsg) -> Option { + match msg { + WsMsg::Text(text) => Some(text), + _ => None, + } +} diff --git a/modeling-session/src/lib.rs b/modeling-session/src/lib.rs index d59a07a5..38e0508c 100644 --- a/modeling-session/src/lib.rs +++ b/modeling-session/src/lib.rs @@ -1,21 +1,17 @@ //! Establish a modeling session with the KittyCAD API. -use futures::{ - stream::{SplitSink, SplitStream}, - SinkExt, StreamExt, -}; +use std::time::Duration; + +use futures::StreamExt; use kittycad::{types::error::Error as ApiError, Client}; use kittycad_modeling_cmds::{ - ok_response::OkModelingCmdResponse, - websocket::{ - FailureWebSocketResponse, ModelingCmdReq, OkWebSocketResponseData, WebSocketRequest, WebSocketResponse, - }, - ModelingCmd, + id::ModelingCmdId, ok_response::OkModelingCmdResponse, websocket::ModelingCmdReq, ModelingCmd, }; -use reqwest::Upgraded; -use tokio_tungstenite::{tungstenite::Message as WsMsg, WebSocketStream}; +use tokio::sync::{mpsc, oneshot}; use uuid::Uuid; +mod actor; + /// Parameters for starting a session with the KittyCAD Modeling API. pub struct SessionBuilder { /// Client to the KittyCAD API. @@ -28,13 +24,17 @@ pub struct SessionBuilder { pub video_res_height: Option, /// Width of the video feed. Must be a multiple of 4. pub video_res_width: Option, + /// How many requests for sending/receiving to/from the API can be in-flight at once. + pub buffer_reqs: Option, + /// How long to wait for the response to a modeling command. + /// Defaults to 10 seconds. + pub await_response_timeout: Option, } /// An active session with the KittyCAD Modeling API. /// TODO: This needs some sort of buffering. It should allow users to send many requests in a row and then wait for the responses. pub struct Session { - write_to_ws: SplitSink, WsMsg>, - read_from_ws: SplitStream>, + actor_tx: mpsc::Sender, } impl Session { @@ -46,6 +46,8 @@ impl Session { unlocked_framerate, video_res_height, video_res_width, + buffer_reqs, + await_response_timeout, }: SessionBuilder, ) -> Result { // TODO: establish WebRTC connections for the user. @@ -63,86 +65,42 @@ impl Session { ) .await .split(); - Ok(Self { + let (actor_tx, actor_rx) = mpsc::channel(buffer_reqs.unwrap_or(10)); + tokio::task::spawn(actor::start( + actor_rx, write_to_ws, read_from_ws, - }) + await_response_timeout.unwrap_or(Duration::from_secs(10)), + )); + Ok(Self { actor_tx }) } /// Send a modeling command and wait for its response. - pub async fn run_command<'de, Cmd>(&mut self, cmd: Cmd) -> Result + pub async fn run_command<'de, Cmd>( + &mut self, + cmd_id: ModelingCmdId, + cmd: Cmd, + ) -> Result where Cmd: kittycad_modeling_cmds::ModelingCmdVariant<'de>, { // All messages to the KittyCAD Modeling API will be sent over the WebSocket as Text. // The text will contain JSON representing a `ModelingCmdReq`. // This takes in a command and its ID, and makes a WebSocket message containing that command. - let cmd_id = kittycad_modeling_cmds::id::ModelingCmdId(Uuid::new_v4()); let cmd = ModelingCmd::from(cmd); - let ws_msg = WsMsg::Text( - serde_json::to_string(&WebSocketRequest::ModelingCmdReq(ModelingCmdReq { cmd, cmd_id })).unwrap(), - ); - self.write_to_ws - .send(ws_msg) + let (tx, rx) = oneshot::channel(); + self.actor_tx + .send(actor::Request::SendModelingCmd(ModelingCmdReq { cmd, cmd_id }, tx)) .await - .map_err(RunCommandError::WebSocketSend)?; - while let Some(msg) = self.read_from_ws.next().await { - // We're looking for a WebSocket response with text. - // Ignore any other type of WebSocket messages. - let Some(resp) = text_from_ws(msg.map_err(RunCommandError::WebSocketRecv)?) else { - continue; - }; - // What did the WebSocket response contain? - // It should either match the KittyCAD successful response schema, or the failed response schema. - match decode_websocket_text(&resp, cmd_id.into())? { - // Success! - Ok(OkWebSocketResponseData::Modeling { modeling_response }) => { - return Ok(modeling_response); - } - // Success, but not a modeling response - Ok(_) => {} - // Failure - Err(e) => { - return Err(RunCommandError::ModelingApiFailure { - request_id: e.request_id, - errors: e.errors, - }) - } - } - } - Err(RunCommandError::WebSocketClosed) - } -} - -/// Given the text from a WebSocket, deserialize its JSON. -/// Returns OK if the WebSocket's JSON represents a successful response. -/// Returns an error if the WebSocket's JSON represented a failure response. -fn decode_websocket_text( - text: &str, - request_id: Uuid, -) -> Result, RunCommandError> { - let resp: WebSocketResponse = serde_json::from_str(text)?; - match resp { - WebSocketResponse::Success(s) => { - if s.request_id == Some(request_id) { - assert!(s.success); - Ok(Ok(s.resp)) - } else { - Err(RunCommandError::WrongId) - } - } - WebSocketResponse::Failure(f) => { - assert!(!f.success); - Ok(Err(f)) - } - } -} - -/// Find the text in a WebSocket message, if there's any. -fn text_from_ws(msg: WsMsg) -> Option { - match msg { - WsMsg::Text(text) => Some(text), - _ => None, + .expect("Actor should never terminate"); + rx.await.expect("Actor should never terminate")?; + let (tx, rx) = oneshot::channel(); + self.actor_tx + .send(actor::Request::GetResponse(cmd_id, tx)) + .await + .expect("Actor should never terminate"); + let resp = rx.await.expect("Actor should never terminate")?; + Ok(resp) } } @@ -175,4 +133,10 @@ pub enum RunCommandError { /// Received a response for an unexpected request ID. #[error("Received a response for an unexpected request ID")] WrongId, + /// Timed out waiting for a response. + #[error("Timed out waiting for a response")] + TimeOutWaitingForResponse, + /// Server returned the wrong type. + #[error("Server returned the wrong type")] + ServerSentWrongType, } From 09815298b7ff7c4092664cd0d409e0acf44507a4 Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Wed, 13 Dec 2023 15:57:28 -0600 Subject: [PATCH 3/4] Working example --- Cargo.toml | 2 +- execution-plan/Cargo.toml | 1 - modeling-cmds/src/def_enum.rs | 2 +- modeling-cmds/src/each_cmd.rs | 4 + modeling-cmds/src/impl_traits.rs | 6 + modeling-session/Cargo.toml | 2 +- modeling-session/examples/cube_png.rs | 176 ++++++++++++-------------- unit-conversion-derive/Cargo.toml | 6 +- 8 files changed, 97 insertions(+), 102 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 17a08aab..412604d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] resolver = "2" members = [ - "modeling-cmds", "execution-plan", + "modeling-cmds", "modeling-session", "unit-conversion-derive", ] diff --git a/execution-plan/Cargo.toml b/execution-plan/Cargo.toml index 38b1f456..357e56ba 100644 --- a/execution-plan/Cargo.toml +++ b/execution-plan/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" repository = "https://github.com/KittyCAD/execution-plan" rust-version = "1.73" description = "A DSL for composing KittyCAD API queries" - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/modeling-cmds/src/def_enum.rs b/modeling-cmds/src/def_enum.rs index b920066d..3b38b073 100644 --- a/modeling-cmds/src/def_enum.rs +++ b/modeling-cmds/src/def_enum.rs @@ -8,7 +8,7 @@ pub use crate::each_cmd::*; #[serde(rename_all = "snake_case", tag = "type")] pub enum ModelingCmd { /// Start a path. - StartPath, + StartPath(StartPath), /// Move the path's "pen". MovePathPen(MovePathPen), /// Extend a path by adding a new segment which starts at the path's "pen". diff --git a/modeling-cmds/src/each_cmd.rs b/modeling-cmds/src/each_cmd.rs index a3c99f79..a413c368 100644 --- a/modeling-cmds/src/each_cmd.rs +++ b/modeling-cmds/src/each_cmd.rs @@ -16,6 +16,10 @@ use crate::{ units, }; +/// Start a new path. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct StartPath; + /// Move the path's "pen". #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct MovePathPen { diff --git a/modeling-cmds/src/impl_traits.rs b/modeling-cmds/src/impl_traits.rs index 3723b73c..92ba8276 100644 --- a/modeling-cmds/src/impl_traits.rs +++ b/modeling-cmds/src/impl_traits.rs @@ -1,5 +1,11 @@ use crate::{each_cmd::*, output as out, ModelingCmd, ModelingCmdVariant}; +impl<'de> ModelingCmdVariant<'de> for StartPath { + type Output = (); + fn into_enum(self) -> ModelingCmd { + ModelingCmd::StartPath(self) + } +} impl<'de> ModelingCmdVariant<'de> for MovePathPen { type Output = (); fn into_enum(self) -> ModelingCmd { diff --git a/modeling-session/Cargo.toml b/modeling-session/Cargo.toml index e104e9d3..2d7c4db5 100644 --- a/modeling-session/Cargo.toml +++ b/modeling-session/Cargo.toml @@ -17,9 +17,9 @@ kittycad-modeling-cmds = { path = "../modeling-cmds", features = ["websocket"] } reqwest = "0.11.22" serde_json = "1.0.108" thiserror = "1.0.50" +tokio = { version = "1", features = ["sync"] } tokio-tungstenite = "0.21.0" uuid = { version = "1.6.1", features = ["v4"] } -tokio = { version = "1", features = ["sync"] } [dev-dependencies] color-eyre = "0.6" diff --git a/modeling-session/examples/cube_png.rs b/modeling-session/examples/cube_png.rs index 932310c3..2be0088a 100644 --- a/modeling-session/examples/cube_png.rs +++ b/modeling-session/examples/cube_png.rs @@ -1,24 +1,21 @@ //! Use the KittyCAD modeling API to draw a cube and save it to a PNG. -use std::{env, io::Cursor, time::Duration}; +use std::{env, io::Cursor}; use color_eyre::{ - eyre::{bail, Context, Error}, + eyre::{bail, Context}, Result, }; -use futures::{ - stream::{SplitSink, SplitStream}, - SinkExt, StreamExt, -}; -use kittycad::types::{ - FailureWebSocketResponse, ModelingCmd, OkModelingCmdResponse, OkWebSocketResponseData, PathSegment, Point3D, - SuccessWebSocketResponse, WebSocketRequest, +use kittycad_modeling_cmds::{ + id::ModelingCmdId, + ok_response::OkModelingCmdResponse, + shared::{PathSegment, Point3d}, + ClosePath, ExtendPath, Extrude, MovePathPen, StartPath, TakeSnapshot, }; use kittycad_modeling_session::{Session, SessionBuilder}; -use reqwest::Upgraded; -use tokio::time::timeout; -use tokio_tungstenite::{tungstenite::Message as WsMsg, WebSocketStream}; use uuid::Uuid; +const CUBE_WIDTH: f64 = 10.0; + #[tokio::main] async fn main() -> Result<()> { // Set up the API client. @@ -34,119 +31,108 @@ async fn main() -> Result<()> { unlocked_framerate: Some(false), video_res_height: Some(720), video_res_width: Some(1280), + buffer_reqs: None, + await_response_timeout: None, }; - let session = Session::start(session_builder).await?; - - // First, send all commands to the API, to draw a cube. - // Then, read all responses from the API, to download the cube as a PNG. - // draw_cube(write, 10.0).await?; - // export_png(read, img_output_path).await - Ok(()) -} - -/// Send modeling commands to the KittyCAD API. -/// We're going to draw a cube and export it as a PNG. -async fn draw_cube(mut write_to_ws: SplitSink, WsMsg>, width: f64) -> Result<()> { - // All messages to the KittyCAD Modeling API will be sent over the WebSocket as Text. - // The text will contain JSON representing a `ModelingCmdReq`. - // This takes in a command and its ID, and makes a WebSocket message containing that command. - fn to_msg(cmd: ModelingCmd, cmd_id: Uuid) -> WsMsg { - WsMsg::Text(serde_json::to_string(&WebSocketRequest::ModelingCmdReq { cmd, cmd_id }).unwrap()) - } - - // Now the WebSocket is set up and ready to use! - // We can start sending commands. + let mut session = Session::start(session_builder) + .await + .context("could not establish session")?; // Create a new empty path. let path_id = Uuid::new_v4(); - write_to_ws.send(to_msg(ModelingCmd::StartPath {}, path_id)).await?; + let path = path_id.into(); + session + .run_command(path, StartPath {}) + .await + .context("could not create path")?; // Add four lines to the path, // in the shape of a square. // First, start the path at the first corner. - let start = Point3D { - x: -width, - y: -width, - z: -width, + let start = Point3d { + x: -CUBE_WIDTH, + y: -CUBE_WIDTH, + z: -CUBE_WIDTH, }; - write_to_ws - .send(to_msg( - ModelingCmd::MovePathPen { - path: path_id, - to: start.clone(), - }, - Uuid::new_v4(), - )) - .await?; + session + .run_command(random_id(), MovePathPen { path, to: start }) + .await + .context("could not move path pen to start")?; // Now extend the path to each corner, and back to the start. let points = [ - Point3D { - x: width, - y: -width, - z: -width, + Point3d { + x: CUBE_WIDTH, + y: -CUBE_WIDTH, + z: -CUBE_WIDTH, }, - Point3D { - x: width, - y: width, - z: -width, + Point3d { + x: CUBE_WIDTH, + y: CUBE_WIDTH, + z: -CUBE_WIDTH, }, - Point3D { - x: -width, - y: width, - z: -width, + Point3d { + x: -CUBE_WIDTH, + y: CUBE_WIDTH, + z: -CUBE_WIDTH, }, start, ]; for point in points { - write_to_ws - .send(to_msg( - ModelingCmd::ExtendPath { - path: path_id, + session + .run_command( + random_id(), + ExtendPath { + path, segment: PathSegment::Line { end: point, relative: false, }, }, - Uuid::new_v4(), - )) - .await?; + ) + .await + .context("could not draw square")?; } - // Extrude the square into a cube. - write_to_ws - .send(to_msg(ModelingCmd::ClosePath { path_id }, Uuid::new_v4())) - .await?; - write_to_ws - .send(to_msg( - ModelingCmd::Extrude { + session + .run_command(random_id(), ClosePath { path_id }) + .await + .context("could not close square path")?; + session + .run_command( + random_id(), + Extrude { cap: true, - distance: width * 2.0, - target: path_id, + distance: CUBE_WIDTH * 2.0, + target: path, }, - Uuid::new_v4(), - )) - .await?; - - // Export the model as a PNG. - write_to_ws - .send(to_msg( - ModelingCmd::TakeSnapshot { - format: kittycad::types::ImageFormat::Png, + ) + .await + .context("could not extrude square into cube")?; + // Export model as a PNG. + let snapshot_resp = session + .run_command( + random_id(), + TakeSnapshot { + format: kittycad_modeling_cmds::ImageFormat::Png, }, - Uuid::new_v4(), - )) - .await?; + ) + .await + .context("could not get PNG snapshot")?; - // Finish sending - drop(write_to_ws); + // Save the PNG to disk. + match snapshot_resp { + OkModelingCmdResponse::TakeSnapshot(snap) => { + let mut img = image::io::Reader::new(Cursor::new(snap.contents)); + img.set_format(image::ImageFormat::Png); + let img = img.decode().context("could not decode PNG bytes")?; + img.save(img_output_path).context("could not save PNG to disk")?; + } + other => bail!("Unexpected response: {other:?}"), + }; Ok(()) } -fn save_image(contents: Vec, output_path: &str) -> Result<()> { - let mut img = image::io::Reader::new(Cursor::new(contents)); - img.set_format(image::ImageFormat::Png); - let img = img.decode()?; - img.save(output_path)?; - Ok(()) +fn random_id() -> ModelingCmdId { + Uuid::new_v4().into() } diff --git a/unit-conversion-derive/Cargo.toml b/unit-conversion-derive/Cargo.toml index 50769e1a..73543600 100644 --- a/unit-conversion-derive/Cargo.toml +++ b/unit-conversion-derive/Cargo.toml @@ -12,10 +12,10 @@ license = "MIT" proc-macro = true [dependencies] -syn = { version = "1.0.103", features = ["derive", "fold"] } -quote = "1.0.28" -proc-macro2 = "1.0.60" inflections = "1" +proc-macro2 = "1.0.60" +quote = "1.0.28" +syn = { version = "1.0.103", features = ["derive", "fold"] } [dev-dependencies] pretty_assertions = "1" From 555cce9f5eb7d33f32618707b67928dae289469a Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Wed, 13 Dec 2023 16:41:19 -0600 Subject: [PATCH 4/4] CI for cargo sort --- .github/workflows/ci.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e00c8c7..d9b440e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,6 +95,17 @@ jobs: # Edit this file to tweak the typo list and other configuration. run: codespell --config .codespellrc + cargo-toml-sorted: + name: Check Cargo.toml is sorted + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install cargo-sort + run: cargo install cargo-sort + - name: Run check + run: cargo sort --workspace --check + semver-checks: runs-on: ubuntu-latest steps: