diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 950d4fc..bf75163 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,9 +20,9 @@ jobs: include: - operating-system: ubuntu-22.04 targets: x86_64-unknown-linux-gnu,x86_64-unknown-linux-musl,aarch64-unknown-linux-gnu,aarch64-unknown-linux-musl - - operating-system: windows-2019 + - operating-system: windows-latest targets: aarch64-pc-windows-msvc,x86_64-pc-windows-msvc - - operating-system: macos-13 + - operating-system: macos-latest targets: aarch64-apple-darwin,x86_64-apple-darwin fail-fast: false steps: diff --git a/Cargo.lock b/Cargo.lock index f89fa58..c110bb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arbitrary" @@ -222,8 +222,8 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "itoa", "matchit", @@ -248,8 +248,8 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -274,12 +274,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[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" @@ -418,7 +412,7 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -513,6 +507,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -850,6 +854,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures" version = "0.3.31" @@ -1006,25 +1019,6 @@ dependencies = [ "libc", ] -[[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.9.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.10" @@ -1036,7 +1030,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.3.1", + "http", "indexmap 2.9.0", "slab", "tokio", @@ -1111,17 +1105,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[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.3.1" @@ -1133,17 +1116,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -1151,7 +1123,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.3.1", + "http", ] [[package]] @@ -1162,16 +1134,17 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", + "tokio", ] [[package]] name = "http-range-header" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" @@ -1203,44 +1176,22 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.32" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2 0.4.10", - "http 1.3.1", - "http-body 1.0.1", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -1253,8 +1204,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", - "http 1.3.1", - "hyper 1.6.0", + "http", + "hyper", "hyper-util", "rustls", "rustls-native-certs", @@ -1270,7 +1221,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.6.0", + "hyper", "hyper-util", "pin-project-lite", "tokio", @@ -1279,22 +1230,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.12" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ + "base64", "bytes", "futures-channel", + "futures-core", "futures-util", - "http 1.3.1", - "http-body 1.0.1", - "hyper 1.6.0", + "http", + "http-body", + "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.0", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1477,7 +1434,7 @@ dependencies = [ "quote", "strum", "syn", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -1495,13 +1452,33 @@ dependencies = [ [[package]] name = "infer" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc150e5ce2330295b8616ce0e3f53250e53af31759a9dbedad1621ba29151847" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" dependencies = [ "cfb", ] +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.9.1", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "inout" version = "0.1.4" @@ -1598,6 +1575,26 @@ dependencies = [ "regex", ] +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1641,9 +1638,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libmimalloc-sys" -version = "0.1.42" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" +checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" dependencies = [ "cc", "libc", @@ -1708,7 +1705,7 @@ dependencies = [ "lazy_static", "proc-macro2", "quote", - "regex-syntax 0.8.5", + "regex-syntax", "rustc_version", "syn", ] @@ -1757,11 +1754,11 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] @@ -1784,9 +1781,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mimalloc" -version = "0.1.46" +version = "0.1.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" +checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" dependencies = [ "libmimalloc-sys", ] @@ -1829,6 +1826,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -1842,7 +1840,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.3.1", + "http", "httparse", "memchr", "mime", @@ -1874,6 +1872,42 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.9.1", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-debouncer-mini" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17849edfaabd9a5fef1c606d99cfc615a8e99f7ac4366406d86c7942a3184cf2" +dependencies = [ + "log", + "notify", + "notify-types", + "tempfile", +] + +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" + [[package]] name = "ntapi" version = "0.4.1" @@ -1885,12 +1919,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "overload", - "winapi", + "windows-sys 0.61.2", ] [[package]] @@ -1931,11 +1964,11 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "onig" -version = "6.4.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "libc", "once_cell", "onig_sys", @@ -1943,9 +1976,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.8.1" +version = "69.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" dependencies = [ "cc", "pkg-config", @@ -1978,12 +2011,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "pact-plugin-driver" version = "0.7.4" @@ -2031,16 +2058,22 @@ name = "pact-stub-server" version = "0.6.3" dependencies = [ "anyhow", - "base64 0.21.7", + "base64", + "bytes", "clap", "env_logger 0.10.2", "expectest", "futures", - "http 0.2.12", - "hyper 0.14.32", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", "itertools 0.10.5", "maplit", "mimalloc", + "notify", + "notify-debouncer-mini", "pact_matching", "pact_models", "pact_verifier", @@ -2052,7 +2085,7 @@ dependencies = [ "serde_json", "test-log", "tokio", - "tower 0.4.13", + "tower 0.5.2", "tower-http", "tower-service", "tracing", @@ -2063,19 +2096,19 @@ dependencies = [ [[package]] name = "pact_matching" -version = "2.0.0-beta.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb7056d9f060df24290009baaaeb77311ff73eea22bd176882b42ca5403ae8a" +checksum = "53025d152918a0dbe4df6c7773b20ecb098e90f49e7a720ff91e490ba761fd3a" dependencies = [ "ansi_term", "anyhow", - "base64 0.22.1", + "base64", "bytes", "chrono", "difference", "futures", "hex", - "http 1.3.1", + "http", "infer", "itertools 0.14.0", "kiss_xml", @@ -2095,7 +2128,6 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "snailquote", "sxd-document", "tokio", "tracing", @@ -2106,13 +2138,13 @@ dependencies = [ [[package]] name = "pact_models" -version = "1.3.1" +version = "1.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab72b0e00fd38e4c8ee9275898c9e2c73b7eaf0fe1066d5a99b7bc282255c4c" +checksum = "cca6eddec95d1d3ef89449ab28c5d5c966e848897717007813845b7e909d56a2" dependencies = [ "anyhow", "ariadne", - "base64 0.22.1", + "base64", "bytes", "chrono", "chrono-tz", @@ -2134,7 +2166,7 @@ dependencies = [ "rand 0.8.5", "rand_regex", "regex", - "regex-syntax 0.8.5", + "regex-syntax", "reqwest", "semver", "serde", @@ -2147,18 +2179,18 @@ dependencies = [ [[package]] name = "pact_verifier" -version = "1.3.0" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7297846557c882bd339ddf8346907e8dfa43c5faa85a4dcc50dce8d687ea92bd" +checksum = "95e5ce9ac40d5c70b1c29fddd57ae5be043960b058dc96653bb249d339231310" dependencies = [ "ansi_term", "anyhow", "async-trait", - "base64 0.22.1", + "base64", "bytes", "difference", "futures", - "http 1.3.1", + "http", "humantime", "itertools 0.14.0", "lazy_static", @@ -2172,7 +2204,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.12", + "thiserror", "tokio", "tracing", "tracing-core", @@ -2454,8 +2486,8 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", - "thiserror 2.0.12", + "socket2 0.5.9", + "thiserror", "tokio", "tracing", "web-time", @@ -2476,7 +2508,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror", "tinyvec", "tracing", "web-time", @@ -2491,7 +2523,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.5.9", "tracing", "windows-sys 0.59.0", ] @@ -2577,7 +2609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bfbd599a8c757f89100e3ae559fb1ef9efa1cfd9276136862e3089dec627b31" dependencies = [ "rand 0.8.5", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] @@ -2611,42 +2643,27 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.5" @@ -2655,33 +2672,29 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "async-compression", - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.6.0", + "hyper", "hyper-rustls", "hyper-util", - "ipnet", "js-sys", "log", - "mime", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", "rustls", "rustls-native-certs", - "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", @@ -2691,13 +2704,13 @@ dependencies = [ "tokio-rustls", "tokio-util", "tower 0.5.2", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "windows-registry", ] [[package]] @@ -2774,15 +2787,6 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -2847,7 +2851,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags 2.9.1", - "core-foundation", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -2871,18 +2875,28 @@ checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2891,14 +2905,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -2928,7 +2943,7 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" dependencies = [ - "base64 0.22.1", + "base64", "chrono", "hex", "indexmap 1.9.3", @@ -3031,16 +3046,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" -[[package]] -name = "snailquote" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec62a949bda7f15800481a711909f946e1204f2460f89210eaf7f57730f88f86" -dependencies = [ - "thiserror 1.0.69", - "unicode_categories", -] - [[package]] name = "snapbox" version = "0.4.17" @@ -3082,6 +3087,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "spin" version = "0.9.8" @@ -3184,6 +3199,27 @@ dependencies = [ "windows", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tar" version = "0.4.44" @@ -3239,33 +3275,13 @@ dependencies = [ "syn", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.12", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -3347,27 +3363,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.0" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.0", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -3458,19 +3473,19 @@ dependencies = [ "async-stream", "async-trait", "axum", - "base64 0.22.1", + "base64", "bytes", - "h2 0.4.10", - "http 1.3.1", - "http-body 1.0.1", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.6.0", + "hyper", "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", "prost", - "socket2", + "socket2 0.5.9", "tokio", "tokio-stream", "tower 0.4.13", @@ -3501,7 +3516,6 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "hdrhistogram", "indexmap 1.9.3", "pin-project", "pin-project-lite", @@ -3522,27 +3536,33 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", + "hdrhistogram", + "indexmap 2.9.0", "pin-project-lite", + "slab", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "tower-http" -version = "0.4.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "async-compression", - "base64 0.21.7", + "base64", "bitflags 2.9.1", "bytes", "futures-core", "futures-util", - "http 0.2.12", - "http-body 0.4.6", + "http", + "http-body", + "http-body-util", "http-range-header", "httpdate", "iri-string", @@ -3552,7 +3572,7 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower 0.4.13", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -3596,9 +3616,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -3617,14 +3637,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "sharded-slab", "smallvec", "thread_local", @@ -3704,12 +3724,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - [[package]] name = "untrusted" version = "0.9.0" @@ -3747,11 +3761,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.16.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -3971,9 +3987,9 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.1", "windows-result", - "windows-strings 0.4.2", + "windows-strings", ] [[package]] @@ -4004,15 +4020,21 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-registry" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" dependencies = [ + "windows-link 0.1.1", "windows-result", - "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-strings", ] [[package]] @@ -4021,16 +4043,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -4039,7 +4052,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -4060,6 +4073,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -4078,18 +4109,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -4100,9 +4132,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -4112,9 +4144,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -4124,9 +4156,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -4136,9 +4168,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -4148,9 +4180,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -4160,9 +4192,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -4172,9 +4204,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -4184,9 +4216,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" @@ -4377,7 +4409,7 @@ dependencies = [ "memchr", "pbkdf2", "sha1", - "thiserror 2.0.12", + "thiserror", "time", "xz2", "zeroize", diff --git a/Cargo.toml b/Cargo.toml index 11e5b70..c7b019f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,29 +14,35 @@ exclude = [ ] [dependencies] -anyhow = "1.0.71" -base64 = "0.21.1" +anyhow = "1.0.100" +base64 = "0.22.1" clap = { version = "4.3.0", features = ["env", "std", "cargo"] } futures = "0.3.28" -http = "0.2.9" -hyper = { version = "0.14.26", features = ["full"] } +http = "1.3.1" +hyper = { version = "1.7.0", features = ["full"] } +hyper-util = {version = "0.1.17", features = ["full"] } itertools = "0.10.5" maplit = "1.0.2" -mimalloc = "0.1.46" -pact_matching = "~2.0.0-beta.1" -pact_verifier = "~1.3.0" -pact_models = "~1.3.1" -regex = "1.8.2" +mimalloc = "0.1.48" +pact_matching = "~2.0.1" +pact_verifier = "1.3.3" +pact_models = "1.3.7" +regex = "1.12.2" reqwest = { version = "0.12.12", default-features = false, features = ["json", "rustls-tls-native-roots"] } -serde = "1.0.163" -serde_json = "1.0.96" -tokio = { version = "1.28.1", features = ["full"] } -tower = { version = "0.4.13", features = [ "full" ] } -tower-service = { version = "0.3.2" } -tower-http = { version = "0.4.0", features = [ "full" ] } +serde = "1.0.228" +serde_json = "1.0.145" +tokio = { version = "1.48.0", features = ["full"] } +tower = { version = "0.5.2", features = [ "full" ] } +tower-service = { version = "0.3.3" } +tower-http = { version = "0.6.6", features = [ "full" ] } tracing = { version = "0.1.37", features = [ "log" ] } tracing-core = "0.1.31" +http-body-util = { version = "0.1.3", features = ["full"] } +http-body = "1.0.1" +bytes = "1" tracing-subscriber = "0.3.17" +notify = "8.2.0" +notify-debouncer-mini = "0.7.0" [dev-dependencies] expectest = "0.12.0" diff --git a/README.md b/README.md index 384f08e..93f0282 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,50 @@ The running server can be controlled with the following options: |--------|-------------| | `-p, --port ` | The port to bind to. If not specified, a random port will be allocated by the operating system. | +### Watch mode + +The Pact Stub Server now supports a watch mode that automatically monitors pact files and directories for changes and reloads them without restarting the server. This feature is particularly useful during development when pact files are frequently updated. + +#### Usage + +To enable watch mode, use the `--watch` or `-w` flag: + +```bash +# Watch a single pact file +pact-stub-server --file path/to/pact.json --watch --port 8080 + +# Watch a directory of pact files +pact-stub-server --dir path/to/pacts --watch --port 8080 + +# Watch multiple files and directories +pact-stub-server --file pact1.json --file pact2.json --dir pacts_dir --watch --port 8080 +``` + +#### Supported Source Types + +Watch mode supports the following pact source types: + +- **File sources** (`--file`): Individual pact files are monitored for changes +- **Directory sources** (`--dir`): Entire directories are monitored recursively for changes to pact files + +**Note**: URL sources (`--url`) and Pact Broker sources (`--broker-url`) are not supported for watching as they are remote resources. + +#### File System Events + +The watcher responds to the following types of file system events: + +- File modifications (content changes) +- File creation (new pact files added) +- File deletion (pact files removed) +- Directory changes (new files added to watched directories) + +#### Limitations + +- Only works with local file and directory sources +- URL and Pact Broker sources cannot be watched +- Requires file system notifications to be available on the host system +- Memory usage may be slightly higher due to shared state management + ## Docker ### Usage @@ -184,10 +228,10 @@ Docker images are published to multiple registries | OS | Architecture | Supported | Pact Stub Server Version | | ------- | ------------ | --------- | ---------------- | -| OSX | x86_64 | ✅ | All | +| MacOS | x86_64 | ✅ | All | | Linux | x86_64 | ✅ | All | | Windows | x86_64 | ✅ | All | -| OSX | arm64 | ✅ | >=0.5.2 | +| MacOS | arm64 | ✅ | >=0.5.2 | | Linux | arm64 | ✅ | >=0.5.2 | | Windows | arm64 | ✅ | >=0.6.0 | | Alpine | x86_64 | ✅ | >=0.6.0 | diff --git a/release.sh b/release.sh index f13bec6..9a64b71 100755 --- a/release.sh +++ b/release.sh @@ -52,12 +52,10 @@ build_linux_aarch64() { } # Build the x86_64 darwin release build_macos_x86_64() { + export MACOSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET:-12} cargo build --target x86_64-apple-darwin "${cargo_flags[@]}" if [[ "${cargo_flags[*]}" =~ "--release" ]]; then - gzip_and_sum \ - "$CARGO_TARGET_DIR/x86_64-apple-darwin/release/${APP}" \ - "$ARTIFACTS_DIR/${APP}-osx-x86_64.gz" gzip_and_sum \ "$CARGO_TARGET_DIR/x86_64-apple-darwin/release/${APP}" \ "$ARTIFACTS_DIR/${APP}-macos-x86_64.gz" @@ -66,12 +64,10 @@ build_macos_x86_64() { # Build the aarch64 darwin release build_macos_aarch64() { + export MACOSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET:-12} cargo build --target aarch64-apple-darwin "${cargo_flags[@]}" if [[ "${cargo_flags[*]}" =~ "--release" ]]; then - gzip_and_sum \ - "$CARGO_TARGET_DIR/aarch64-apple-darwin/release/${APP}" \ - "$ARTIFACTS_DIR/${APP}-osx-aarch64.gz" gzip_and_sum \ "$CARGO_TARGET_DIR/aarch64-apple-darwin/release/${APP}" \ "$ARTIFACTS_DIR/${APP}-macos-aarch64.gz" diff --git a/scripts/gzip-and-sum.sh b/scripts/gzip-and-sum.sh index 42f1f1d..c7847b5 100644 --- a/scripts/gzip-and-sum.sh +++ b/scripts/gzip-and-sum.sh @@ -15,4 +15,11 @@ gzip_and_sum() { gzip --stdout --best "$orig_file" > "$target_file" openssl dgst -sha256 -r "$target_file" > "$digest_file" -} + basenamedir=$(basename $target_file) + if [[ "$(uname -s)" == "Darwin" ]]; then + # OSX requires an empty arg passed to -i, but this doesn't work on Lin/Win + sed -Ei '' "s|\*(.*)$|\*$basenamedir|" "$digest_file" + else + sed -Ei "s|\*(.*)$|\*$basenamedir|" "$digest_file" + fi +} \ No newline at end of file diff --git a/scripts/install-stub-server-cli.sh b/scripts/install-stub-server-cli.sh index 42908b0..5ab299c 100755 --- a/scripts/install-stub-server-cli.sh +++ b/scripts/install-stub-server-cli.sh @@ -9,11 +9,11 @@ mkdir -p ~/.pact/bin case "$(uname -s)" in Darwin) - echo '== Installing stub server CLI for Mac OSX ==' + echo '== Installing stub server CLI for Mac OS ==' if [ "$(uname -m)" = "arm64" ]; then - curl -L -o ~/.pact/bin/pact-stub-server.gz https://github.com/pact-foundation/pact-stub-server/releases/download/v${VERSION}/pact-stub-server-osx-aarch64.gz + curl -L -o ~/.pact/bin/pact-stub-server.gz https://github.com/pact-foundation/pact-stub-server/releases/download/v${VERSION}/pact-stub-server-macos-aarch64.gz else - curl -L -o ~/.pact/bin/pact-stub-server.gz https://github.com/pact-foundation/pact-stub-server/releases/download/v${VERSION}/pact-stub-server-osx-x86_64.gz + curl -L -o ~/.pact/bin/pact-stub-server.gz https://github.com/pact-foundation/pact-stub-server/releases/download/v${VERSION}/pact-stub-server-macos-x86_64.gz fi gunzip -N -f ~/.pact/bin/pact-stub-server.gz chmod +x ~/.pact/bin/pact-stub-server diff --git a/scripts/test_watch.sh b/scripts/test_watch.sh new file mode 100755 index 0000000..58e2f5a --- /dev/null +++ b/scripts/test_watch.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# Test script for the watch mode functionality + +echo "Testing Pact Stub Server Watch Mode" +echo "====================================" + +# Create a temporary test pact file +TEST_PACT_FILE="/tmp/test-watch-pact.json" +cat > "$TEST_PACT_FILE" << 'EOF' +{ + "consumer": { + "name": "test-consumer" + }, + "provider": { + "name": "test-provider" + }, + "interactions": [ + { + "description": "A test interaction", + "request": { + "method": "GET", + "path": "/test" + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": { + "message": "Hello from initial pact!" + } + } + } + ], + "metadata": { + "pactSpecification": { + "version": "2.0.0" + } + } +} +EOF + +echo "Created test pact file at: $TEST_PACT_FILE" +echo "" +echo "Starting pact-stub-server in watch mode..." +echo "The server will watch for changes to the pact file and reload automatically." +echo "" +echo "To test:" +echo "1. Start the server (this script will start it)" +echo "2. In another terminal, run: curl http://localhost:8080/test" +echo "3. Modify the pact file and curl again to see changes" +echo "" +echo "Press Ctrl+C to stop the server" +echo "" + +# Run the pact-stub-server with watch mode +cargo run --bin pact-stub-server -- --file "$TEST_PACT_FILE" --port 8080 --watch --loglevel info \ No newline at end of file diff --git a/src/bin/pact-stub-server.rs b/src/bin/pact-stub-server.rs new file mode 100644 index 0000000..8e44e6f --- /dev/null +++ b/src/bin/pact-stub-server.rs @@ -0,0 +1,9 @@ +use std::env; +use std::process::ExitCode; +use pact_stub_server::handle_command_args; + +#[tokio::main] +async fn main() -> Result<(), ExitCode> { + let args: Vec = env::args().collect(); + handle_command_args(args).await +} \ No newline at end of file diff --git a/src/main.rs b/src/lib.rs similarity index 69% rename from src/main.rs rename to src/lib.rs index 5157ba8..9767fd0 100644 --- a/src/main.rs +++ b/src/lib.rs @@ -87,19 +87,121 @@ use std::env; use std::process::ExitCode; use std::str::FromStr; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use std::sync::mpsc::channel; use clap::{Command, Arg, ArgMatches, ArgAction, command, crate_version}; use clap::error::ErrorKind; use mimalloc::MiMalloc; use pact_models::prelude::*; +use pact_models::prelude::v4::*; use regex::Regex; use tracing::{debug, error, info, warn}; use tracing_core::LevelFilter; use tracing_subscriber::FmtSubscriber; +use tokio::sync::broadcast; +use notify::RecursiveMode; +use notify_debouncer_mini::{DebouncedEventKind, new_debouncer}; use crate::loading::load_pacts; use crate::server::ServerHandler; +/// Setup file watcher for watch mode +fn setup_file_watcher( + sources: Vec, + matches: &ArgMatches, + shared_pacts: Arc>>, + reload_tx: broadcast::Sender<()> +) { + let watch_paths = get_watch_paths(&sources); + if watch_paths.is_empty() { + warn!("No file or directory sources found for watching"); + return; + } + + let insecure_tls = matches.get_flag("insecure-tls"); + let ext = matches.get_one::("ext").cloned(); + + std::thread::spawn(move || { + let (debounce_tx, debounce_rx) = channel(); + let mut debouncer = match new_debouncer(Duration::from_secs(1), debounce_tx) { + Ok(debouncer) => debouncer, + Err(e) => { + error!("Failed to create file debouncer: {}", e); + return; + } + }; + + // Watch all file and directory sources + for path in &watch_paths { + if let Err(e) = debouncer.watcher().watch(path, RecursiveMode::Recursive) { + error!("Failed to watch path {:?}: {}", path, e); + } else { + info!("Watching for changes in: {:?}", path); + } + } + + let runtime = tokio::runtime::Runtime::new().unwrap(); + + loop { + match debounce_rx.recv() { + Ok(Ok(events)) => { + for event in events.iter() { + match &event.kind { + DebouncedEventKind::Any => { + info!("File change detected in watched directory"); + + // Reload pacts + let pacts_result = runtime.block_on(load_pacts(sources.clone(), insecure_tls, ext.as_ref())); + if pacts_result.iter().any(|p| p.is_err()) { + error!("Error reloading pacts:"); + for error in pacts_result.iter().filter_map(|p| p.as_ref().err()) { + error!(" - {}", error); + } + } else { + let new_pacts = pacts_result.iter() + .filter_map(|result| result.as_ref().ok()) + .map(|(p, s)| (p.as_v4_pact().unwrap(), s.clone())) + .collect::>(); + + let interactions: usize = new_pacts.iter().map(|(p, _)| p.interactions.len()).sum(); + info!("Reloaded {} pacts ({} total interactions)", new_pacts.len(), interactions); + + *shared_pacts.lock().unwrap() = new_pacts; + let _ = reload_tx.send(()); + } + break; + } + _ => {} + } + } + } + Ok(Err(e)) => { + error!("Watch error: {:?}", e); + break; + } + Err(e) => { + error!("Debouncer channel error: {:?}", e); + break; + } + } + } + }); +} + +/// Extract file and directory paths from pact sources for watching +fn get_watch_paths(sources: &[PactSource]) -> Vec { + sources.iter() + .filter_map(|source| match source { + PactSource::File(path) => Some(PathBuf::from(path)), + PactSource::Dir(path) => Some(PathBuf::from(path)), + _ => None, // URLs and Broker sources are not watchable + }) + .collect() +} + mod pact_support; mod server; mod loading; @@ -107,13 +209,8 @@ mod loading; #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; -#[tokio::main] -async fn main() -> Result<(), ExitCode> { - let args: Vec = env::args().collect(); - handle_command_args(args).await -} -fn print_version() { +pub fn print_version() { println!("pact stub server version : v{}", env!("CARGO_PKG_VERSION")); println!("pact specification version: v{}", PactSpecification::V4.version_str()); } @@ -197,16 +294,51 @@ fn pact_source(matches: &ArgMatches) -> Vec { sources } -async fn handle_command_args(args: Vec) -> Result<(), ExitCode> { +/// Handles the command line arguments and runs the stub server accordingly. +/// +/// Used by the binary crate. Parses the provided arguments, sets up logging, loads pact files, and starts the server. +pub async fn handle_command_args(args: Vec) -> Result<(), ExitCode> { let app = build_args(); match app.try_get_matches_from(args) { - Ok(ref matches) => { + Ok(results) => handle_matches(&results).await, + + Err(ref err) => match err.kind() { + ErrorKind::DisplayHelp => { + println!("{}", err); + Ok(()) + } + ErrorKind::DisplayVersion => { + print_version(); + println!(); + Ok(()) + } + _ => err.exit(), + }, + } +} + +/// Handles the command line arguments and runs the stub server accordingly. +/// +/// Used by library consumers. Creates a new Tokio runtime, handle the matches +/// and starts the server. +pub fn process_stub_command(args: &ArgMatches) -> Result<(), ExitCode> { + tokio::runtime::Runtime::new().unwrap().block_on(async { + let res = handle_matches(args).await; + match res { + Ok(()) => Ok(()), + Err(code) => Err(code), + } + }) +} + +async fn handle_matches(matches: &ArgMatches) -> Result<(), ExitCode> { let level = matches.get_one::("loglevel").cloned() .unwrap_or_else(|| "info".to_string()); setup_logger(level.as_str()); let sources = pact_source(matches); + let watch_mode = matches.get_flag("watch"); - let pacts = load_pacts(sources, matches.get_flag("insecure-tls"), + let pacts = load_pacts(sources.clone(), matches.get_flag("insecure-tls"), matches.get_one("ext")).await; if pacts.iter().any(|p| p.is_err()) { error!("There were errors loading the pact files."); @@ -235,36 +367,44 @@ async fn handle_command_args(args: Vec) -> Result<(), ExitCode> { info!("Loaded {} pacts ({} total interactions)", pacts.len(), interactions); let auto_cors = matches.get_flag("cors"); let referer = matches.get_flag("cors-referer"); - let server_handler = ServerHandler::new( - pacts, - auto_cors, - referer, - provider_state, - provider_state_header_name, - empty_provider_states); - tokio::task::spawn_blocking(move || { - server_handler.start_server(port) - }).await.unwrap() - } - }, - Err(ref err) => { - match err.kind() { - ErrorKind::DisplayHelp => { - println!("{}", err); - Ok(()) - }, - ErrorKind::DisplayVersion => { - print_version(); - println!(); - Ok(()) - }, - _ => err.exit() + + if watch_mode { + // Setup shared state for pacts when in watch mode + let shared_pacts = Arc::new(Mutex::new(pacts.clone())); + let (reload_tx, reload_rx) = broadcast::channel::<()>(1); + + // Setup file watching if in watch mode + setup_file_watcher(sources, matches, shared_pacts.clone(), reload_tx.clone()); + + let server_handler = ServerHandler::new_with_watch( + shared_pacts, + reload_tx, + auto_cors, + referer, + provider_state, + provider_state_header_name, + empty_provider_states); + tokio::task::spawn_blocking(move || { + server_handler.start_server(port) + }).await.unwrap() + } else { + let server_handler = ServerHandler::new( + pacts, + auto_cors, + referer, + provider_state, + provider_state_header_name, + empty_provider_states); + tokio::task::spawn_blocking(move || { + server_handler.start_server(port) + }).await.unwrap() + } } - } - } } -fn build_args() -> Command { +/// Creates a new clap Command instance with the command line arguments for the stub server. +/// This function defines the command line interface for the stub server, including options for logging, pact file sources, and server configuration. +pub fn build_args() -> Command { command!() .about(format!("Pact Stub Server {}", crate_version!())) .arg_required_else_help(true) @@ -369,6 +509,11 @@ fn build_args() -> Command { .action(ArgAction::Append) .value_parser(regex_value) .help("Provider name or regex to use to filter the Pacts fetched from the Pact broker (can be repeated)")) + .arg(Arg::new("watch") + .short('w') + .long("watch") + .action(ArgAction::SetTrue) + .help("Watch for changes in pact files and reload automatically")) .arg(Arg::new("version") .short('v') .long("version") diff --git a/src/pact_support.rs b/src/pact_support.rs index 8c7e1d3..e2bcb40 100644 --- a/src/pact_support.rs +++ b/src/pact_support.rs @@ -1,10 +1,13 @@ use std::collections::HashMap; +use std::convert::Infallible; use http::{Error, HeaderMap, Uri}; use http::header::{ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE}; use http::header::HeaderValue; use http::request::Parts; -use hyper::{Body, Response as HyperResponse}; +use http_body_util::BodyExt; +use hyper::{Response as HyperResponse}; +use hyper::body::Bytes; use pact_models::content_types::TEXT; use pact_models::http_parts::HttpPart; use pact_models::prelude::*; @@ -12,6 +15,8 @@ use pact_models::query_strings::parse_query_string; use pact_models::v4::http_parts::{HttpRequest, HttpResponse}; use tracing::{debug, info, warn}; +type BoxBody = http_body_util::combinators::BoxBody; + fn extract_query_string(uri: &Uri) -> Option>>> { match uri.query() { Some(q) => parse_query_string(q), @@ -54,7 +59,7 @@ pub fn hyper_request_to_pact_request(req: Parts, body: OptionalBody) -> HttpRequ } } -pub fn pact_response_to_hyper_response(response: &HttpResponse) -> Result, Error> { +pub fn pact_response_to_hyper_response(response: &HttpResponse) -> Result, Error>{ info!("<=== Sending {}", response); debug!(" body: '{}'", response.body.display_string()); debug!(" matching_rules: {:?}", response.matching_rules); @@ -82,9 +87,16 @@ pub fn pact_response_to_hyper_response(response: &HttpResponse) -> Result res.body(Body::empty()) + let body_bytes = Bytes::copy_from_slice(body); + let box_body = http_body_util::Full::from(body_bytes).boxed(); + res.body(box_body) + .map_err(|e| Error::from(e)) + }, + _ => { + let box_body = http_body_util::Full::from(Bytes::new()).boxed(); + res.body(box_body) + .map_err(|e| Error::from(e)) + } } } diff --git a/src/server.rs b/src/server.rs index aba10fa..9dabcc1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,16 +1,18 @@ -use std::future::{Ready, ready}; +use std::convert::Infallible; use std::pin::Pin; use std::process::ExitCode; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; +use tokio::sync::broadcast; use anyhow::anyhow; use futures::executor::block_on; use futures::future::Future; use futures::stream::StreamExt; use futures::task::{Context, Poll}; -use http::{Error, StatusCode}; -use hyper::{Body, Request as HyperRequest, Response as HyperResponse, Server}; -use hyper::server::conn::AddrStream; +use http::{StatusCode}; +use http_body_util::BodyExt; +use hyper::{Request as HyperRequest}; +use hyper::body::{Bytes, Incoming}; use itertools::Itertools; use maplit::hashmap; use pact_matching::{CoreMatchingContext, DiffConfig, Mismatch}; @@ -20,11 +22,19 @@ use pact_models::prelude::v4::*; use pact_models::v4::http_parts::{HttpRequest, HttpResponse}; use pact_models::v4::V4InteractionType; use regex::Regex; -use tower::ServiceBuilder; use tower_http::classify::{ServerErrorsAsFailures, SharedClassifier}; use tower_http::trace::{DefaultMakeSpan, Trace, TraceLayer}; use tower_service::Service; use tracing::{debug, error, info, warn}; +use hyper::Response; +use hyper_util::{ + rt::{TokioExecutor, TokioIo}, + server::conn::auto::Builder, +}; +use tokio::net::TcpListener; +use tower::Layer; + +type BoxBody = http_body_util::combinators::BoxBody; use crate::{pact_support, PactSource}; @@ -35,6 +45,8 @@ pub struct ServerHandler { pub struct Shared { sources: Vec<(V4Pact, PactSource)>, + shared_sources: Option>>>, + reload_tx: Option>, auto_cors: bool, cors_referer: bool, provider_state: Option, @@ -42,35 +54,23 @@ pub struct Shared { empty_provider_states: bool } -#[derive(Clone)] -struct ServerHandlerFactory { - inner: ServerHandler -} - -impl ServerHandlerFactory { - pub fn new(handler: ServerHandler) -> Self { - ServerHandlerFactory { - inner: handler, - } +pub trait TraceLayerProvider { + fn create_trace_layer() -> TraceLayer> { + TraceLayer::new_for_http() + .make_span_with(DefaultMakeSpan::new().include_headers(true)) } } -impl Service<&AddrStream> for ServerHandlerFactory { - type Response = Trace>; - type Error = anyhow::Error; - type Future = Ready>; +#[derive(Clone)] +pub struct ServerHandlerLayer; - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } +impl TraceLayerProvider for ServerHandlerLayer {} + +impl Layer for ServerHandlerLayer { + type Service = Trace>; - fn call(&mut self, req: &AddrStream) -> Self::Future { - debug!("Accepting a new connection from {}", req.remote_addr()); - let service = ServiceBuilder::new() - .layer(TraceLayer::new_for_http() - .make_span_with(DefaultMakeSpan::new().include_headers(true))) - .service(self.inner.clone()); - ready(Ok(service)) + fn layer(&self, inner: S) -> Self::Service { + Self::create_trace_layer().layer(inner) } } @@ -82,10 +82,35 @@ impl ServerHandler { provider_state: Option, provider_state_header_name: Option, empty_provider_states: bool - ) -> ServerHandler { + ) -> ServerHandler { ServerHandler { shared: Arc::new(Shared { sources, + shared_sources: None, + reload_tx: None, + auto_cors, + cors_referer, + provider_state, + provider_state_header_name, + empty_provider_states + }) + } + } + + pub fn new_with_watch( + shared_sources: Arc>>, + reload_tx: broadcast::Sender<()>, + auto_cors: bool, + cors_referer: bool, + provider_state: Option, + provider_state_header_name: Option, + empty_provider_states: bool + ) -> ServerHandler { + ServerHandler { + shared: Arc::new(Shared { + sources: vec![], // Empty since we use shared_sources + shared_sources: Some(shared_sources), + reload_tx: Some(reload_tx), auto_cors, cors_referer, provider_state, @@ -96,39 +121,89 @@ impl ServerHandler { } pub fn start_server(self, port: u16) -> Result<(), ExitCode> { - let addr = ([0, 0, 0, 0], port).into(); - match Server::try_bind(&addr) { - Ok(builder) => { - let server = builder.serve(ServerHandlerFactory::new(self)); - info!("Server started on port {}", server.local_addr().port()); - block_on(server).map_err(|err| { - error!("error occurred scheduling server future on Tokio runtime: {}", err); - ExitCode::from(2) - })?; - Ok(()) - }, - Err(err) => { - error!("could not start server: {}", err); - Err(ExitCode::FAILURE) - } + let addr = std::net::SocketAddrV4::new(std::net::Ipv4Addr::new(0, 0, 0, 0), port); + let addr = std::net::SocketAddr::V4(addr); + + let handler = self.clone(); + + // Start reload listener if in watch mode + if let Some(reload_tx) = &handler.shared.reload_tx { + let mut reload_rx = reload_tx.subscribe(); + tokio::spawn(async move { + while let Ok(_) = reload_rx.recv().await { + info!("Pacts reloaded - server will use updated pacts for new requests"); + } + }); } + + block_on(async move { + let listener = match TcpListener::bind(addr).await { + Ok(l) => l, + Err(err) => { + error!("tcp listener failed to bind address: {}", err); + return Err(ExitCode::FAILURE); + } + }; + + let local_addr = match listener.local_addr() { + Ok(a) => a, + Err(err) => { + error!("failed to get local address: {}", err); + return Err(ExitCode::FAILURE); + } + }; + info!("Server started on port {}", local_addr.port()); + + loop { + match listener.accept().await { + Ok((stream, _)) => { + let handler = handler.clone(); + tokio::spawn(async move { + let io = TokioIo::new(stream); + let handler = handler.clone(); + let tower_service = tower::ServiceBuilder::new() + .layer(ServerHandlerLayer) + .service(handler); + let hyper_service = hyper_util::service::TowerToHyperService::new(tower_service); + if let Err(err) = Builder::new(TokioExecutor::new()) + .serve_connection(io, hyper_service) + .await + { + error!("error serving connection: {:?}", err); + } + }); + } + Err(err) => { + error!("tcp listener failed to accept: {}", err); + return Err(ExitCode::FAILURE); + } + } + } + }) } } -impl Service> for ServerHandler { - type Response = HyperResponse; - type Error = Error; +impl Service> for ServerHandler { + type Response = Response; + type Error = hyper::http::Error; type Future = Pin> + Send>>; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn call(&mut self, req: HyperRequest) -> Self::Future { + fn call(&mut self, req: HyperRequest) -> Self::Future { let shared = self.shared.as_ref(); let auto_cors = shared.auto_cors; let cors_referer = shared.cors_referer; - let sources = shared.sources.clone(); + + // Use shared sources if in watch mode, otherwise use static sources + let sources = if let Some(shared_sources) = &shared.shared_sources { + shared_sources.lock().unwrap().clone() + } else { + shared.sources.clone() + }; + let provider_state = shared.provider_state.clone(); let provider_state_header_name = shared.provider_state_header_name.clone(); let empty_provider_states = shared.empty_provider_states; @@ -147,12 +222,18 @@ impl Service> for ServerHandler { None => provider_state }; - let bytes = hyper::body::to_bytes(body).await; + let bytes = body + .boxed() + .collect() + .await; let body = match bytes { - Ok(contents) => if contents.is_empty() { - OptionalBody::Empty - } else { - OptionalBody::Present(contents, None, None) + Ok(contents) => { + let bytes = contents.to_bytes(); + if bytes.is_empty() { + OptionalBody::Empty + } else { + OptionalBody::Present(bytes, None, None) + } }, Err(err) => { warn!("Failed to read request body: {}", err); diff --git a/tests/cmd/main.stderr b/tests/cmd/main.stderr index 2d6970b..f277957 100644 --- a/tests/cmd/main.stderr +++ b/tests/cmd/main.stderr @@ -37,6 +37,8 @@ Options: Consumer name or regex to use to filter the Pacts fetched from the Pact broker (can be repeated) --provider-name Provider name or regex to use to filter the Pacts fetched from the Pact broker (can be repeated) + -w, --watch + Watch for changes in pact files and reload automatically -v, --version Print version information -h, --help