diff --git a/Cargo.lock b/Cargo.lock index 66a02a0..b231dc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,302 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-cors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more", + "futures-util", + "log", + "once_cell", + "smallvec", +] + +[[package]] +name = "actix-governor" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2e7b88f3804e01bd4191fdb08650430bbfcb43d3d9b2890064df3551ec7d25b" +dependencies = [ + "actix-http", + "actix-web", + "futures", + "governor", +] + +[[package]] +name = "actix-http" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae682f693a9cd7b058f2b0b5d9a6d7728a8555779bedbbc35dd88528611d020" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64 0.22.1", + "bitflags 2.6.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2 0.3.26", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.72", +] + +[[package]] +name = "actix-multipart" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5118a26dee7e34e894f7e85aa0ee5080ae4c18bf03c0e30d49a80e418f00a53" +dependencies = [ + "actix-multipart-derive", + "actix-utils", + "actix-web", + "derive_more", + "futures-core", + "futures-util", + "httparse", + "local-waker", + "log", + "memchr", + "mime", + "rand", + "serde", + "serde_json", + "serde_plain", + "tempfile", + "tokio", +] + +[[package]] +name = "actix-multipart-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11eb847f49a700678ea2fa73daeb3208061afa2b9d1a8527c03390f4c4a1c6b" +dependencies = [ + "darling", + "parse-size", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b02303ce8d4e8be5b855af6cf3c3a08f3eff26880faad82bab679c22d3650cb5" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio 0.8.11", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1988c02af8d2b718c05bc4aeb6a66395b7cdf32858c2c71131e5637a8c05a9ff" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "actix-web-lab" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7675c1a84eec1b179c844cdea8488e3e409d8e4984026e92fa96c87dd86f33c6" +dependencies = [ + "actix-http", + "actix-router", + "actix-service", + "actix-utils", + "actix-web", + "actix-web-lab-derive", + "ahash", + "arc-swap", + "async-trait", + "bytes", + "bytestring", + "csv", + "derive_more", + "futures-core", + "futures-util", + "http 0.2.12", + "impl-more", + "itertools", + "local-channel", + "mediatype", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_html_form", + "serde_json", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "actix-web-lab-derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa0b287c8de4a76b691f29dbb5451e8dd5b79d777eaf87350c9b0cbfdb5e968" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "addr2line" version = "0.22.0" @@ -35,6 +331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -49,6 +346,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.18" @@ -287,6 +599,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -305,6 +623,15 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bitpacking" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c1d3e2bfd8d06048a179f7b17afc3188effa10385e7b00dc65af6aae732ea92" +dependencies = [ + "crunchy", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -336,6 +663,27 @@ dependencies = [ "piper", ] +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bstr" version = "1.10.0" @@ -371,6 +719,15 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +[[package]] +name = "bytestring" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" +dependencies = [ + "bytes", +] + [[package]] name = "bzip2" version = "0.4.4" @@ -411,6 +768,12 @@ dependencies = [ "libc", ] +[[package]] +name = "census" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0" + [[package]] name = "cfg-if" version = "1.0.0" @@ -544,6 +907,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -593,6 +967,34 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -624,6 +1026,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -634,6 +1042,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "darling" version = "0.20.10" @@ -669,6 +1098,19 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "dbus" version = "0.9.7" @@ -698,6 +1140,16 @@ dependencies = [ "sha2", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + [[package]] name = "deflate64" version = "0.1.9" @@ -781,6 +1233,18 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dunce" version = "1.0.4" @@ -793,6 +1257,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -885,6 +1355,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fastdivide" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59668941c55e5c186b8b58c391629af56774ec768f73c08bbcd56f09348eb00b" + [[package]] name = "faster-hex" version = "0.9.0" @@ -909,6 +1385,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + [[package]] name = "flate2" version = "1.0.30" @@ -925,6 +1413,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" @@ -934,6 +1437,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs4" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e180ac76c23b45e767bd7ae9579bc0bb458618c4bc71835926e098e61d15f8" +dependencies = [ + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "full_moon" version = "1.0.0-rc.5" @@ -961,6 +1474,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -977,6 +1505,17 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.30" @@ -1019,12 +1558,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1751,7 +2297,7 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c02b83763ffe95bcc27ce5821b2b7f843315a009c06f1cd59c9b66c508c058" dependencies = [ - "base64", + "base64 0.22.1", "bstr", "gix-command", "gix-credentials", @@ -1854,6 +2400,45 @@ dependencies = [ "thiserror", ] +[[package]] +name = "governor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot", + "portable-atomic", + "quanta", + "rand", + "smallvec", + "spinning_top", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.5" @@ -1865,7 +2450,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.1.0", "indexmap 2.2.6", "slab", "tokio", @@ -1940,6 +2525,34 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "hostname" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +dependencies = [ + "cfg-if", + "libc", + "windows", +] + +[[package]] +name = "htmlescape" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.1.0" @@ -1958,7 +2571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.1.0", ] [[package]] @@ -1969,7 +2582,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http", + "http 1.1.0", "http-body", "pin-project-lite", ] @@ -1980,6 +2593,12 @@ version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "humantime" version = "2.1.0" @@ -1995,8 +2614,8 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2", - "http", + "h2 0.4.5", + "http 1.1.0", "http-body", "httparse", "itoa", @@ -2013,7 +2632,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", - "http", + "http 1.1.0", "hyper", "hyper-util", "rustls", @@ -2024,6 +2643,22 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.6" @@ -2033,7 +2668,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.1.0", "http-body", "hyper", "pin-project-lite", @@ -2083,6 +2718,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-more" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d" + [[package]] name = "indexmap" version = "1.9.3" @@ -2162,6 +2803,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -2216,6 +2860,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -2263,12 +2916,24 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "levenshtein_automata" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" + [[package]] name = "libc" version = "0.2.155" @@ -2298,6 +2963,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libredox" version = "0.1.3" @@ -2350,6 +3021,23 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.12" @@ -2372,6 +3060,21 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "lz4_flex" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" + [[package]] name = "lzma-rs" version = "0.3.0" @@ -2393,6 +3096,32 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "measure_time" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbefd235b0aadd181626f281e1d684e116972988c14c264e42069d5e8a5775cc" +dependencies = [ + "instant", + "log", +] + +[[package]] +name = "mediatype" +version = "0.19.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8878cd8d1b3c8c8ae4b2ba0a36652b7cf192f618a599a7fbdfa25cffd4ea72dd" + [[package]] name = "memchr" version = "2.7.4" @@ -2433,6 +3162,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -2466,6 +3201,29 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "murmurhash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "newline-converter" version = "0.3.0" @@ -2488,6 +3246,28 @@ dependencies = [ "memoffset", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "num" version = "0.4.3" @@ -2565,6 +3345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2607,6 +3388,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oneshot" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e296cf87e61c9cfc1a61c3c63a0f7f286ed4554e0e22be84e8a38e1d264a2a29" + [[package]] name = "open" version = "5.3.0" @@ -2618,6 +3405,32 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "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.72", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -2652,6 +3465,26 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_info" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] + +[[package]] +name = "ownedbytes" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a059efb063b8f425b948e042e6b9bd85edfe60e913630ed727b23e2dfcc558" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "parking" version = "2.2.0" @@ -2681,6 +3514,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parse-size" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae" + [[package]] name = "paste" version = "1.0.15" @@ -2747,6 +3586,37 @@ dependencies = [ "zip", ] +[[package]] +name = "pesde-registry" +version = "0.7.0" +dependencies = [ + "actix-cors", + "actix-governor", + "actix-multipart", + "actix-web", + "actix-web-lab", + "chrono", + "dotenvy", + "flate2", + "git2", + "gix", + "log", + "pesde", + "pretty_env_logger", + "reqwest", + "rusty-s3", + "semver", + "sentry", + "sentry-actix", + "sentry-log", + "serde", + "serde_json", + "tantivy", + "tar", + "thiserror", + "toml", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -2863,6 +3733,31 @@ version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" +[[package]] +name = "quanta" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quinn" version = "0.11.2" @@ -2948,6 +3843,45 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "raw-cpuid" +version = "11.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -3000,6 +3934,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.8.4" @@ -3021,24 +3961,26 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-channel", "futures-core", "futures-util", - "h2", - "http", + "h2 0.4.5", + "http 1.1.0", "http-body", "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -3052,6 +3994,7 @@ dependencies = [ "sync_wrapper", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls", "tower-service", "url", @@ -3077,6 +4020,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rust-stemmers" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" +dependencies = [ + "serde", + "serde_derive", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3131,7 +4084,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64", + "base64 0.22.1", "rustls-pki-types", ] @@ -3152,6 +4105,25 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rusty-s3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31aa883f1b986a5249641e574ca0e11ac4fb9970b009c6fbb96fedaf4fa78db8" +dependencies = [ + "base64 0.21.7", + "hmac", + "md-5", + "percent-encoding", + "quick-xml", + "serde", + "serde_json", + "sha2", + "time", + "url", + "zeroize", +] + [[package]] name = "ryu" version = "1.0.18" @@ -3167,6 +4139,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3224,6 +4205,135 @@ dependencies = [ "serde", ] +[[package]] +name = "sentry" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5484316556650182f03b43d4c746ce0e3e48074a21e2f51244b648b6542e1066" +dependencies = [ + "httpdate", + "native-tls", + "reqwest", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-debug-images", + "sentry-panic", + "sentry-tracing", + "tokio", + "ureq", +] + +[[package]] +name = "sentry-actix" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e461c7d3a46d298b5ffc66127c1f16454dd11d3d89fcfb21023cd499d82b9a78" +dependencies = [ + "actix-web", + "futures-util", + "sentry-core", +] + +[[package]] +name = "sentry-backtrace" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40aa225bb41e2ec9d7c90886834367f560efc1af028f1c5478a6cce6a59c463a" +dependencies = [ + "backtrace", + "once_cell", + "regex", + "sentry-core", +] + +[[package]] +name = "sentry-contexts" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a8dd746da3d16cb8c39751619cefd4fcdbd6df9610f3310fd646b55f6e39910" +dependencies = [ + "hostname", + "libc", + "os_info", + "rustc_version", + "sentry-core", + "uname", +] + +[[package]] +name = "sentry-core" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161283cfe8e99c8f6f236a402b9ccf726b201f365988b5bb637ebca0abbd4a30" +dependencies = [ + "once_cell", + "rand", + "sentry-types", + "serde", + "serde_json", +] + +[[package]] +name = "sentry-debug-images" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc6b25e945fcaa5e97c43faee0267eebda9f18d4b09a251775d8fef1086238a" +dependencies = [ + "findshlibs", + "once_cell", + "sentry-core", +] + +[[package]] +name = "sentry-log" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75bbcc61886955045a1dd4bdb173412a80bb2571be3c5bfcf7eb8f55a442bbf5" +dependencies = [ + "log", + "sentry-core", +] + +[[package]] +name = "sentry-panic" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74f229c7186dd971a9491ffcbe7883544aa064d1589bd30b83fb856cd22d63" +dependencies = [ + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-tracing" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c5faf2103cd01eeda779ea439b68c4ee15adcdb16600836e97feafab362ec" +dependencies = [ + "sentry-backtrace", + "sentry-core", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sentry-types" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d68cdf6bc41b8ff3ae2a9c4671e97426dcdd154cc1d4b6b72813f285d6b163f" +dependencies = [ + "debugid", + "hex", + "rand", + "serde", + "serde_json", + "thiserror", + "time", + "url", + "uuid", +] + [[package]] name = "serde" version = "1.0.204" @@ -3245,16 +4355,39 @@ dependencies = [ ] [[package]] -name = "serde_json" -version = "1.0.120" +name = "serde_html_form" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "8de514ef58196f1fc96dcaef80fe6170a1ce6215df9687a93fe8300e773fefc5" dependencies = [ + "form_urlencoded", + "indexmap 2.2.6", "itoa", "ryu", "serde", ] +[[package]] +name = "serde_json" +version = "1.0.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -3293,7 +4426,7 @@ version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ - "base64", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", @@ -3387,6 +4520,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "sketches-ddsketch" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" +dependencies = [ + "serde", +] + [[package]] name = "slab" version = "0.4.9" @@ -3427,6 +4569,21 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -3494,6 +4651,147 @@ dependencies = [ "libc", ] +[[package]] +name = "tantivy" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8d0582f186c0a6d55655d24543f15e43607299425c5ad8352c242b914b31856" +dependencies = [ + "aho-corasick", + "arc-swap", + "base64 0.22.1", + "bitpacking", + "byteorder", + "census", + "crc32fast", + "crossbeam-channel", + "downcast-rs", + "fastdivide", + "fnv", + "fs4", + "htmlescape", + "itertools", + "levenshtein_automata", + "log", + "lru", + "lz4_flex", + "measure_time", + "memmap2", + "num_cpus", + "once_cell", + "oneshot", + "rayon", + "regex", + "rust-stemmers", + "rustc-hash", + "serde", + "serde_json", + "sketches-ddsketch", + "smallvec", + "tantivy-bitpacker", + "tantivy-columnar", + "tantivy-common", + "tantivy-fst", + "tantivy-query-grammar", + "tantivy-stacker", + "tantivy-tokenizer-api", + "tempfile", + "thiserror", + "time", + "uuid", + "winapi", +] + +[[package]] +name = "tantivy-bitpacker" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284899c2325d6832203ac6ff5891b297fc5239c3dc754c5bc1977855b23c10df" +dependencies = [ + "bitpacking", +] + +[[package]] +name = "tantivy-columnar" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12722224ffbe346c7fec3275c699e508fd0d4710e629e933d5736ec524a1f44e" +dependencies = [ + "downcast-rs", + "fastdivide", + "itertools", + "serde", + "tantivy-bitpacker", + "tantivy-common", + "tantivy-sstable", + "tantivy-stacker", +] + +[[package]] +name = "tantivy-common" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8019e3cabcfd20a1380b491e13ff42f57bb38bf97c3d5fa5c07e50816e0621f4" +dependencies = [ + "async-trait", + "byteorder", + "ownedbytes", + "serde", + "time", +] + +[[package]] +name = "tantivy-fst" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18" +dependencies = [ + "byteorder", + "regex-syntax", + "utf8-ranges", +] + +[[package]] +name = "tantivy-query-grammar" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847434d4af57b32e309f4ab1b4f1707a6c566656264caa427ff4285c4d9d0b82" +dependencies = [ + "nom", +] + +[[package]] +name = "tantivy-sstable" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c69578242e8e9fc989119f522ba5b49a38ac20f576fc778035b96cc94f41f98e" +dependencies = [ + "tantivy-bitpacker", + "tantivy-common", + "tantivy-fst", + "zstd", +] + +[[package]] +name = "tantivy-stacker" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56d6ff5591fc332739b3ce7035b57995a3ce29a93ffd6012660e0949c956ea8" +dependencies = [ + "murmurhash32", + "rand_distr", + "tantivy-common", +] + +[[package]] +name = "tantivy-tokenizer-api" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0dcade25819a89cfe6f17d932c9cedff11989936bf6dd4f336d50392053b04" +dependencies = [ + "serde", +] + [[package]] name = "tar" version = "0.4.41" @@ -3623,11 +4921,35 @@ dependencies = [ "bytes", "libc", "mio 1.0.1", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" @@ -3639,6 +4961,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.11" @@ -3730,6 +5063,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3753,6 +5087,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "tracing-core", ] [[package]] @@ -3778,6 +5122,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + [[package]] name = "unicase" version = "2.7.0" @@ -3832,6 +5185,19 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" +dependencies = [ + "base64 0.22.1", + "log", + "native-tls", + "once_cell", + "url", +] + [[package]] name = "url" version = "2.5.2" @@ -3844,12 +5210,34 @@ dependencies = [ "serde", ] +[[package]] +name = "utf8-ranges" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "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" @@ -4003,6 +5391,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index cb7eac2..3558c5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,7 @@ sha2 = "0.10.8" git2 = { version = "0.19.0", optional = true } zip = { version = "2.1.5", optional = true } -serde_json = { version = "1.0.120", optional = true } +serde_json = { version = "1.0.121", optional = true } anyhow = { version = "1.0.86", optional = true } open = { version = "5.3.0", optional = true } @@ -86,7 +86,7 @@ winreg = { version = "0.52.0", optional = true } [workspace] resolver = "2" -members = [] +members = ["registry"] [profile.dev.package.full_moon] opt-level = 3 diff --git a/fly.toml b/fly.toml index fd179eb..935a40d 100644 --- a/fly.toml +++ b/fly.toml @@ -13,14 +13,13 @@ kill_timeout = '5s' [env] ADDRESS = '0.0.0.0' PORT = '8080' -INDEX_REPO_URL = 'https://github.com/daimond113/pesde-index' -COMMITTER_GIT_NAME = 'Pesde Index Updater' +COMMITTER_GIT_NAME = 'pesde index updater' COMMITTER_GIT_EMAIL = 'pesde@daimond113.com' [http_service] internal_port = 8080 force_https = true -auto_stop_machines = true +auto_stop_machines = "suspend" auto_start_machines = true min_machines_running = 0 processes = ['app'] diff --git a/registry/.env.example b/registry/.env.example new file mode 100644 index 0000000..e69de29 diff --git a/registry/Cargo.toml b/registry/Cargo.toml new file mode 100644 index 0000000..372c68f --- /dev/null +++ b/registry/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "pesde-registry" +version = "0.7.0" +edition = "2021" +repository = "https://github.com/daimond113/pesde-index" +publish = false + +[dependencies] +actix-web = "4.8.0" +actix-web-lab = "0.20.2" +actix-multipart = { version = "0.7.2", features = ["derive"] } +actix-cors = "0.7.0" +actix-governor = "0.5.0" +dotenvy = "0.15.7" +thiserror = "1.0.63" +tantivy = "0.22.0" +semver = "1.0.23" +chrono = { version = "0.4.38", features = ["serde"] } + +git2 = "0.19.0" +gix = { version = "0.64.0", default-features = false, features = ["blocking-http-transport-reqwest-rust-tls", "credentials"] } + +serde = "1.0.204" +serde_json = "1.0.121" +toml = "0.8.16" + +rusty-s3 = "0.5.0" +reqwest = { version = "0.12.5", features = ["json", "rustls-tls"] } + +tar = "0.4.41" +flate2 = "1.0.30" + +log = "0.4.22" +pretty_env_logger = "0.5.0" + +sentry = "0.34.0" +sentry-log = "0.34.0" +sentry-actix = "0.34.0" + +pesde = { path = "..", features = ["roblox", "lune", "luau", "wally-compat", "git2"] } \ No newline at end of file diff --git a/registry/src/auth.rs b/registry/src/auth.rs new file mode 100644 index 0000000..31ee54c --- /dev/null +++ b/registry/src/auth.rs @@ -0,0 +1,95 @@ +use crate::AppState; +use actix_governor::{KeyExtractor, SimpleKeyExtractionError}; +use actix_web::{ + body::MessageBody, + dev::{ServiceRequest, ServiceResponse}, + error::Error as ActixError, + http::header::AUTHORIZATION, + web, HttpMessage, HttpResponse, +}; +use actix_web_lab::middleware::Next; +use serde::Deserialize; + +#[derive(Debug, Copy, Clone, Hash, PartialOrd, PartialEq, Eq, Ord)] +pub struct UserId(pub u64); + +#[derive(Debug, Deserialize)] +struct UserResponse { + id: u64, +} + +pub async fn authentication( + app_state: web::Data, + req: ServiceRequest, + next: Next, +) -> Result, ActixError> { + let token = match req + .headers() + .get(AUTHORIZATION) + .map(|token| token.to_str().unwrap()) + { + Some(token) => token, + None => { + return Ok(req + .into_response(HttpResponse::Unauthorized().finish()) + .map_into_right_body()) + } + }; + + let token = if token.to_lowercase().starts_with("bearer ") { + token[7..].to_string() + } else { + token.to_string() + }; + + let response = match app_state + .reqwest_client + .get("https://api.github.com/user") + .header(reqwest::header::AUTHORIZATION, format!("Bearer {token}")) + .send() + .await + .and_then(|res| res.error_for_status()) + { + Ok(response) => response, + Err(e) if e.status() == Some(reqwest::StatusCode::UNAUTHORIZED) => { + return Ok(req + .into_response(HttpResponse::Unauthorized().finish()) + .map_into_right_body()) + } + Err(e) => { + log::error!("failed to get user: {e}"); + return Ok(req + .into_response(HttpResponse::InternalServerError().finish()) + .map_into_right_body()); + } + }; + + let user_id = match response.json::().await { + Ok(user) => user.id, + Err(_) => { + return Ok(req + .into_response(HttpResponse::Unauthorized().finish()) + .map_into_right_body()) + } + }; + + req.extensions_mut().insert(UserId(user_id)); + + let res = next.call(req).await?; + Ok(res.map_into_left_body()) +} + +#[derive(Debug, Clone)] +pub struct UserIdExtractor; + +impl KeyExtractor for UserIdExtractor { + type Key = UserId; + type KeyExtractionError = SimpleKeyExtractionError<&'static str>; + + fn extract(&self, req: &ServiceRequest) -> Result { + match req.extensions().get::() { + Some(user_id) => Ok(*user_id), + None => Err(SimpleKeyExtractionError::new("UserId not found")), + } + } +} diff --git a/registry/src/endpoints/mod.rs b/registry/src/endpoints/mod.rs new file mode 100644 index 0000000..be8e8fa --- /dev/null +++ b/registry/src/endpoints/mod.rs @@ -0,0 +1,4 @@ +pub mod package_version; +pub mod package_versions; +pub mod publish_version; +pub mod search; diff --git a/registry/src/endpoints/package_version.rs b/registry/src/endpoints/package_version.rs new file mode 100644 index 0000000..df80c6a --- /dev/null +++ b/registry/src/endpoints/package_version.rs @@ -0,0 +1,146 @@ +use std::str::FromStr; + +use actix_web::{http::header::ACCEPT, web, HttpRequest, HttpResponse, Responder}; +use rusty_s3::{actions::GetObject, S3Action}; +use semver::Version; +use serde::{Deserialize, Deserializer}; + +use pesde::{manifest::target::TargetKind, names::PackageName, source::pesde::IndexFile}; + +use crate::{ + error::Error, + package::{s3_name, PackageResponse, S3_SIGN_DURATION}, + AppState, +}; + +#[derive(Debug)] +pub enum VersionRequest { + Latest, + Specific(Version), +} + +impl<'de> Deserialize<'de> for VersionRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if s.eq_ignore_ascii_case("latest") { + return Ok(VersionRequest::Latest); + } + + Version::parse(&s) + .map(VersionRequest::Specific) + .map_err(serde::de::Error::custom) + } +} + +#[derive(Debug)] +pub enum TargetRequest { + All, + Specific(TargetKind), +} + +impl<'de> Deserialize<'de> for TargetRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if s.eq_ignore_ascii_case("all") { + return Ok(TargetRequest::All); + } + + TargetKind::from_str(&s) + .map(TargetRequest::Specific) + .map_err(serde::de::Error::custom) + } +} + +pub async fn get_package_version( + request: HttpRequest, + app_state: web::Data, + path: web::Path<(PackageName, VersionRequest, TargetRequest)>, +) -> Result { + let (name, version, target) = path.into_inner(); + + let (scope, name_part) = name.as_str(); + + let versions: IndexFile = { + let source = app_state.source.lock().unwrap(); + + match source.read_file([scope, name_part], &app_state.project)? { + Some(versions) => toml::de::from_str(&versions)?, + None => return Ok(HttpResponse::NotFound().finish()), + } + }; + + let version = match version { + VersionRequest::Latest => versions + .iter() + .filter(|(v_id, _)| match target { + TargetRequest::All => true, + TargetRequest::Specific(target) => *v_id.target() == target, + }) + .max_by_key(|(v, _)| v.version().clone()), + VersionRequest::Specific(version) => versions.iter().find(|(v, _)| { + *v.version() == version + && match target { + TargetRequest::All => true, + TargetRequest::Specific(target) => *v.target() == target, + } + }), + }; + + let Some((v_id, entry)) = version else { + return Ok(HttpResponse::NotFound().finish()); + }; + + let other_targets = versions + .iter() + .filter(|(v, _)| v.version() == v_id.version() && v.target() != v_id.target()) + .map(|(v_id, _)| v_id.target().to_string()) + .collect::>(); + + if request + .headers() + .get(ACCEPT) + .and_then(|accept| accept.to_str().ok()) + .is_some_and(|accept| accept.eq_ignore_ascii_case("application/octet-stream")) + { + let object_url = GetObject::new( + &app_state.s3_bucket, + Some(&app_state.s3_credentials), + &s3_name(&name, v_id), + ) + .sign(S3_SIGN_DURATION); + + return Ok(HttpResponse::Ok().body( + app_state + .reqwest_client + .get(object_url) + .send() + .await? + .error_for_status()? + .bytes() + .await?, + )); + } + + let entry = entry.clone(); + + let mut response = serde_json::to_value(PackageResponse { + name: name.to_string(), + version: v_id.version().to_string(), + target: Some(entry.target.into()), + description: entry.description.unwrap_or_default(), + published_at: entry.published_at, + license: entry.license.unwrap_or_default(), + })?; + + if !other_targets.is_empty() { + response["other_targets"] = serde_json::to_value(other_targets)?; + } + + Ok(HttpResponse::Ok().json(response)) +} diff --git a/registry/src/endpoints/package_versions.rs b/registry/src/endpoints/package_versions.rs new file mode 100644 index 0000000..26ebf78 --- /dev/null +++ b/registry/src/endpoints/package_versions.rs @@ -0,0 +1,34 @@ +use actix_web::{web, HttpResponse, Responder}; + +use pesde::{names::PackageName, source::pesde::IndexFile}; + +use crate::{error::Error, package::PackageResponse, AppState}; + +pub async fn get_package_versions( + app_state: web::Data, + path: web::Path, +) -> Result { + let name = path.into_inner(); + + let (scope, name_part) = name.as_str(); + + let source = app_state.source.lock().unwrap(); + let versions: IndexFile = match source.read_file([scope, name_part], &app_state.project)? { + Some(versions) => toml::de::from_str(&versions)?, + None => return Ok(HttpResponse::NotFound().finish()), + }; + + Ok(HttpResponse::Ok().json( + versions + .into_iter() + .map(|(v_id, entry)| PackageResponse { + name: name.to_string(), + version: v_id.version().to_string(), + target: Some(entry.target.into()), + description: entry.description.unwrap_or_default(), + published_at: entry.published_at, + license: entry.license.unwrap_or_default(), + }) + .collect::>(), + )) +} diff --git a/registry/src/endpoints/publish_version.rs b/registry/src/endpoints/publish_version.rs new file mode 100644 index 0000000..065c7da --- /dev/null +++ b/registry/src/endpoints/publish_version.rs @@ -0,0 +1,270 @@ +use std::{ + collections::BTreeSet, + io::{Cursor, Read, Write}, +}; + +use actix_multipart::form::{bytes::Bytes, MultipartForm}; +use actix_web::{web, HttpResponse, Responder}; +use flate2::read::GzDecoder; +use git2::{Remote, Repository, Signature}; +use rusty_s3::{actions::PutObject, S3Action}; +use tar::Archive; + +use pesde::{ + manifest::Manifest, + source::{ + pesde::{IndexFile, IndexFileEntry, ScopeInfo, SCOPE_INFO_FILE}, + specifiers::DependencySpecifiers, + traits::PackageSource, + version_id::VersionId, + }, + DEFAULT_INDEX_NAME, MANIFEST_FILE_NAME, +}; + +use crate::{ + auth::UserId, + benv, + error::Error, + package::{s3_name, S3_SIGN_DURATION}, + search::update_version, + AppState, +}; + +#[derive(MultipartForm)] +pub struct PublishBody { + #[multipart(limit = "4 MiB")] + tarball: Bytes, +} + +fn signature<'a>() -> Signature<'a> { + Signature::now( + &benv!(required "COMMITTER_GIT_NAME"), + &benv!(required "COMMITTER_GIT_EMAIL"), + ) + .unwrap() +} + +fn get_refspec(repo: &Repository, remote: &mut Remote) -> Result { + let upstream_branch_buf = repo.branch_upstream_name(repo.head()?.name().unwrap())?; + let upstream_branch = upstream_branch_buf.as_str().unwrap(); + + let refspec_buf = remote + .refspecs() + .find(|r| r.direction() == git2::Direction::Fetch && r.dst_matches(upstream_branch)) + .unwrap() + .rtransform(upstream_branch)?; + let refspec = refspec_buf.as_str().unwrap(); + + Ok(refspec.to_string()) +} + +const FORBIDDEN_FILES: &[&str] = &[".DS_Store", "default.project.json"]; +const FORBIDDEN_DIRECTORIES: &[&str] = &[".git"]; + +pub async fn publish_package( + app_state: web::Data, + body: MultipartForm, + user_id: web::ReqData, +) -> Result { + let bytes = body.tarball.data.to_vec(); + let mut decoder = GzDecoder::new(Cursor::new(&bytes)); + let mut archive = Archive::new(&mut decoder); + + let entries = archive.entries()?; + let mut manifest = None::; + + for entry in entries { + let mut entry = entry?; + let path = entry.path()?; + let path = path.to_str().ok_or(Error::InvalidArchive)?; + + if entry.header().entry_type().is_dir() { + if FORBIDDEN_DIRECTORIES.contains(&path) { + return Err(Error::InvalidArchive); + } + + continue; + } + + if FORBIDDEN_FILES.contains(&path) { + return Err(Error::InvalidArchive); + } + + if path == MANIFEST_FILE_NAME { + let mut content = String::new(); + entry.read_to_string(&mut content)?; + manifest = Some(toml::de::from_str(&content).map_err(|_| Error::InvalidArchive)?); + } + } + + let Some(manifest) = manifest else { + return Err(Error::InvalidArchive); + }; + + { + let source = app_state.source.lock().unwrap(); + source.refresh(&app_state.project).map_err(Box::new)?; + let config = source.config(&app_state.project)?; + + if manifest + .indices + .get(DEFAULT_INDEX_NAME) + .filter(|index_url| *index_url == source.repo_url()) + .is_none() + { + return Err(Error::InvalidArchive); + } + + let dependencies = manifest + .all_dependencies() + .map_err(|_| Error::InvalidArchive)?; + + for (specifier, _) in dependencies.values() { + match specifier { + DependencySpecifiers::Pesde(specifier) => { + if specifier + .index + .as_ref() + .is_some_and(|index| index != DEFAULT_INDEX_NAME) + && !config.other_registries_allowed + { + return Err(Error::InvalidArchive); + } + + let (dep_scope, dep_name) = specifier.name.as_str(); + if source + .read_file([dep_scope, dep_name], &app_state.project)? + .is_none() + { + return Err(Error::InvalidArchive); + } + } + }; + } + + let repo = source.repo_git2(&app_state.project)?; + + let (scope, name) = manifest.name.as_str(); + let mut oids = vec![]; + + match source.read_file([scope, SCOPE_INFO_FILE], &app_state.project)? { + Some(info) => { + let info: ScopeInfo = toml::de::from_str(&info)?; + if !info.owners.contains(&user_id.0) { + return Ok(HttpResponse::Forbidden().finish()); + } + } + None => { + let scope_info = toml::to_string(&ScopeInfo { + owners: BTreeSet::from([user_id.0]), + })?; + + let mut blob_writer = repo.blob_writer(None)?; + blob_writer.write_all(scope_info.as_bytes())?; + oids.push((SCOPE_INFO_FILE, blob_writer.commit()?)); + } + }; + + let mut entries: IndexFile = toml::de::from_str( + &source + .read_file([scope, name], &app_state.project)? + .unwrap_or_default(), + )?; + + let new_entry = IndexFileEntry { + target: manifest.target.clone(), + published_at: chrono::Utc::now(), + description: manifest.description.clone(), + license: manifest.license.clone(), + + dependencies, + }; + + if entries + .insert( + VersionId::new(manifest.version.clone(), manifest.target.kind()), + new_entry.clone(), + ) + .is_some() + { + return Ok(HttpResponse::Conflict().finish()); + } + + let mut remote = repo.find_remote("origin")?; + let refspec = get_refspec(&repo, &mut remote)?; + + let reference = repo.find_reference(&refspec)?; + + { + let index_content = toml::to_string(&entries)?; + let mut blob_writer = repo.blob_writer(None)?; + blob_writer.write_all(index_content.as_bytes())?; + oids.push((name, blob_writer.commit()?)); + } + + let old_root_tree = reference.peel_to_tree()?; + let old_scope_tree = match old_root_tree.get_name(scope) { + Some(entry) => Some(repo.find_tree(entry.id())?), + None => None, + }; + + let mut scope_tree = repo.treebuilder(old_scope_tree.as_ref())?; + for (file, oid) in oids { + scope_tree.insert(file, oid, 0o100644)?; + } + + let scope_tree_id = scope_tree.write()?; + let mut root_tree = repo.treebuilder(Some(&repo.find_tree(old_root_tree.id())?))?; + root_tree.insert(scope, scope_tree_id, 0o040000)?; + + let tree_oid = root_tree.write()?; + + repo.commit( + Some("HEAD"), + &signature(), + &signature(), + &format!( + "add {}@{} {}", + manifest.name, manifest.version, manifest.target + ), + &repo.find_tree(tree_oid)?, + &[&reference.peel_to_commit()?], + )?; + + let mut push_options = git2::PushOptions::new(); + let mut remote_callbacks = git2::RemoteCallbacks::new(); + + let git_creds = app_state.project.auth_config().git_credentials().unwrap(); + remote_callbacks.credentials(|_, _, _| { + git2::Cred::userpass_plaintext(&git_creds.username, &git_creds.password) + }); + + push_options.remote_callbacks(remote_callbacks); + + remote.push(&[refspec], Some(&mut push_options))?; + + update_version(&app_state, &manifest.name, new_entry); + } + + let object_url = PutObject::new( + &app_state.s3_bucket, + Some(&app_state.s3_credentials), + &s3_name( + &manifest.name, + &VersionId::new(manifest.version.clone(), manifest.target.kind()), + ), + ) + .sign(S3_SIGN_DURATION); + + app_state + .reqwest_client + .put(object_url) + .body(bytes) + .send() + .await?; + + Ok(HttpResponse::Ok().body(format!( + "published {}@{} {}", + manifest.name, manifest.version, manifest.target + ))) +} diff --git a/registry/src/endpoints/search.rs b/registry/src/endpoints/search.rs new file mode 100644 index 0000000..d161e6a --- /dev/null +++ b/registry/src/endpoints/search.rs @@ -0,0 +1,94 @@ +use std::collections::HashMap; + +use actix_web::{web, HttpResponse, Responder}; +use serde::Deserialize; +use tantivy::{query::AllQuery, schema::Value, DateTime, Order}; + +use pesde::{names::PackageName, source::pesde::IndexFile}; + +use crate::{error::Error, package::PackageResponse, AppState}; + +#[derive(Deserialize)] +pub struct Request { + #[serde(default)] + query: Option, + #[serde(default)] + offset: Option, +} + +pub async fn search_packages( + app_state: web::Data, + request: web::Query, +) -> Result { + let searcher = app_state.search_reader.searcher(); + let schema = searcher.schema(); + + let id = schema.get_field("id").unwrap(); + + let scope = schema.get_field("scope").unwrap(); + let name = schema.get_field("name").unwrap(); + let description = schema.get_field("description").unwrap(); + + let query = request.query.as_deref().unwrap_or_default().trim(); + + let query = if query.is_empty() { + Box::new(AllQuery) + } else { + let mut query_parser = tantivy::query::QueryParser::for_index( + searcher.index(), + vec![scope, name, description], + ); + query_parser.set_field_boost(scope, 2.0); + query_parser.set_field_boost(name, 3.5); + + query_parser.parse_query(query)? + }; + + let top_docs = searcher + .search( + &query, + &tantivy::collector::TopDocs::with_limit(50) + .and_offset(request.offset.unwrap_or_default()) + .order_by_fast_field::("published_at", Order::Desc), + ) + .unwrap(); + + let source = app_state.source.lock().unwrap(); + + let top_docs = top_docs + .into_iter() + .map(|(_, doc_address)| { + let doc = searcher.doc::>(doc_address).unwrap(); + + let id = doc + .get(&id) + .unwrap() + .as_str() + .unwrap() + .parse::() + .unwrap(); + let (scope, name) = id.as_str(); + + let mut versions: IndexFile = toml::de::from_str( + &source + .read_file([scope, name], &app_state.project) + .unwrap() + .unwrap(), + ) + .unwrap(); + + let (version_id, entry) = versions.pop_last().unwrap(); + + PackageResponse { + name: id.to_string(), + version: version_id.version().to_string(), + target: None, + description: entry.description.unwrap_or_default(), + published_at: entry.published_at, + license: entry.license.unwrap_or_default(), + } + }) + .collect::>(); + + Ok(HttpResponse::Ok().json(top_docs)) +} diff --git a/registry/src/error.rs b/registry/src/error.rs new file mode 100644 index 0000000..064067e --- /dev/null +++ b/registry/src/error.rs @@ -0,0 +1,63 @@ +use actix_web::{body::BoxBody, HttpResponse, ResponseError}; +use log::error; +use pesde::source::pesde::errors::ReadFile; +use serde::Serialize; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("failed to parse query")] + Query(#[from] tantivy::query::QueryParserError), + + #[error("error reading repo file")] + ReadFile(#[from] ReadFile), + + #[error("error deserializing file")] + Deserialize(#[from] toml::de::Error), + + #[error("error sending request")] + Reqwest(#[from] reqwest::Error), + + #[error("failed to parse archive entries")] + Tar(#[from] std::io::Error), + + #[error("invalid archive")] + InvalidArchive, + + #[error("failed to read index config")] + Config(#[from] pesde::source::pesde::errors::ConfigError), + + #[error("git error")] + Git(#[from] git2::Error), + + #[error("failed to refresh source")] + Refresh(#[from] Box), + + #[error("failed to serialize struct")] + Serialize(#[from] toml::ser::Error), + + #[error("failed to serialize struct")] + SerializeJson(#[from] serde_json::Error), +} + +#[derive(Debug, Serialize)] +pub struct ErrorResponse { + pub error: String, +} + +impl ResponseError for Error { + fn error_response(&self) -> HttpResponse { + match self { + Error::Query(e) => HttpResponse::BadRequest().json(ErrorResponse { + error: format!("failed to parse query: {e}"), + }), + Error::Tar(_) | Error::InvalidArchive => HttpResponse::BadRequest().json(ErrorResponse { + error: "invalid archive. ensure it has all the required files, and all the dependencies exist in the registry.".to_string(), + }), + e => { + log::error!("unhandled error: {e:?}"); + HttpResponse::InternalServerError().finish() + } + } + } +} diff --git a/registry/src/main.rs b/registry/src/main.rs new file mode 100644 index 0000000..1080763 --- /dev/null +++ b/registry/src/main.rs @@ -0,0 +1,222 @@ +use std::{env::current_dir, fs::create_dir_all, sync::Mutex}; + +use actix_cors::Cors; +use actix_governor::{Governor, GovernorConfigBuilder}; +use actix_web::{ + middleware::{Compress, Condition, Logger, NormalizePath, TrailingSlash}, + rt::System, + web, App, HttpServer, +}; +use actix_web_lab::middleware::from_fn; +use log::info; +use rusty_s3::{Bucket, Credentials, UrlStyle}; + +use pesde::{ + source::{pesde::PesdePackageSource, traits::PackageSource}, + AuthConfig, Project, +}; + +use crate::{auth::UserIdExtractor, search::make_search}; + +mod auth; +mod endpoints; +mod error; +mod package; +mod search; + +pub struct AppState { + pub s3_bucket: Bucket, + pub s3_credentials: Credentials, + + pub source: Mutex, + pub project: Project, + pub reqwest_client: reqwest::Client, + + pub search_reader: tantivy::IndexReader, + pub search_writer: Mutex, +} + +#[macro_export] +macro_rules! benv { + ($name:expr) => { + std::env::var($name) + }; + ($name:expr => $default:expr) => { + benv!($name).unwrap_or($default.to_string()) + }; + (required $name:expr) => { + benv!($name).expect(concat!("Environment variable `", $name, "` must be set")) + }; + (parse $name:expr) => { + benv!($name) + .map(|v| v.parse().expect(concat!( + "Environment variable `", + $name, + "` must be a valid value" + ))) + }; + (parse required $name:expr) => { + benv!(parse $name).expect(concat!("Environment variable `", $name, "` must be set")) + }; + (parse $name:expr => $default:expr) => { + benv!($name => $default) + .parse() + .expect(concat!( + "Environment variable `", + $name, + "` must a valid value" + )) + }; +} + +async fn run(with_sentry: bool) -> std::io::Result<()> { + let address = benv!("ADDRESS" => "127.0.0.1"); + let port: u16 = benv!(parse "PORT" => "8080"); + + let cwd = current_dir().unwrap(); + let data_dir = cwd.join("data"); + create_dir_all(&data_dir).unwrap(); + + let project = Project::new( + &cwd, + data_dir.join("project"), + &cwd, + AuthConfig::new().with_git_credentials(Some(gix::sec::identity::Account { + username: benv!(required "GITHUB_USERNAME"), + password: benv!(required "GITHUB_PAT"), + })), + ); + let source = PesdePackageSource::new(env!("CARGO_PKG_REPOSITORY").try_into().unwrap()); + source.refresh(&project).expect("failed to refresh source"); + + let (search_reader, search_writer) = make_search(&project, &source); + + let app_data = web::Data::new(AppState { + s3_bucket: Bucket::new( + benv!(parse required "S3_ENDPOINT"), + UrlStyle::Path, + benv!(required "S3_BUCKET_NAME"), + benv!(required "S3_REGION"), + ) + .unwrap(), + s3_credentials: Credentials::new( + benv!(required "S3_ACCESS_KEY"), + benv!(required "S3_SECRET_KEY"), + ), + + source: Mutex::new(source), + project, + reqwest_client: reqwest::ClientBuilder::new() + .user_agent(concat!( + env!("CARGO_PKG_NAME"), + "/", + env!("CARGO_PKG_VERSION") + )) + .build() + .unwrap(), + + search_reader, + search_writer: Mutex::new(search_writer), + }); + + let generic_governor_config = GovernorConfigBuilder::default() + .burst_size(500) + .per_millisecond(500) + .use_headers() + .finish() + .unwrap(); + + let publish_governor_config = GovernorConfigBuilder::default() + .key_extractor(UserIdExtractor) + .burst_size(12) + .per_second(60) + .use_headers() + .finish() + .unwrap(); + + info!("listening on {address}:{port}"); + + HttpServer::new(move || { + App::new() + .wrap(Condition::new(with_sentry, sentry_actix::Sentry::new())) + .wrap(NormalizePath::new(TrailingSlash::Trim)) + .wrap(Cors::permissive()) + .wrap(Logger::default()) + .wrap(Compress::default()) + .app_data(app_data.clone()) + .route( + "/", + web::get().to(|| async { + concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")) + }), + ) + .service( + web::scope("/v0") + .route( + "/search", + web::get() + .to(endpoints::search::search_packages) + .wrap(Governor::new(&generic_governor_config)), + ) + .route( + "/packages/{name}", + web::get() + .to(endpoints::package_versions::get_package_versions) + .wrap(Governor::new(&generic_governor_config)), + ) + .route( + "/packages/{name}/{version}/{target}", + web::get() + .to(endpoints::package_version::get_package_version) + .wrap(Governor::new(&generic_governor_config)), + ) + .route( + "/packages", + web::post() + .to(endpoints::publish_version::publish_package) + .wrap(Governor::new(&publish_governor_config)) + .wrap(from_fn(auth::authentication)), + ), + ) + }) + .bind((address, port))? + .run() + .await +} + +// can't use #[actix_web::main] because of Sentry: +// "Note: Macros like #[tokio::main] and #[actix_web::main] are not supported. The Sentry client must be initialized before the async runtime is started so that all threads are correctly connected to the Hub." +// https://docs.sentry.io/platforms/rust/guides/actix-web/ +fn main() -> std::io::Result<()> { + let _ = dotenvy::dotenv(); + + let sentry_url = benv!("SENTRY_URL").ok(); + let with_sentry = sentry_url.is_some(); + + let mut log_builder = pretty_env_logger::formatted_builder(); + log_builder.parse_env(pretty_env_logger::env_logger::Env::default().default_filter_or("info")); + + if with_sentry { + let logger = sentry_log::SentryLogger::with_dest(log_builder.build()); + log::set_boxed_logger(Box::new(logger)).unwrap(); + log::set_max_level(log::LevelFilter::Info); + } else { + log_builder.try_init().unwrap(); + } + + let _guard = if let Some(sentry_url) = sentry_url { + std::env::set_var("RUST_BACKTRACE", "1"); + + Some(sentry::init(( + sentry_url, + sentry::ClientOptions { + release: sentry::release_name!(), + ..Default::default() + }, + ))) + } else { + None + }; + + System::new().block_on(run(with_sentry)) +} diff --git a/registry/src/package.rs b/registry/src/package.rs new file mode 100644 index 0000000..7772646 --- /dev/null +++ b/registry/src/package.rs @@ -0,0 +1,44 @@ +use chrono::{DateTime, Utc}; +use pesde::{ + manifest::target::{Target, TargetKind}, + names::PackageName, + source::version_id::VersionId, +}; +use serde::Serialize; +use std::time::Duration; + +pub const S3_SIGN_DURATION: Duration = Duration::from_secs(60 * 60); + +pub fn s3_name(name: &PackageName, version_id: &VersionId) -> String { + format!("{}+{}.tar.gz", name.escaped(), version_id.escaped()) +} + +#[derive(Debug, Serialize)] +pub struct TargetInfo { + kind: TargetKind, + lib: bool, + bin: bool, +} + +impl From for TargetInfo { + fn from(target: Target) -> Self { + TargetInfo { + kind: target.kind(), + lib: target.lib_path().is_some(), + bin: target.bin_path().is_some(), + } + } +} + +#[derive(Debug, Serialize)] +pub struct PackageResponse { + pub name: String, + pub version: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub target: Option, + #[serde(skip_serializing_if = "String::is_empty")] + pub description: String, + pub published_at: DateTime, + #[serde(skip_serializing_if = "String::is_empty")] + pub license: String, +} diff --git a/registry/src/search.rs b/registry/src/search.rs new file mode 100644 index 0000000..322f689 --- /dev/null +++ b/registry/src/search.rs @@ -0,0 +1,79 @@ +use crate::AppState; +use pesde::{ + names::PackageName, + source::pesde::{IndexFileEntry, PesdePackageSource}, + Project, +}; +use tantivy::{ + doc, + schema::{IndexRecordOption, TextFieldIndexing, TextOptions, FAST, STORED, STRING}, + DateTime, IndexReader, IndexWriter, Term, +}; + +pub fn make_search(project: &Project, source: &PesdePackageSource) -> (IndexReader, IndexWriter) { + let mut schema_builder = tantivy::schema::SchemaBuilder::new(); + + let field_options = TextOptions::default().set_indexing_options( + TextFieldIndexing::default() + .set_tokenizer("ngram") + .set_index_option(IndexRecordOption::WithFreqsAndPositions), + ); + + let id_field = schema_builder.add_text_field("id", STRING | STORED); + let scope = schema_builder.add_text_field("scope", field_options.clone()); + let name = schema_builder.add_text_field("name", field_options.clone()); + let description = schema_builder.add_text_field("description", field_options); + let published_at = schema_builder.add_date_field("published_at", FAST); + + let search_index = tantivy::Index::create_in_ram(schema_builder.build()); + search_index.tokenizers().register( + "ngram", + tantivy::tokenizer::NgramTokenizer::all_ngrams(1, 12).unwrap(), + ); + + let search_reader = search_index + .reader_builder() + .reload_policy(tantivy::ReloadPolicy::Manual) + .try_into() + .unwrap(); + let mut search_writer = search_index.writer(50_000_000).unwrap(); + + for (pkg_name, mut file) in source.all_packages(project).unwrap() { + let Some((_, latest_entry)) = file.pop_last() else { + log::warn!("no versions found for {pkg_name}"); + continue; + }; + + search_writer.add_document(doc!( + id_field => pkg_name.to_string(), + scope => pkg_name.as_str().0, + name => pkg_name.as_str().1, + description => latest_entry.description.unwrap_or_default(), + published_at => DateTime::from_timestamp_secs(latest_entry.published_at.timestamp()), + )).unwrap(); + } + + search_writer.commit().unwrap(); + search_reader.reload().unwrap(); + + (search_reader, search_writer) +} + +pub fn update_version(app_state: &AppState, name: &PackageName, entry: IndexFileEntry) { + let mut search_writer = app_state.search_writer.lock().unwrap(); + let schema = search_writer.index().schema(); + let id_field = schema.get_field("id").unwrap(); + + search_writer.delete_term(Term::from_field_text(id_field, &name.to_string())); + + search_writer.add_document(doc!( + id_field => name.to_string(), + schema.get_field("scope").unwrap() => name.as_str().0, + schema.get_field("name").unwrap() => name.as_str().1, + schema.get_field("description").unwrap() => entry.description.unwrap_or_default(), + schema.get_field("published_at").unwrap() => DateTime::from_timestamp_secs(entry.published_at.timestamp()) + )).unwrap(); + + search_writer.commit().unwrap(); + app_state.search_reader.reload().unwrap(); +} diff --git a/src/cli/auth.rs b/src/cli/auth.rs index 4808b6e..ddea6e2 100644 --- a/src/cli/auth.rs +++ b/src/cli/auth.rs @@ -67,6 +67,8 @@ pub fn get_token_login( .header("Authorization", format!("Bearer {access_token}")) .send() .context("failed to send user request")? + .error_for_status() + .context("failed to get user")? .json::() .context("failed to parse user response")?; diff --git a/src/cli/commands/auth/login.rs b/src/cli/commands/auth/login.rs index 79b0bd3..ebc6992 100644 --- a/src/cli/commands/auth/login.rs +++ b/src/cli/commands/auth/login.rs @@ -102,6 +102,8 @@ impl LoginCommand { )?) .send() .context("failed to send device code request")? + .error_for_status() + .context("failed to get device code response")? .json::() .context("failed to parse device code response")?; @@ -146,6 +148,8 @@ impl LoginCommand { )?) .send() .context("failed to send access token request")? + .error_for_status() + .context("failed to get access token response")? .json::() .context("failed to parse access token response")?; diff --git a/src/cli/commands/mod.rs b/src/cli/commands/mod.rs index b3a095f..332dbb7 100644 --- a/src/cli/commands/mod.rs +++ b/src/cli/commands/mod.rs @@ -75,7 +75,7 @@ impl Subcommand { Subcommand::Init(init) => init.run(project), Subcommand::Run(run) => run.run(project), Subcommand::Install(install) => install.run(project, multi, reqwest), - Subcommand::Publish(publish) => publish.run(project), + Subcommand::Publish(publish) => publish.run(project, reqwest), Subcommand::SelfInstall(self_install) => self_install.run(project), #[cfg(feature = "patches")] Subcommand::Patch(patch) => patch.run(project, reqwest), diff --git a/src/cli/commands/publish.rs b/src/cli/commands/publish.rs index ebfe36b..a1f6162 100644 --- a/src/cli/commands/publish.rs +++ b/src/cli/commands/publish.rs @@ -2,8 +2,12 @@ use anyhow::Context; use clap::Args; use colored::Colorize; use pesde::{ - manifest::target::Target, scripts::ScriptName, Project, MANIFEST_FILE_NAME, MAX_ARCHIVE_SIZE, + manifest::target::Target, + scripts::ScriptName, + source::{pesde::PesdePackageSource, traits::PackageSource}, + Project, DEFAULT_INDEX_NAME, MANIFEST_FILE_NAME, MAX_ARCHIVE_SIZE, }; +use reqwest::StatusCode; use std::path::Component; #[derive(Debug, Args)] @@ -14,7 +18,7 @@ pub struct PublishCommand { } impl PublishCommand { - pub fn run(self, project: Project) -> anyhow::Result<()> { + pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> { let mut manifest = project .deser_manifest() .context("failed to read manifest")?; @@ -258,10 +262,12 @@ impl PublishCommand { ); if !self.dry_run && !inquire::Confirm::new("is this information correct?").prompt()? { - println!("{}", "publish aborted".red().bold()); + println!("\n{}", "publish aborted".red().bold()); return Ok(()); } + + println!(); } let temp_manifest_path = project @@ -308,6 +314,56 @@ impl PublishCommand { return Ok(()); } - todo!("publishing to registry"); + let source = PesdePackageSource::new( + manifest + .indices + .get(DEFAULT_INDEX_NAME) + .context("missing default index")? + .clone(), + ); + source + .refresh(&project) + .context("failed to refresh source")?; + let config = source + .config(&project) + .context("failed to get source config")?; + + match reqwest + .post(format!("{}/v0/packages", config.api())) + .multipart(reqwest::blocking::multipart::Form::new().part( + "tarball", + reqwest::blocking::multipart::Part::bytes(archive).file_name("package.tar.gz"), + )) + .send() + .context("failed to send request")? + .error_for_status() + .and_then(|response| response.text()) + { + Ok(response) => { + println!("{response}"); + + Ok(()) + } + Err(e) + if e.status() + .is_some_and(|status| status == StatusCode::CONFLICT) => + { + println!("{}", "package version already exists".red().bold()); + + Ok(()) + } + Err(e) + if e.status() + .is_some_and(|status| status == StatusCode::FORBIDDEN) => + { + println!( + "{}", + "unauthorized to publish under this scope".red().bold() + ); + + Ok(()) + } + Err(e) => Err(e).context("failed to get response"), + } } } diff --git a/src/cli/version.rs b/src/cli/version.rs index 214247f..0574bd5 100644 --- a/src/cli/version.rs +++ b/src/cli/version.rs @@ -56,6 +56,8 @@ pub fn check_for_updates(reqwest: &reqwest::blocking::Client) -> anyhow::Result< )) .send() .context("failed to send request to GitHub API")? + .error_for_status() + .context("failed to get GitHub API response")? .json::>() .context("failed to parse GitHub API response")?; @@ -108,6 +110,8 @@ pub fn download_github_release( )) .send() .context("failed to send request to GitHub API")? + .error_for_status() + .context("failed to get GitHub API response")? .json::() .context("failed to parse GitHub API response")?; @@ -128,6 +132,8 @@ pub fn download_github_release( .header(ACCEPT, "application/octet-stream") .send() .context("failed to send request to download asset")? + .error_for_status() + .context("failed to download asset")? .bytes() .context("failed to download asset")?; diff --git a/src/main.rs b/src/main.rs index 25b5158..10f92d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -152,7 +152,15 @@ fn run() -> anyhow::Result<()> { .build()? }; - check_for_updates(&reqwest)?; + match check_for_updates(&reqwest) { + Ok(_) => {} + Err(e) => { + println!( + "{}", + format!("failed to check for updates: {e}\n\n").red().bold() + ); + } + } let target_version = project .deser_manifest() diff --git a/src/manifest/mod.rs b/src/manifest/mod.rs index 3cc78a6..269a8d4 100644 --- a/src/manifest/mod.rs +++ b/src/manifest/mod.rs @@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize}; use crate::{ manifest::{overrides::OverrideKey, target::Target}, - names::{PackageName, PackageNames}, - source::{specifiers::DependencySpecifiers, version_id::VersionId}, + names::PackageName, + source::specifiers::DependencySpecifiers, }; pub mod overrides; @@ -50,7 +50,10 @@ pub struct Manifest { pub includes: BTreeSet, #[cfg(feature = "patches")] #[serde(default, skip_serializing)] - pub patches: BTreeMap>, + pub patches: BTreeMap< + crate::names::PackageNames, + BTreeMap, + >, #[serde(default, skip_serializing)] pub pesde_version: Option, diff --git a/src/source/pesde/mod.rs b/src/source/pesde/mod.rs index af028b2..3b4b433 100644 --- a/src/source/pesde/mod.rs +++ b/src/source/pesde/mod.rs @@ -1,10 +1,15 @@ use gix::remote::Direction; -use relative_path::RelativePathBuf; -use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug, hash::Hash, io::Read}; - use pkg_ref::PesdePackageRef; +use relative_path::RelativePathBuf; +use reqwest::header::ACCEPT; +use serde::{Deserialize, Serialize}; use specifier::PesdeDependencySpecifier; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Debug, + hash::Hash, + io::Read, +}; use crate::{ manifest::{ @@ -28,7 +33,12 @@ pub struct PesdePackageSource { repo_url: gix::Url, } -const SCOPE_INFO_FILE: &str = "scope.toml"; +pub const SCOPE_INFO_FILE: &str = "scope.toml"; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ScopeInfo { + pub owners: BTreeSet, +} impl PesdePackageSource { pub fn new(repo_url: gix::Url) -> Self { @@ -50,21 +60,21 @@ impl PesdePackageSource { pub(crate) fn tree<'a>( &'a self, repo: &'a gix::Repository, - ) -> Result> { + ) -> Result { // this is a bare repo, so this is the actual path let path = repo.path().to_path_buf(); let remote = match repo.find_default_remote(Direction::Fetch) { Some(Ok(remote)) => remote, - Some(Err(e)) => return Err(Box::new(errors::TreeError::GetDefaultRemote(path, e))), + Some(Err(e)) => return Err(errors::TreeError::GetDefaultRemote(path, Box::new(e))), None => { - return Err(Box::new(errors::TreeError::NoDefaultRemote(path))); + return Err(errors::TreeError::NoDefaultRemote(path)); } }; let refspec = match remote.refspecs(Direction::Fetch).first() { Some(head) => head, - None => return Err(Box::new(errors::TreeError::NoRefSpecs(path))), + None => return Err(errors::TreeError::NoRefSpecs(path)), }; let spec_ref = refspec.to_ref(); @@ -72,59 +82,50 @@ impl PesdePackageSource { Some(local) => local .to_string() .replace('*', repo.branch_names().first().unwrap_or(&"main")), - None => return Err(Box::new(errors::TreeError::NoLocalRefSpec(path))), + None => return Err(errors::TreeError::NoLocalRefSpec(path)), }; let reference = match repo.find_reference(&local_ref) { Ok(reference) => reference, - Err(e) => { - return Err(Box::new(errors::TreeError::NoReference( - local_ref.to_string(), - e, - ))) - } + Err(e) => return Err(errors::TreeError::NoReference(local_ref.to_string(), e)), }; let reference_name = reference.name().as_bstr().to_string(); let id = match reference.into_fully_peeled_id() { Ok(id) => id, - Err(e) => return Err(Box::new(errors::TreeError::CannotPeel(reference_name, e))), + Err(e) => return Err(errors::TreeError::CannotPeel(reference_name, e)), }; let id_str = id.to_string(); let object = match id.object() { Ok(object) => object, - Err(e) => { - return Err(Box::new(errors::TreeError::CannotConvertToObject( - id_str, e, - ))) - } + Err(e) => return Err(errors::TreeError::CannotConvertToObject(id_str, e)), }; match object.peel_to_tree() { Ok(tree) => Ok(tree), - Err(e) => Err(Box::new(errors::TreeError::CannotPeelToTree(id_str, e))), + Err(e) => Err(errors::TreeError::CannotPeelToTree(id_str, e)), } } - pub(crate) fn read_file< + pub fn read_file< I: IntoIterator + Clone, P: ToString + PartialEq, >( &self, file_path: I, project: &Project, - ) -> Result, Box> { + ) -> Result, errors::ReadFile> { let path = self.path(project); let repo = match gix::open(&path) { Ok(repo) => repo, - Err(e) => return Err(Box::new(errors::ReadFile::Open(path, e))), + Err(e) => return Err(errors::ReadFile::Open(path, Box::new(e))), }; let tree = match self.tree(&repo) { Ok(tree) => tree, - Err(e) => return Err(Box::new(errors::ReadFile::Tree(path, e))), + Err(e) => return Err(errors::ReadFile::Tree(path, Box::new(e))), }; let file_path_str = file_path @@ -138,36 +139,34 @@ impl PesdePackageSource { let entry = match tree.lookup_entry(file_path, &mut lookup_buf) { Ok(Some(entry)) => entry, Ok(None) => return Ok(None), - Err(e) => return Err(Box::new(errors::ReadFile::Lookup(file_path_str, e))), + Err(e) => return Err(errors::ReadFile::Lookup(file_path_str, e)), }; let object = match entry.object() { Ok(object) => object, - Err(e) => return Err(Box::new(errors::ReadFile::Lookup(file_path_str, e))), + Err(e) => return Err(errors::ReadFile::Lookup(file_path_str, e)), }; let blob = object.into_blob(); let string = String::from_utf8(blob.data.clone()) - .map_err(|e| Box::new(errors::ReadFile::Utf8(file_path_str, e)))?; + .map_err(|e| errors::ReadFile::Utf8(file_path_str, e))?; Ok(Some(string)) } - pub fn config(&self, project: &Project) -> Result> { - let file = self - .read_file(["config.toml"], project) - .map_err(|e| Box::new(e.into()))?; + pub fn config(&self, project: &Project) -> Result { + let file = self.read_file(["config.toml"], project).map_err(Box::new)?; let string = match file { Some(s) => s, None => { - return Err(Box::new(errors::ConfigError::Missing( + return Err(errors::ConfigError::Missing(Box::new( self.repo_url.clone(), ))) } }; - let config: IndexConfig = toml::from_str(&string).map_err(|e| Box::new(e.into()))?; + let config: IndexConfig = toml::from_str(&string)?; Ok(config) } @@ -175,17 +174,17 @@ impl PesdePackageSource { pub fn all_packages( &self, project: &Project, - ) -> Result, Box> { + ) -> Result, errors::AllPackagesError> { let path = self.path(project); let repo = match gix::open(&path) { Ok(repo) => repo, - Err(e) => return Err(Box::new(errors::AllPackagesError::Open(path, e))), + Err(e) => return Err(errors::AllPackagesError::Open(path, Box::new(e))), }; let tree = match self.tree(&repo) { Ok(tree) => tree, - Err(e) => return Err(Box::new(errors::AllPackagesError::Tree(path, e))), + Err(e) => return Err(errors::AllPackagesError::Tree(path, Box::new(e))), }; let mut packages = BTreeMap::::new(); @@ -193,12 +192,12 @@ impl PesdePackageSource { for entry in tree.iter() { let entry = match entry { Ok(entry) => entry, - Err(e) => return Err(Box::new(errors::AllPackagesError::Decode(path, e))), + Err(e) => return Err(errors::AllPackagesError::Decode(path, e)), }; let object = match entry.object() { Ok(object) => object, - Err(e) => return Err(Box::new(errors::AllPackagesError::Convert(path, e))), + Err(e) => return Err(errors::AllPackagesError::Convert(path, e)), }; // directories will be trees, and files will be blobs @@ -211,12 +210,12 @@ impl PesdePackageSource { for inner_entry in object.into_tree().iter() { let inner_entry = match inner_entry { Ok(entry) => entry, - Err(e) => return Err(Box::new(errors::AllPackagesError::Decode(path, e))), + Err(e) => return Err(errors::AllPackagesError::Decode(path, e)), }; let object = match inner_entry.object() { Ok(object) => object, - Err(e) => return Err(Box::new(errors::AllPackagesError::Convert(path, e))), + Err(e) => return Err(errors::AllPackagesError::Convert(path, e)), }; if !matches!(object.kind, gix::object::Kind::Blob) { @@ -230,18 +229,17 @@ impl PesdePackageSource { } let blob = object.into_blob(); - let string = String::from_utf8(blob.data.clone()).map_err(|e| { - Box::new(errors::AllPackagesError::Utf8(package_name.to_string(), e)) - })?; + let string = String::from_utf8(blob.data.clone()) + .map_err(|e| errors::AllPackagesError::Utf8(package_name.to_string(), e))?; let file: IndexFile = match toml::from_str(&string) { Ok(file) => file, Err(e) => { - return Err(Box::new(errors::AllPackagesError::Deserialize( + return Err(errors::AllPackagesError::Deserialize( package_name, path, - e, - ))) + Box::new(e), + )) } }; @@ -254,6 +252,13 @@ impl PesdePackageSource { Ok(packages) } + + #[cfg(feature = "git2")] + pub fn repo_git2(&self, project: &Project) -> Result { + let path = self.path(project); + + git2::Repository::open_bare(&path) + } } impl PackageSource for PesdePackageSource { @@ -321,7 +326,12 @@ impl PackageSource for PesdePackageSource { let string = match self.read_file([scope, name], project) { Ok(Some(s)) => s, Ok(None) => return Err(Self::ResolveError::NotFound(specifier.name.to_string())), - Err(e) => return Err(Self::ResolveError::Read(specifier.name.to_string(), e)), + Err(e) => { + return Err(Self::ResolveError::Read( + specifier.name.to_string(), + Box::new(e), + )) + } }; let entries: IndexFile = toml::from_str(&string) @@ -363,7 +373,7 @@ impl PackageSource for PesdePackageSource { project: &Project, reqwest: &reqwest::blocking::Client, ) -> Result<(PackageFS, Target), Self::DownloadError> { - let config = self.config(project)?; + let config = self.config(project).map_err(Box::new)?; let index_file = project .cas_dir .join("index") @@ -385,21 +395,20 @@ impl PackageSource for PesdePackageSource { Err(e) => return Err(errors::DownloadError::ReadIndex(e)), } - let (scope, name) = pkg_ref.name.as_str(); let url = config .download() - .replace("{PACKAGE_SCOPE}", scope) - .replace("{PACKAGE_NAME}", name) - .replace("{PACKAGE_VERSION}", &pkg_ref.version.to_string()); + .replace("{PACKAGE}", &pkg_ref.name.to_string().replace("/", "%2F")) + .replace("{PACKAGE_VERSION}", &pkg_ref.version.to_string()) + .replace("{PACKAGE_TARGET}", &pkg_ref.target.to_string()); - let mut response = reqwest.get(url); + let mut response = reqwest.get(url).header(ACCEPT, "application/octet-stream"); if let Some(token) = &project.auth_config.pesde_token { log::debug!("using token for pesde package download"); response = response.header("Authorization", format!("Bearer {token}")); } - let response = response.send()?; + let response = response.send()?.error_for_status()?; let bytes = response.bytes()?; let mut decoder = flate2::read::GzDecoder::new(bytes.as_ref()); @@ -445,7 +454,7 @@ pub struct IndexConfig { #[serde(default)] pub git_allowed: bool, #[serde(default)] - pub custom_registry_allowed: bool, + pub other_registries_allowed: bool, pub github_oauth_client_id: String, } @@ -456,11 +465,8 @@ impl IndexConfig { pub fn download(&self) -> String { self.download - .as_ref() - .unwrap_or( - &"{API_URL}/v0/packages/{PACKAGE_SCOPE}/{PACKAGE_NAME}/{PACKAGE_VERSION}" - .to_string(), - ) + .as_deref() + .unwrap_or("{API_URL}/v0/packages/{PACKAGE}/{PACKAGE_VERSION}/{PACKAGE_TARGET}") .replace("{API_URL}", self.api()) } } @@ -473,6 +479,8 @@ pub struct IndexFileEntry { #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub license: Option, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub dependencies: BTreeMap, @@ -522,14 +530,11 @@ pub mod errors { #[error("error interacting with the filesystem")] Io(#[from] std::io::Error), - #[error("error opening repository at {0}")] - Open(PathBuf, #[source] gix::open::Error), - #[error("no default remote found in repository at {0}")] NoDefaultRemote(PathBuf), #[error("error getting default remote from repository at {0}")] - GetDefaultRemote(PathBuf, #[source] gix::remote::find::existing::Error), + GetDefaultRemote(PathBuf, #[source] Box), #[error("no refspecs found in repository at {0}")] NoRefSpecs(PathBuf), @@ -554,7 +559,7 @@ pub mod errors { #[non_exhaustive] pub enum ReadFile { #[error("error opening repository at {0}")] - Open(PathBuf, #[source] gix::open::Error), + Open(PathBuf, #[source] Box), #[error("error getting tree from repository at {0}")] Tree(PathBuf, #[source] Box), @@ -595,14 +600,14 @@ pub mod errors { Parse(#[from] toml::de::Error), #[error("missing config file for index at {0}")] - Missing(gix::Url), + Missing(Box), } #[derive(Debug, Error)] #[non_exhaustive] pub enum AllPackagesError { #[error("error opening repository at {0}")] - Open(PathBuf, #[source] gix::open::Error), + Open(PathBuf, #[source] Box), #[error("error getting tree from repository at {0}")] Tree(PathBuf, #[source] Box), @@ -614,7 +619,7 @@ pub mod errors { Convert(PathBuf, #[source] gix::object::find::existing::Error), #[error("error deserializing file {0} in repository at {1}")] - Deserialize(String, PathBuf, #[source] toml::de::Error), + Deserialize(String, PathBuf, #[source] Box), #[error("error parsing file for {0} as utf8")] Utf8(String, #[source] std::string::FromUtf8Error), diff --git a/src/source/traits.rs b/src/source/traits.rs index d644d31..4421583 100644 --- a/src/source/traits.rs +++ b/src/source/traits.rs @@ -1,3 +1,8 @@ +use std::{ + collections::BTreeMap, + fmt::{Debug, Display}, +}; + use crate::{ manifest::{ target::{Target, TargetKind}, @@ -6,10 +11,6 @@ use crate::{ source::{DependencySpecifiers, PackageFS, PackageSources, ResolveResult}, Project, }; -use std::{ - collections::BTreeMap, - fmt::{Debug, Display}, -}; pub trait DependencySpecifier: Debug + Display {}