diff --git a/Cargo.lock b/Cargo.lock index e2a86fad..d1792fe9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,32 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.7.7" @@ -49,12 +75,45 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "bitvec" version = "1.0.1" @@ -150,12 +209,34 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -183,7 +264,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -208,6 +289,32 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -275,6 +382,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "deranged" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +dependencies = [ + "powerfmt", +] + [[package]] name = "deunicode" version = "1.4.1" @@ -289,6 +405,16 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", +] + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", ] [[package]] @@ -297,12 +423,107 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -324,6 +545,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "globset" version = "0.4.14" @@ -343,11 +570,30 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" dependencies = [ - "bitflags", + "bitflags 1.3.2", "ignore", "walkdir", ] +[[package]] +name = "h2" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -363,6 +609,49 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "humansize" version = "2.1.3" @@ -372,6 +661,43 @@ dependencies = [ "libm", ] +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -395,6 +721,16 @@ dependencies = [ "cc", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "ignore" version = "0.4.21" @@ -421,12 +757,36 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.66" @@ -454,6 +814,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + [[package]] name = "log" version = "0.4.20" @@ -475,6 +841,50 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -484,12 +894,65 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parse-zoneinfo" version = "0.3.0" @@ -499,6 +962,29 @@ dependencies = [ "regex", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -588,6 +1074,30 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -701,6 +1211,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "regex" version = "1.10.2" @@ -736,7 +1255,45 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" dependencies = [ - "bytecheck", + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.11.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", ] [[package]] @@ -793,6 +1350,25 @@ dependencies = [ "rust_decimal", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "ryu" version = "1.0.16" @@ -808,6 +1384,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -820,6 +1405,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.193" @@ -851,6 +1459,29 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -874,6 +1505,15 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "slug" version = "0.1.5" @@ -884,6 +1524,32 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -918,12 +1584,46 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "tera" version = "1.19.1" @@ -966,6 +1666,24 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "time" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +dependencies = [ + "deranged", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + [[package]] name = "tinyvec" version = "1.6.0" @@ -981,6 +1699,57 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2 0.5.5", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[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-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml_datetime" version = "0.6.3" @@ -998,6 +1767,37 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.17.0" @@ -1060,18 +1860,50 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicode-bidi" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "uuid" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -1088,6 +1920,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1119,6 +1960,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.89" @@ -1148,6 +2001,16 @@ version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +[[package]] +name = "web-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1185,7 +2048,25 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -1194,13 +2075,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -1209,42 +2105,84 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.5.26" @@ -1254,6 +2192,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wyz" version = "0.5.1" @@ -1297,10 +2245,14 @@ checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" name = "xsender" version = "0.1.0" dependencies = [ + "base64", "lazy_static", "log", + "regex", + "reqwest", "serde", "tera", + "tokio", "xml", "zip", ] @@ -1311,7 +2263,45 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" dependencies = [ + "aes", "byteorder", + "bzip2", + "constant_time_eq", "crc32fast", "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", ] diff --git a/xsender/Cargo.toml b/xsender/Cargo.toml index 7f5af55f..3911708e 100644 --- a/xsender/Cargo.toml +++ b/xsender/Cargo.toml @@ -8,7 +8,13 @@ description = "Sends XML files through SOAP - SUNAT" [dependencies] xml = "0.8.10" log = "0.4.20" -zip = { version = "0.6.6", default-features = false } +zip = { version = "0.6.6" } tera = "1.19.1" lazy_static = "1.4.0" serde = { version = "1.0.193", features = ["derive"] } +reqwest = "0.11.22" +regex = "1.10.2" +base64 = "0.21.5" + +[dev-dependencies] +tokio = { version = "1.35.1", features = ["macros"] } diff --git a/xsender/resources/test/R-12345678901-01-F001-00000587.xml b/xsender/resources/test/R-12345678901-01-F001-00000587.xml new file mode 100644 index 00000000..31877a25 --- /dev/null +++ b/xsender/resources/test/R-12345678901-01-F001-00000587.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + VKY1IcYW+H/VLoYyShq8xvzea6YGCg6VOR7X4ZeA+hz/z9PZdkDPzN4e83PUOnRoBKnj3DDqZ0N9d6OSWAfaag== + + + *Private key 'BetaPublicCert' not up**Named certificate 'BetaPrivateKey' not up**Named certificate 'BetaPrivateKey' not up**Named certificate 'BetaPrivateKey' not up*15374531431912018-09-20T09:16:2500:00:002018-09-2010:19:03SignSUNAT20131312955SUNAT#SignSUNAT2013131295512345678901F001-000005870La Factura numero F001-00000587, ha sido aceptadaF001-000005876-10414774453 \ No newline at end of file diff --git a/xsender/resources/test/R-20220557805-01-F001-22Openubl.xml b/xsender/resources/test/R-20220557805-01-F001-22Openubl.xml new file mode 100644 index 00000000..36f660b3 --- /dev/null +++ b/xsender/resources/test/R-20220557805-01-F001-22Openubl.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + S67rWjkW4fu/JwOm8yKOO6A/vc+is7qOkh0SOrOXIvqaPid5hSr09GAV11VDkZn+fd9e3z5RDiJQKu1+8NyhpA== + + + *Private key 'BetaPublicCert' not up**Named certificate 'BetaPrivateKey' not up**Named certificate 'BetaPrivateKey' not up**Named certificate 'BetaPrivateKey' not up*16097397490912021-01-03T23:54:1700:00:002021-01-0323:55:49SignSUNAT20131312955SUNAT#SignSUNAT4252 - El dato ingresado como atributo @listName es incorrecto. - INFO: 4252 (nodo: "cbc:InvoiceTypeCode/listName" valor: "SUNAT:Identificador de Tipo de Documento")4255 - El dato ingresado como atributo @schemeName es incorrecto. - INFO: 4255 (nodo: "cbc:ID/schemeName" valor: "SUNAT:Identificador de Documento de Identidad")3030 - El XML no contiene el tag o no existe información del código de local anexo del emisor - INFO: 3030 (nodo: "/" valor: "")4255 - El dato ingresado como atributo @schemeName es incorrecto. - INFO: 4255 (nodo: "cbc:ID/schemeName" valor: "SUNAT:Identificador de Documento de Identidad")4252 - El dato ingresado como atributo @listName es incorrecto. - Error en la linea: 1: 4252 (nodo: "cbc:PriceTypeCode/listName" valor: "SUNAT:Indicador de Tipo de Precio")4252 - El dato ingresado como atributo @listName es incorrecto. - Error en la linea: 1 Tributo: 1000: 4252 (nodo: "cbc:TaxExemptionReasonCode/listName" valor: "SUNAT:Codigo de Tipo de Afectacion del IGV")4252 - El dato ingresado como atributo @listName es incorrecto. - Error en la linea: 2: 4252 (nodo: "cbc:PriceTypeCode/listName" valor: "SUNAT:Indicador de Tipo de Precio")4252 - El dato ingresado como atributo @listName es incorrecto. - Error en la linea: 2 Tributo: 1000: 4252 (nodo: "cbc:TaxExemptionReasonCode/listName" valor: "SUNAT:Codigo de Tipo de Afectacion del IGV")2013131295520220557805F001-220La Factura numero F001-22, ha sido aceptadaF001-226-20601814120 \ No newline at end of file diff --git a/xsender/resources/test/bill_service_response_fault.xml b/xsender/resources/test/bill_service_response_fault.xml new file mode 100644 index 00000000..9ec1dea1 --- /dev/null +++ b/xsender/resources/test/bill_service_response_fault.xml @@ -0,0 +1,10 @@ + + + + + + soap-env:Client.0111 + No tiene el perfil para enviar comprobantes electronicos - Detalle: Rejected by policy. + + + \ No newline at end of file diff --git a/xsender/resources/test/bill_service_response_ok.xml b/xsender/resources/test/bill_service_response_ok.xml new file mode 100644 index 00000000..8674c1ff --- /dev/null +++ b/xsender/resources/test/bill_service_response_ok.xml @@ -0,0 +1,9 @@ + + + + + + UEsDBBQAAgAIAEwklVcAAAAAAgAAAAAAAAAGAAAAZHVtbXkvAwBQSwMEFAACAAgATCSVVwwGkuIxBAAAGQ0AABsAAABSLTEyMzQ1Njc4OTEyLTAxLUYwMDEtMS54bWy1V2FP2zwQ/r5fEZUPk6Y3OElbWKPQqVDGsgFitDC0byY52uhN7WA7peXXv+e4SdMStHbSq/LBuXvuubvHZ1sEXxaz1JqDkAlnJy330GlZwCIeJ2xy0robf7U/t770PwRU+IMsS5OIKgTegsw4k2BhMJMnrVwwn1OZSJ/RGUhfZhAlTyuwnz+mvoymMKP+QsZ+yOY8icD2Wibcp2JPhoZK1mywUHvSnfHZjLPzhQKmVcBPpASm5Jo0eoz+ivQU4VEjIf07wsFkImBCFTSRxrgVU6Uyn5CXl5fDl/YhFxPiOY5DnB5BTCyTyUGJlpxmFd4kkofo0vYiUC8IsDmkPANSJcHkVRgsZKoKsDZLm7LYVgn2UiUp+5Q5o+rdPjMQeb3ZkUY39eqWxIv3enXJw9XlqKAqscgCi6yhaHTkKRU2egVIvfmy1Q9wgvy708tqIGQ55g0+Y6nNDsOV6gejZIId5KI6IjvsCx4zHQZxyJ54/4NlBWeUcYY6pclrodUVqCmPrUE64SJR09m7EriOpsW+IjtyO+zgF6L1AGkNW6TgrircmdTplLXaMy7gQEhqyyntut6K8haeQODtAdbdbajlQiOax4Iy+cTFTBpD3fTHtBsSlcMY27Ks3qTek3QXgZCQbFceDJMJSLWnYqjIQV2niueepjn0H16PRPsBEvXt+ntGw3m4hMvrQXZ+OoB0zKYv98+/w2V4/hS/dm/uur+PB/C8OLvoEX41ypxzGf5KLn9cPF9E38/vczn8OVVikRz9PDkJSD2L3h9SbRCOGtmctfpEmIhPNyKZ4+mz/oWl9fEUFL3Bo4rXGQj10WJcWXn2ydDUooIfsCw4g4eu0xtSRc1KR5kzj8zXeA3EVrQ2rfhNQmSo8W8HF2yhlDmIEYiEpnWLJt6fvhZbcBne63z2CGJ/to3oeoKyXLJWhlRqrXXEdfOdQt5ePm9Msh/gW6VN9+ZND4d979AJyBtrgTvLpeKz1e2CRreEbjsKtAYcO20XJ/mo2z72DLTy6iaHeos8x+3Zrmd7nbHj+MXfClpB1hFjfC76DbDCXsDKN37F7bULbtdgN5wbcEPc8dsd3+tuglfcNPJrqq960ZbR3fVgXOuuAnKxvKFCLY2tWIYxbk71mlU0qEEbf16v210TkfejSoeZQh1QrGqVGA/ZQpL3isPDnyiaVg0OlKLRdFZMkvbrkRGMpus7wUzObdg/2NJA20yihiDyp2RkW2f9CSwG8f9ISRoT3EIEyXznnK7X7nSPjj/3XG/nnA0phjzKtQrl4JW1VF/FUK60xBRf8dWwq8le2zcG+4zHONibE13YCtQQZCSSrKjrklpfaYSyU4thHYJbJsE/1pRaMom5RSPIFI2pYavHlj3VC1+3szEzW4VXUjXBjU5JlqB9x704wvNe/fbZjY0spHk/SPN/Nv3/AFBLAQIAABQAAgAIAEwklVcAAAAAAgAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAABkdW1teS9QSwECAAAUAAIACABMJJVXDAaS4jEEAAAZDQAAGwAAAAAAAAABAAAAAAAmAAAAUi0xMjM0NTY3ODkxMi0wMS1GMDAxLTEueG1sUEsFBgAAAAACAAIAfQAAAJAEAAAAAA== + + + \ No newline at end of file diff --git a/xsender/resources/test/bill_service_response_ticket.xml b/xsender/resources/test/bill_service_response_ticket.xml new file mode 100644 index 00000000..8b16984a --- /dev/null +++ b/xsender/resources/test/bill_service_response_ticket.xml @@ -0,0 +1,8 @@ + + + + + 1703154974517 + + + \ No newline at end of file diff --git a/xsender/resources/test/get_status_response_ok.xml b/xsender/resources/test/get_status_response_ok.xml new file mode 100644 index 00000000..14f432f9 --- /dev/null +++ b/xsender/resources/test/get_status_response_ok.xml @@ -0,0 +1,11 @@ + + + + + + UEsDBBQAAgAIAK07lVcAAAAAAgAAAAAAAAAGAAAAZHVtbXkvAwBQSwMEFAACAAgArTuVV2PumuIyBAAACA0AAB8AAABSLTEyMzQ1Njc4OTEyLVJBLTIwMjAwMzI4LTEueG1stVdRT+M4EH7fXxGVh5X2LjhJoYUoZFUo3FULPba0gPbNJEMbLrFzttMWfv2OkyZNS9C2K53gwZn55puZz2MbvK/LJDbmIGTE2VnLPrRaBrCAhxGbnrUm4yvzpPXV/+RR4fbSNI4CqhA4AplyJsHAYCbPWplgLqcyki6jCUhXphBEzyuwmz3FrgxmkFB3KUN3wOY8CsB0WkW4S8WeDA2VrNlgqfaku+BJwtnlUgHTKuAnUgJTck0aPAW/RXqO8KCRkP4eYW86FTClCppIQ9yKmVKpS8hisThctA+5mBLHsixinRLEhDKaHpRoyWla4YtE8hBd2p4H6gUBNoeYp0CqJJi8CoOljFUO1mZpUhaaKsJeqiRlnzJjVH3YZwoiqzd7p9FNvdol8fKjXm3yeHN9l1OVWGSBZdpQNDqymAoTvQKk3nzZ8j2cIHdyfl0NhCzHvMFXWGqzw3ClfO8ummIHmaiOyA77gsdMh0E4YM/c/2QY3gVlnKFOcfSWa3UDasZDoxdPuYjULPlQAtvStNhXYAb2ETt4QLQeIK1hi+TcVYU7k1pHZa1mwgUcCElNOaPHtrOiHMEzCLw9wJiMBlouNKJ5LCiTz1wksjDUTb9MuyFROYyhKcvqi9R7ku4iEBKS7cq9fjQFqfZUDBU5qOtU8dzTOAP/hS7D/iWx5peB9eMvMumyf9KnvmN/z0bf528L/kYerkZXr7Ph+ViSm5voPusOX5aTq6Pn3n8PCfs7kT+GycPL203YW9BR6jyO/7gNF2dnHqln0ftDqg3CUSObs1afiCLiy62I5nj6jH/h1fh8Dore4lHF6wyE+mwwrows/VLQ1KK8b/Cac3qPx9ZpnyparHRUceaReYjXQGgEa9OKv0iIDDX+7eCcbSBlBuIORETjukUT709fi825Ct5hljyB2J9tI7qeoCyXrJUhlVprHXHdfKeQ95fPO5P0PXyrtOm+eNMHfd85tDzyzprjLjKpeLK6XdBol9BtR47WgK7Vtjt2t9M5alt2ga3cukvsC0jte4yvgW9Zbv67wlf2HFY+4TrSdyynbdqO6ay4N5wb8IK46zqnrtPZBK+4aeDWRF1Vqi13k2FvXKu9AnLxekuFei1s+XIQovbVY1XROJbdxh/n9Ph4TUQ+jiodxZDpgHxVq6TwkC0k+ag4PNuRonHVYE8pGsySfFC0X0+EYDReH/liMEYD/2BLA20rEjUEkV8lI9s6609gIYj/R0rSmGAEAUTznXPaTvvouNM9ObWdnXM2pOjzINMqlINX1lJ95UO50hJTjHomDrhltZ0TsxrwtXtjvi94iPO9Odi5LUf1QQYiSvPyrqmBT1em/1AI8NsIwXiiL9TYyPanMaOGjEJu0ABSRUNaUNeJyj7rzaxb3Jij5mYqFZuiCgmjNEL7jtvUMffZnQ1q0rw/pPkfGf8nUEsBAgAAFAACAAgArTuVVwAAAAACAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAGR1bW15L1BLAQIAABQAAgAIAK07lVdj7priMgQAAAgNAAAfAAAAAAAAAAEAAAAAACYAAABSLTEyMzQ1Njc4OTEyLVJBLTIwMjAwMzI4LTEueG1sUEsFBgAAAAACAAIAgQAAAJUEAAAAAA== + 0 + + + + \ No newline at end of file diff --git a/xsender/src/analyzer.rs b/xsender/src/analyzer.rs index a7a87fe4..d2c4cd41 100644 --- a/xsender/src/analyzer.rs +++ b/xsender/src/analyzer.rs @@ -1,147 +1,153 @@ -use crate::metadata::Metadata; - -pub struct DocumentType {} - -impl DocumentType { - const INVOICE: &'static str = "Invoice"; - const CREDIT_NOTE: &'static str = "CreditNote"; - const DEBIT_NOTE: &'static str = "DebitNote"; - const VOIDED_DOCUMENTS: &'static str = "VoidedDocuments"; - const SUMMARY_DOCUMENTS: &'static str = "SummaryDocuments"; - const DESPATCH_ADVICE: &'static str = "DespatchAdvise"; - const PERCEPTION: &'static str = "Perception"; - const RETENTION: &'static str = "Retention"; -} - -pub struct Catalog1 {} - -#[allow(dead_code)] -impl Catalog1 { - const FACTURA: &'static str = "01"; - const BOLETA: &'static str = "03"; - const NOTA_CREDITO: &'static str = "07"; - const NOTA_DEBITO: &'static str = "08"; - const GUIA_REMISION_REMITENTE: &'static str = "09"; - const RETENCION: &'static str = "20"; - const PERCEPCION: &'static str = "40"; -} - -pub struct DeliveryUrls { - pub invoice: String, - pub perception_retention: String, - pub despatch: String, -} - -#[derive(Debug, PartialEq)] -pub enum Delivery { - SOAP(String, SoapAction), - REST(String, RestAction), -} - -#[derive(Debug, PartialEq)] -pub enum SoapAction { - SendBill, - SendSummary, - SendPack, - GetStatus, -} - -#[derive(Debug, PartialEq)] -pub enum RestAction { - SendDocument, - VerifyTicket, -} - -pub trait Analyze { - fn target(&self, urls: &DeliveryUrls) -> Option; - fn ticket(&self, urls: &DeliveryUrls) -> Option; -} - -impl Analyze for Metadata { - fn target(&self, urls: &DeliveryUrls) -> Option { - match self.document_type.as_str() { - DocumentType::INVOICE | DocumentType::CREDIT_NOTE | DocumentType::DEBIT_NOTE => { - Some(Delivery::SOAP(urls.invoice.clone(), SoapAction::SendBill)) - } - DocumentType::SUMMARY_DOCUMENTS => Some(Delivery::SOAP( - urls.invoice.clone(), - SoapAction::SendSummary, - )), - DocumentType::VOIDED_DOCUMENTS => { - match self.voided_line_document_type_code.as_deref() { - Some(Catalog1::RETENCION) | Some(Catalog1::PERCEPCION) => Some(Delivery::SOAP( - urls.perception_retention.clone(), - SoapAction::SendSummary, - )), - Some(Catalog1::GUIA_REMISION_REMITENTE) => None, - Some(_) => Some(Delivery::SOAP( - urls.invoice.clone(), - SoapAction::SendSummary, - )), - _ => None, - } - } - DocumentType::PERCEPTION | DocumentType::RETENTION => Some(Delivery::SOAP( +use log::warn; + +use crate::global::{ + BOLETA_SERIE_REGEX, FACTURA_SERIE_REGEX, GUIA_REMISION_REMITENTE_SERIE_REGEX, + GUIA_REMISION_TRANSPORTISTA_SERIE_REGEX, +}; +use crate::models::Urls; +use crate::models::{ + Catalog1, DocumentType, RestFileTargetAction, SendFileTarget, SoapFileTargetAction, + VerifyTicketTarget, +}; + +pub fn send_file_target( + document_type: &str, + voided_line_document_type_code: &Option, + urls: &Urls, +) -> Option { + match document_type { + DocumentType::INVOICE | DocumentType::CREDIT_NOTE | DocumentType::DEBIT_NOTE => Some( + SendFileTarget::Soap(urls.invoice.clone(), SoapFileTargetAction::Bill), + ), + DocumentType::SUMMARY_DOCUMENTS => Some(SendFileTarget::Soap( + urls.invoice.clone(), + SoapFileTargetAction::Summary, + )), + DocumentType::VOIDED_DOCUMENTS => match voided_line_document_type_code.as_deref() { + Some(Catalog1::RETENCION) | Some(Catalog1::PERCEPCION) => Some(SendFileTarget::Soap( urls.perception_retention.clone(), - SoapAction::SendBill, + SoapFileTargetAction::Summary, )), - DocumentType::DESPATCH_ADVICE => Some(Delivery::REST( - urls.despatch.clone(), - RestAction::SendDocument, + Some(Catalog1::GUIA_REMISION_REMITENTE) => None, + Some(_) => Some(SendFileTarget::Soap( + urls.invoice.clone(), + SoapFileTargetAction::Summary, )), _ => None, - } + }, + DocumentType::PERCEPTION | DocumentType::RETENTION => Some(SendFileTarget::Soap( + urls.perception_retention.clone(), + SoapFileTargetAction::Bill, + )), + DocumentType::DESPATCH_ADVICE => Some(SendFileTarget::Rest( + urls.despatch.clone(), + RestFileTargetAction::SendDocument, + )), + _ => None, } +} - fn ticket(&self, urls: &DeliveryUrls) -> Option { - match self.document_type.as_str() { - DocumentType::VOIDED_DOCUMENTS => { - match self.voided_line_document_type_code.as_deref() { - Some(Catalog1::RETENCION) | Some(Catalog1::PERCEPCION) => Some(Delivery::SOAP( - urls.perception_retention.clone(), - SoapAction::GetStatus, - )), - Some(Catalog1::GUIA_REMISION_REMITENTE) => None, - Some(_) => Some(Delivery::SOAP(urls.invoice.clone(), SoapAction::GetStatus)), - _ => None, - } +pub fn verify_ticket_target( + document_type: &str, + voided_line_document_type_code: &Option, + urls: &Urls, +) -> Option { + match document_type { + DocumentType::VOIDED_DOCUMENTS => match voided_line_document_type_code.as_deref() { + Some(Catalog1::RETENCION) | Some(Catalog1::PERCEPCION) => { + Some(VerifyTicketTarget::Soap(urls.perception_retention.clone())) } - DocumentType::SUMMARY_DOCUMENTS => { - Some(Delivery::SOAP(urls.invoice.clone(), SoapAction::GetStatus)) - } - DocumentType::DESPATCH_ADVICE => Some(Delivery::REST( - urls.despatch.clone(), - RestAction::VerifyTicket, - )), + Some(Catalog1::GUIA_REMISION_REMITENTE) => None, + Some(_) => Some(VerifyTicketTarget::Soap(urls.invoice.clone())), _ => None, + }, + DocumentType::SUMMARY_DOCUMENTS => Some(VerifyTicketTarget::Soap(urls.invoice.clone())), + DocumentType::DESPATCH_ADVICE => Some(VerifyTicketTarget::Rest(urls.despatch.clone())), + _ => None, + } +} + +pub fn filename_formatted_without_extension( + document_type: &str, + document_id: &str, + ruc: &str, +) -> Option { + match document_type { + DocumentType::INVOICE => { + if FACTURA_SERIE_REGEX.is_match(document_id) { + Some(format!("{ruc}-{}-{document_id}", Catalog1::FACTURA)) + } else if BOLETA_SERIE_REGEX.is_match(document_id) { + Some(format!("{ruc}-{}-{document_id}", Catalog1::BOLETA)) + } else { + warn!("Could not build filename from Invoice"); + None + } + } + DocumentType::CREDIT_NOTE => { + Some(format!("{ruc}-{}-{document_id}", Catalog1::NOTA_CREDITO)) + } + DocumentType::DEBIT_NOTE => Some(format!("{ruc}-{}-{document_id}", Catalog1::NOTA_DEBITO)), + DocumentType::VOIDED_DOCUMENTS | DocumentType::SUMMARY_DOCUMENTS => { + Some(format!("{ruc}-{document_id}")) } + DocumentType::PERCEPTION => Some(format!("{ruc}-{}-{document_id}", Catalog1::PERCEPCION)), + DocumentType::RETENTION => Some(format!("{ruc}-{}-{document_id}", Catalog1::RETENCION)), + DocumentType::DESPATCH_ADVICE => { + if GUIA_REMISION_REMITENTE_SERIE_REGEX.is_match(document_id) { + Some(format!( + "{ruc}-{}-{document_id}", + Catalog1::GUIA_REMISION_REMITENTE + )) + } else if GUIA_REMISION_TRANSPORTISTA_SERIE_REGEX.is_match(document_id) { + Some(format!( + "{ruc}-{}-{document_id}", + Catalog1::GUIA_REMISION_TRANSPORTISTA + )) + } else { + warn!("Could not build filename from DespatchAdvice"); + None + } + } + _ => None, } } #[cfg(test)] mod tests { - use crate::analyzer::{ - Analyze, Catalog1, Delivery, DeliveryUrls, DocumentType, RestAction, SoapAction, + use crate::analyzer::{send_file_target, verify_ticket_target}; + use crate::models::{ + Catalog1, DocumentType, RestFileTargetAction, SendFileTarget, SoapFileTargetAction, Urls, + VerifyTicketTarget, }; - use crate::metadata::Metadata; + use crate::ubl_file::UblMetadata; - #[test] - fn unknow_document_type() { - let delivery_urls: DeliveryUrls = DeliveryUrls { + lazy_static::lazy_static! { + pub static ref DELIVERY_URLS: Urls = Urls { invoice: String::from("https://invoice"), perception_retention: String::from("https://perception_retention"), despatch: String::from("https://despatch"), }; + } - let metadata = Metadata { + #[test] + fn unknown_document_type() { + let metadata = UblMetadata { document_type: String::from("Unknown"), - document_id: Some(String::from("F001-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("F001-1"), + ruc: String::from("123456789012"), voided_line_document_type_code: None, }; - let document_delivery = metadata.target(&delivery_urls); - let ticket_delivery = metadata.ticket(&delivery_urls); + let document_delivery = send_file_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ); + let ticket_delivery = verify_ticket_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ); assert_eq!(document_delivery, None); assert_eq!(ticket_delivery, None); @@ -149,213 +155,277 @@ mod tests { #[test] fn invoice() { - let delivery_urls: DeliveryUrls = DeliveryUrls { - invoice: String::from("https://invoice"), - perception_retention: String::from("https://perception_retention"), - despatch: String::from("https://despatch"), - }; - - let metadata = Metadata { + let metadata = UblMetadata { document_type: String::from(DocumentType::INVOICE), - document_id: Some(String::from("F001-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("F001-1"), + ruc: String::from("123456789012"), voided_line_document_type_code: None, }; - let document_delivery = metadata.target(&delivery_urls).unwrap(); - let ticket_delivery = metadata.ticket(&delivery_urls); + let document_delivery = send_file_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); + let ticket_delivery = verify_ticket_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ); assert_eq!( document_delivery, - Delivery::SOAP(String::from("https://invoice"), SoapAction::SendBill) + SendFileTarget::Soap(String::from("https://invoice"), SoapFileTargetAction::Bill) ); assert_eq!(ticket_delivery, None); } #[test] fn credit_note() { - let delivery_urls: DeliveryUrls = DeliveryUrls { - invoice: String::from("https://invoice"), - perception_retention: String::from("https://perception_retention"), - despatch: String::from("https://despatch"), - }; - - let metadata = Metadata { + let metadata = UblMetadata { document_type: String::from(DocumentType::CREDIT_NOTE), - document_id: Some(String::from("FC01-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("FC01-1"), + ruc: String::from("123456789012"), voided_line_document_type_code: None, }; - let document_delivery = metadata.target(&delivery_urls).unwrap(); - let ticket_delivery = metadata.ticket(&delivery_urls); + let document_delivery = send_file_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); + let ticket_delivery = verify_ticket_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ); assert_eq!( document_delivery, - Delivery::SOAP(String::from("https://invoice"), SoapAction::SendBill) + SendFileTarget::Soap(String::from("https://invoice"), SoapFileTargetAction::Bill) ); assert_eq!(ticket_delivery, None); } #[test] fn debit_note() { - let delivery_urls: DeliveryUrls = DeliveryUrls { - invoice: String::from("https://invoice"), - perception_retention: String::from("https://perception_retention"), - despatch: String::from("https://despatch"), - }; - - let metadata = Metadata { + let metadata = UblMetadata { document_type: String::from(DocumentType::DEBIT_NOTE), - document_id: Some(String::from("FD01-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("FD01-1"), + ruc: String::from("123456789012"), voided_line_document_type_code: None, }; - let document_delivery = metadata.target(&delivery_urls).unwrap(); - let ticket_delivery = metadata.ticket(&delivery_urls); + let document_delivery = send_file_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); + let ticket_delivery = verify_ticket_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ); assert_eq!( document_delivery, - Delivery::SOAP(String::from("https://invoice"), SoapAction::SendBill) + SendFileTarget::Soap(String::from("https://invoice"), SoapFileTargetAction::Bill) ); assert_eq!(ticket_delivery, None); } #[test] fn voided_documents() { - let delivery_urls: DeliveryUrls = DeliveryUrls { - invoice: String::from("https://invoice"), - perception_retention: String::from("https://perception_retention"), - despatch: String::from("https://despatch"), - }; - // Invoice - let baja_invoice = Metadata { + let baja_invoice = UblMetadata { voided_line_document_type_code: Some(String::from(Catalog1::FACTURA)), document_type: String::from(DocumentType::VOIDED_DOCUMENTS), - document_id: Some(String::from("RA-20200328-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("RA-20200328-1"), + ruc: String::from("123456789012"), }; - let document_delivery = baja_invoice.target(&delivery_urls).unwrap(); - let ticket_delivery = baja_invoice.ticket(&delivery_urls).unwrap(); + let document_delivery = send_file_target( + &baja_invoice.document_type, + &baja_invoice.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); + let ticket_delivery = verify_ticket_target( + &baja_invoice.document_type, + &baja_invoice.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); assert_eq!( document_delivery, - Delivery::SOAP(String::from("https://invoice"), SoapAction::SendSummary) + SendFileTarget::Soap( + String::from("https://invoice"), + SoapFileTargetAction::Summary, + ) ); assert_eq!( ticket_delivery, - Delivery::SOAP(String::from("https://invoice"), SoapAction::GetStatus) + VerifyTicketTarget::Soap(String::from("https://invoice")) ); // CreditNote - let baja_credit_note = Metadata { + let baja_credit_note = UblMetadata { voided_line_document_type_code: Some(String::from(Catalog1::NOTA_CREDITO)), document_type: String::from(DocumentType::VOIDED_DOCUMENTS), - document_id: Some(String::from("RA-20200328-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("RA-20200328-1"), + ruc: String::from("123456789012"), }; - let document_delivery = baja_credit_note.target(&delivery_urls).unwrap(); - let ticket_delivery = baja_credit_note.ticket(&delivery_urls).unwrap(); + let document_delivery = send_file_target( + &baja_credit_note.document_type, + &baja_credit_note.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); + let ticket_delivery = verify_ticket_target( + &baja_credit_note.document_type, + &baja_credit_note.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); assert_eq!( document_delivery, - Delivery::SOAP(String::from("https://invoice"), SoapAction::SendSummary) + SendFileTarget::Soap( + String::from("https://invoice"), + SoapFileTargetAction::Summary, + ) ); assert_eq!( ticket_delivery, - Delivery::SOAP(String::from("https://invoice"), SoapAction::GetStatus) + VerifyTicketTarget::Soap(String::from("https://invoice")) ); // DebitNote - let baja_debit_note = Metadata { + let baja_debit_note = UblMetadata { voided_line_document_type_code: Some(String::from(Catalog1::NOTA_DEBITO)), document_type: String::from(DocumentType::VOIDED_DOCUMENTS), - document_id: Some(String::from("RA-20200328-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("RA-20200328-1"), + ruc: String::from("123456789012"), }; - let document_delivery = baja_debit_note.target(&delivery_urls).unwrap(); - let ticket_delivery = baja_debit_note.ticket(&delivery_urls).unwrap(); + let document_delivery = send_file_target( + &baja_debit_note.document_type, + &baja_debit_note.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); + let ticket_delivery = verify_ticket_target( + &baja_debit_note.document_type, + &baja_debit_note.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); assert_eq!( document_delivery, - Delivery::SOAP(String::from("https://invoice"), SoapAction::SendSummary) + SendFileTarget::Soap( + String::from("https://invoice"), + SoapFileTargetAction::Summary, + ) ); assert_eq!( ticket_delivery, - Delivery::SOAP(String::from("https://invoice"), SoapAction::GetStatus) + VerifyTicketTarget::Soap(String::from("https://invoice")) ); // Percepcion - let baja_percepcion = Metadata { + let baja_percepcion = UblMetadata { voided_line_document_type_code: Some(String::from(Catalog1::PERCEPCION)), document_type: String::from(DocumentType::VOIDED_DOCUMENTS), - document_id: Some(String::from("RA-20200328-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("RA-20200328-1"), + ruc: String::from("123456789012"), }; - let document_delivery = baja_percepcion.target(&delivery_urls).unwrap(); - let ticket_delivery = baja_percepcion.ticket(&delivery_urls).unwrap(); + let document_delivery = send_file_target( + &baja_percepcion.document_type, + &baja_percepcion.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); + let ticket_delivery = verify_ticket_target( + &baja_percepcion.document_type, + &baja_percepcion.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); assert_eq!( document_delivery, - Delivery::SOAP( + SendFileTarget::Soap( String::from("https://perception_retention"), - SoapAction::SendSummary + SoapFileTargetAction::Summary, ) ); assert_eq!( ticket_delivery, - Delivery::SOAP( - String::from("https://perception_retention"), - SoapAction::GetStatus - ) + VerifyTicketTarget::Soap(String::from("https://perception_retention")) ); // Retencion - let baja_retencion = Metadata { + let baja_retencion = UblMetadata { document_type: String::from(DocumentType::VOIDED_DOCUMENTS), - document_id: Some(String::from("RA-20200328-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("RA-20200328-1"), + ruc: String::from("123456789012"), voided_line_document_type_code: Some(String::from(Catalog1::RETENCION)), }; - let document_delivery = baja_retencion.target(&delivery_urls).unwrap(); - let ticket_delivery = baja_retencion.ticket(&delivery_urls).unwrap(); + let document_delivery = send_file_target( + &baja_retencion.document_type, + &baja_retencion.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); + let ticket_delivery = verify_ticket_target( + &baja_retencion.document_type, + &baja_retencion.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); assert_eq!( document_delivery, - Delivery::SOAP( + SendFileTarget::Soap( String::from("https://perception_retention"), - SoapAction::SendSummary + SoapFileTargetAction::Summary, ) ); assert_eq!( ticket_delivery, - Delivery::SOAP( - String::from("https://perception_retention"), - SoapAction::GetStatus - ) + VerifyTicketTarget::Soap(String::from("https://perception_retention")) ); // Guia - let baja_guia = Metadata { + let baja_guia = UblMetadata { document_type: String::from(DocumentType::VOIDED_DOCUMENTS), - document_id: Some(String::from("RA-20200328-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("RA-20200328-1"), + ruc: String::from("123456789012"), voided_line_document_type_code: Some(String::from(Catalog1::GUIA_REMISION_REMITENTE)), }; - let document_delivery = baja_guia.target(&delivery_urls); - let ticket_delivery = baja_guia.ticket(&delivery_urls); + let document_delivery = send_file_target( + &baja_guia.document_type, + &baja_guia.voided_line_document_type_code, + &DELIVERY_URLS, + ); + let ticket_delivery = verify_ticket_target( + &baja_guia.document_type, + &baja_guia.voided_line_document_type_code, + &DELIVERY_URLS, + ); assert_eq!(document_delivery, None); assert_eq!(ticket_delivery, None); @@ -363,55 +433,65 @@ mod tests { #[test] fn summary_documents() { - let delivery_urls: DeliveryUrls = DeliveryUrls { - invoice: String::from("https://invoice"), - perception_retention: String::from("https://perception_retention"), - despatch: String::from("https://despatch"), - }; - - let metadata = Metadata { + let metadata = UblMetadata { document_type: String::from(DocumentType::SUMMARY_DOCUMENTS), - document_id: Some(String::from("S001-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("S001-1"), + ruc: String::from("123456789012"), voided_line_document_type_code: None, }; - let document_delivery = metadata.target(&delivery_urls).unwrap(); - let ticket_delivery = metadata.ticket(&delivery_urls).unwrap(); + let document_delivery = send_file_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); + let ticket_delivery = verify_ticket_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); assert_eq!( document_delivery, - Delivery::SOAP(String::from("https://invoice"), SoapAction::SendSummary) + SendFileTarget::Soap( + String::from("https://invoice"), + SoapFileTargetAction::Summary, + ) ); assert_eq!( ticket_delivery, - Delivery::SOAP(String::from("https://invoice"), SoapAction::GetStatus) + VerifyTicketTarget::Soap(String::from("https://invoice")) ); } #[test] fn perception() { - let delivery_urls: DeliveryUrls = DeliveryUrls { - invoice: String::from("https://invoice"), - perception_retention: String::from("https://perception_retention"), - despatch: String::from("https://despatch"), - }; - - let metadata = Metadata { + let metadata = UblMetadata { document_type: String::from(DocumentType::PERCEPTION), - document_id: Some(String::from("S001-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("S001-1"), + ruc: String::from("123456789012"), voided_line_document_type_code: None, }; - let document_delivery = metadata.target(&delivery_urls).unwrap(); - let ticket_delivery = metadata.ticket(&delivery_urls); + let document_delivery = send_file_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); + let ticket_delivery = verify_ticket_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ); assert_eq!( document_delivery, - Delivery::SOAP( + SendFileTarget::Soap( String::from("https://perception_retention"), - SoapAction::SendBill + SoapFileTargetAction::Bill, ) ); assert_eq!(ticket_delivery, None); @@ -419,27 +499,30 @@ mod tests { #[test] fn retention() { - let delivery_urls: DeliveryUrls = DeliveryUrls { - invoice: String::from("https://invoice"), - perception_retention: String::from("https://perception_retention"), - despatch: String::from("https://despatch"), - }; - - let metadata = Metadata { + let metadata = UblMetadata { document_type: String::from(DocumentType::RETENTION), - document_id: Some(String::from("R001-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("R001-1"), + ruc: String::from("123456789012"), voided_line_document_type_code: None, }; - let document_delivery = metadata.target(&delivery_urls).unwrap(); - let ticket_delivery = metadata.ticket(&delivery_urls); + let document_delivery = send_file_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); + let ticket_delivery = verify_ticket_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ); assert_eq!( document_delivery, - Delivery::SOAP( + SendFileTarget::Soap( String::from("https://perception_retention"), - SoapAction::SendBill + SoapFileTargetAction::Bill, ) ); assert_eq!(ticket_delivery, None); @@ -447,29 +530,36 @@ mod tests { #[test] fn despatch_advise() { - let delivery_urls: DeliveryUrls = DeliveryUrls { - invoice: String::from("https://invoice"), - perception_retention: String::from("https://perception_retention"), - despatch: String::from("https://despatch"), - }; - - let metadata = Metadata { + let metadata = UblMetadata { document_type: String::from(DocumentType::DESPATCH_ADVICE), - document_id: Some(String::from("D001-1")), - ruc: Some(String::from("123456789012")), + document_id: String::from("D001-1"), + ruc: String::from("123456789012"), voided_line_document_type_code: None, }; - let document_delivery = metadata.target(&delivery_urls).unwrap(); - let ticket_delivery = metadata.ticket(&delivery_urls).unwrap(); + let document_delivery = send_file_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); + let ticket_delivery = verify_ticket_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &DELIVERY_URLS, + ) + .unwrap(); assert_eq!( document_delivery, - Delivery::REST(String::from("https://despatch"), RestAction::SendDocument) + SendFileTarget::Rest( + String::from("https://despatch"), + RestFileTargetAction::SendDocument, + ) ); assert_eq!( ticket_delivery, - Delivery::REST(String::from("https://despatch"), RestAction::VerifyTicket) + VerifyTicketTarget::Rest(String::from("https://despatch")) ); } } diff --git a/xsender/src/client_sunat.rs b/xsender/src/client_sunat.rs new file mode 100644 index 00000000..d588c1b2 --- /dev/null +++ b/xsender/src/client_sunat.rs @@ -0,0 +1,228 @@ +use std::str::FromStr; + +use crate::models::{ + Credentials, RestFileTargetAction, SendFileTarget, SoapFileTargetAction, VerifyTicketTarget, +}; +use crate::soap::cdr::{Cdr, CdrReadError}; +use crate::soap::envelope::{BodyData, EnvelopeData, FileData, ToStringXml}; +use crate::soap::send_file_response::{SendFileXmlResponse, SendFileXmlResponseFromStrError}; +use crate::soap::verify_ticket_response::{ + VerifyTicketXmlResponse, VerifyTicketXmlResponseFromStrError, +}; +use crate::zip_manager::{extract_cdr_from_base64_zip, ExtractCdrFromBase64ZipError}; + +#[derive(Default)] +pub struct ClientSUNAT {} + +pub struct File { + pub name: String, + pub base_64: String, +} + +#[derive(Debug)] +pub struct ErrorClientSUNAT { + pub kind: Layer, +} + +#[derive(Debug)] +pub enum Layer { + Templating(String), + Request(reqwest::Error), + ReadingResponse, +} + +pub enum SendFileResponse { + Ok(String, Cdr), + Ticket(String), + Error(String, String), +} + +pub enum VerifyTicketResponse { + Ok(String, Cdr, String), + Error(String, String), +} + +impl ClientSUNAT { + pub async fn send_file( + &self, + target: &SendFileTarget, + file: &File, + credentials: &Credentials, + ) -> Result { + match &target { + SendFileTarget::Soap(url, action) => { + let envelope_body = match action { + SoapFileTargetAction::Bill | SoapFileTargetAction::Pack => { + BodyData::SendBill(FileData { + filename: file.name.clone(), + file_content: file.base_64.clone(), + }) + } + SoapFileTargetAction::Summary => BodyData::SendSummary(FileData { + filename: file.name.clone(), + file_content: file.base_64.clone(), + }), + }; + + let envelope = EnvelopeData { + username: credentials.username.clone(), + password: credentials.password.clone(), + body: envelope_body, + }; + + let soap_action = match action { + SoapFileTargetAction::Bill => "urn:sendBill", + SoapFileTargetAction::Summary => "urn:sendSummary", + SoapFileTargetAction::Pack => "urn:sendPack", + }; + + let body = envelope.to_string_xml()?; + + let body = reqwest::Client::new() + .post(url) + .body(body) + .header("Content-Type", "text/xml; charset=utf-8") + .header("SOAPAction", soap_action) + .send() + .await? + .text() + .await?; + + let response_body = SendFileXmlResponse::from_str(&body)?; + + let result = match response_body { + SendFileXmlResponse::Cdr(cdr_base64) => { + let cdr_xml = extract_cdr_from_base64_zip(&cdr_base64)?; + let cdr = Cdr::from_str(&cdr_xml)?; + SendFileResponse::Ok(cdr_base64, cdr) + } + SendFileXmlResponse::Ticket(ticket) => SendFileResponse::Ticket(ticket), + SendFileXmlResponse::Fault(code, description) => { + SendFileResponse::Error(code, description) + } + }; + + Ok(result) + } + SendFileTarget::Rest(url, action) => { + let path = match action { + RestFileTargetAction::SendDocument => "/comprobantes", + }; + let _ = reqwest::Client::new() + .post(format!("{url}/{path}/{}", file.name)) + .body("test") + .header("Content-Type", "application/json") + .send() + .await? + .text() + .await?; + Ok(SendFileResponse::Ticket("".to_string())) + } + } + } + + pub async fn verify_ticket( + &self, + target: &VerifyTicketTarget, + ticket: &str, + credentials: &Credentials, + ) -> Result { + match &target { + VerifyTicketTarget::Soap(url) => { + let envelope = EnvelopeData { + username: credentials.username.clone(), + password: credentials.password.clone(), + body: BodyData::VerifyTicket(ticket.to_string()), + }; + + let body = envelope.to_string_xml()?; + + let body = reqwest::Client::new() + .post(url) + .body(body) + .header("Content-Type", "text/xml; charset=utf-8") + .header("SOAPAction", "urn:getStatus") + .send() + .await? + .text() + .await?; + + let response_body = VerifyTicketXmlResponse::from_str(&body)?; + + match response_body { + VerifyTicketXmlResponse::Cdr(cdr_base64, status_code) => { + let cdr_xml = extract_cdr_from_base64_zip(&cdr_base64)?; + let cdr = Cdr::from_str(&cdr_xml)?; + let result = VerifyTicketResponse::Ok(cdr_base64, cdr, status_code); + + Ok(result) + } + VerifyTicketXmlResponse::Fault(code, description) => { + let result = VerifyTicketResponse::Error(code, description); + + Ok(result) + } + } + } + VerifyTicketTarget::Rest(url) => { + let _ = reqwest::Client::new() + .post(format!("{url}/ticket")) + .body("test") + .header("Content-Type", "application/json") + .send() + .await? + .text() + .await?; + Ok(VerifyTicketResponse::Error("".to_string(), "".to_string())) + } + } + } +} + +impl From for ErrorClientSUNAT { + fn from(error: reqwest::Error) -> Self { + Self { + kind: Layer::Request(error), + } + } +} + +impl From for ErrorClientSUNAT { + fn from(value: tera::Error) -> Self { + Self { + kind: Layer::Templating(value.to_string()), + } + } +} + +impl From for ErrorClientSUNAT { + fn from(_: SendFileXmlResponseFromStrError) -> Self { + Self { + kind: Layer::ReadingResponse, + } + } +} + +impl From for ErrorClientSUNAT { + fn from(_: VerifyTicketXmlResponseFromStrError) -> Self { + Self { + kind: Layer::ReadingResponse, + } + } +} + +impl From for ErrorClientSUNAT { + fn from(_: ExtractCdrFromBase64ZipError) -> Self { + Self { + kind: Layer::ReadingResponse, + } + } +} + +impl From for ErrorClientSUNAT { + fn from(_: CdrReadError) -> Self { + Self { + kind: Layer::ReadingResponse, + } + } +} diff --git a/xsender/src/file_sender.rs b/xsender/src/file_sender.rs new file mode 100644 index 00000000..7bdce3a8 --- /dev/null +++ b/xsender/src/file_sender.rs @@ -0,0 +1,121 @@ +use base64::engine::general_purpose; +use base64::Engine; +use zip::result::ZipError; + +use crate::analyzer::{ + filename_formatted_without_extension, send_file_target, verify_ticket_target, +}; +use crate::client_sunat::{ + ClientSUNAT, ErrorClientSUNAT, File, SendFileResponse, VerifyTicketResponse, +}; +use crate::models::{Credentials, SendFileTarget, Urls, VerifyTicketTarget}; +use crate::ubl_file::{UblFile, UblMetadataError}; +use crate::zip_manager::create_zip; + +pub struct FileSender { + pub urls: Urls, + pub credentials: Credentials, +} + +#[derive(Debug)] +pub struct FileSenderError { + pub message: String, + pub client_error: Option, +} + +pub struct SendFileResponseWrapper { + pub data: SendFileResponse, + pub send_file_target: SendFileTarget, + pub verify_ticket_target: Option, +} + +impl FileSender { + pub async fn send_file( + &self, + xml: &UblFile, + ) -> Result { + let metadata = xml.metadata()?; + + let filename_without_extension = filename_formatted_without_extension( + &metadata.document_type, + &metadata.document_id, + &metadata.ruc, + ) + .ok_or(FileSenderError { + message: "Could not determine the filename of the file to be sent".to_string(), + client_error: None, + })?; + + let send_target = send_file_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &self.urls, + ) + .ok_or(FileSenderError { + message: "Could not determine the target for the file to be sent".to_string(), + client_error: None, + })?; + let verify_ticket_target = verify_ticket_target( + &metadata.document_type, + &metadata.voided_line_document_type_code, + &self.urls, + ); + + let zip = create_zip(&filename_without_extension, &xml.file_content.clone())?; + let zip_base64 = general_purpose::STANDARD.encode(zip); + + let file_to_be_sent = File { + name: format!("{filename_without_extension}.zip"), + base_64: zip_base64, + }; + + let client = ClientSUNAT::default(); + let result = client + .send_file(&send_target, &file_to_be_sent, &self.credentials) + .await?; + + Ok(SendFileResponseWrapper { + data: result, + send_file_target: send_target, + verify_ticket_target, + }) + } + + pub async fn verify_ticket( + &self, + target: &VerifyTicketTarget, + ticket: &str, + ) -> Result { + let client = ClientSUNAT::default(); + client + .verify_ticket(target, ticket, &self.credentials) + .await + } +} + +impl From for FileSenderError { + fn from(value: UblMetadataError) -> Self { + Self { + message: value.message.to_string(), + client_error: None, + } + } +} + +impl From for FileSenderError { + fn from(value: ZipError) -> Self { + Self { + message: value.to_string(), + client_error: None, + } + } +} + +impl From for FileSenderError { + fn from(value: ErrorClientSUNAT) -> Self { + Self { + message: "Error while sending the file".to_string(), + client_error: Some(value), + } + } +} diff --git a/xsender/src/global.rs b/xsender/src/global.rs new file mode 100644 index 00000000..372c3704 --- /dev/null +++ b/xsender/src/global.rs @@ -0,0 +1,24 @@ +use regex::Regex; +use tera::Tera; + +pub const CBC_NS: &str = "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"; +pub const CAC_NS: &str = "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"; +pub const SAC_NS: &str = + "urn:sunat:names:specification:ubl:peru:schema:xsd:SunatAggregateComponents-1"; + +lazy_static::lazy_static! { + pub static ref TEMPLATES: Tera = { + match Tera::new("src/templates/*.xml") { + Ok(t) => t, + Err(e) => { + println!("Parsing error(s): {}", e); + ::std::process::exit(1); + } + } + }; + + pub static ref FACTURA_SERIE_REGEX: Regex = Regex::new("^[F|f].*$").expect("Invalid FACTURA_SERIE_REGEX"); + pub static ref BOLETA_SERIE_REGEX: Regex = Regex::new("^[B|f].*$").expect("Invalid BOLETA_SERIE_REGEX"); + pub static ref GUIA_REMISION_REMITENTE_SERIE_REGEX: Regex = Regex::new("^[T|t].*$").expect("Invalid GUIA_REMISION_REMITENTE_SERIE_REGEX"); + pub static ref GUIA_REMISION_TRANSPORTISTA_SERIE_REGEX: Regex = Regex::new("^[V|v].*$").expect("Invalid GUIA_REMISION_TRANSPORTISTA_SERIE_REGEX"); +} diff --git a/xsender/src/lib.rs b/xsender/src/lib.rs index b2a15b36..e21e4b11 100644 --- a/xsender/src/lib.rs +++ b/xsender/src/lib.rs @@ -1,4 +1,9 @@ -pub mod analyzer; -pub mod message_renderer; -pub mod metadata; -pub mod xml; +mod analyzer; +mod client_sunat; +mod file_sender; +mod global; +mod models; +pub mod prelude; +mod soap; +mod ubl_file; +mod zip_manager; diff --git a/xsender/src/models.rs b/xsender/src/models.rs new file mode 100644 index 00000000..ee80a5f8 --- /dev/null +++ b/xsender/src/models.rs @@ -0,0 +1,76 @@ +/// Set of URL where the company should send his documents +#[derive(Clone)] +pub struct Urls { + pub invoice: String, + pub perception_retention: String, + pub despatch: String, +} + +/// Credentials of the company for sending documents +#[derive(Clone)] +pub struct Credentials { + pub username: String, + pub password: String, +} + +/// Defines where the document should be sent +#[derive(Debug, PartialEq)] +pub enum SendFileTarget { + /// URL and Method + Soap(String, SoapFileTargetAction), + + /// URL and Method + Rest(String, RestFileTargetAction), +} + +/// Defines where the document should be sent +#[derive(Debug, PartialEq)] +pub enum VerifyTicketTarget { + /// URL and Method + Soap(String), + + /// URL and Method + Rest(String), +} + +#[derive(Debug, PartialEq)] +pub enum SoapFileTargetAction { + /// Sends a single file + Bill, + /// Send summary-documents or voided-documents + Summary, + /// Sends a set of files all together + Pack, +} + +#[derive(Debug, PartialEq)] +pub enum RestFileTargetAction { + SendDocument, +} + +pub struct DocumentType {} + +impl DocumentType { + pub const INVOICE: &'static str = "Invoice"; + pub const CREDIT_NOTE: &'static str = "CreditNote"; + pub const DEBIT_NOTE: &'static str = "DebitNote"; + pub const VOIDED_DOCUMENTS: &'static str = "VoidedDocuments"; + pub const SUMMARY_DOCUMENTS: &'static str = "SummaryDocuments"; + pub const DESPATCH_ADVICE: &'static str = "DespatchAdvise"; + pub const PERCEPTION: &'static str = "Perception"; + pub const RETENTION: &'static str = "Retention"; +} + +pub struct Catalog1 {} + +#[allow(dead_code)] +impl Catalog1 { + pub const FACTURA: &'static str = "01"; + pub const BOLETA: &'static str = "03"; + pub const NOTA_CREDITO: &'static str = "07"; + pub const NOTA_DEBITO: &'static str = "08"; + pub const GUIA_REMISION_REMITENTE: &'static str = "09"; + pub const RETENCION: &'static str = "20"; + pub const GUIA_REMISION_TRANSPORTISTA: &'static str = "31"; + pub const PERCEPCION: &'static str = "40"; +} diff --git a/xsender/src/prelude.rs b/xsender/src/prelude.rs new file mode 100644 index 00000000..8290f5c5 --- /dev/null +++ b/xsender/src/prelude.rs @@ -0,0 +1,4 @@ +pub use crate::client_sunat::*; +pub use crate::file_sender::*; +pub use crate::models::*; +pub use crate::ubl_file::*; diff --git a/xsender/src/soap/cdr.rs b/xsender/src/soap/cdr.rs new file mode 100644 index 00000000..ec047a03 --- /dev/null +++ b/xsender/src/soap/cdr.rs @@ -0,0 +1,158 @@ +use std::str::FromStr; + +use xml::name::OwnedName; +use xml::reader::XmlEvent; +use xml::EventReader; + +use crate::global::{CAC_NS, CBC_NS}; + +pub struct Cdr { + pub response_code: String, + pub description: String, + pub notes: Vec, +} + +#[derive(Debug)] +pub struct CdrReadError { + pub message: String, +} + +impl FromStr for Cdr { + type Err = CdrReadError; + + fn from_str(s: &str) -> Result { + let event_reader = EventReader::from_str(s); + + enum Wrapper { + Response, + Note, + } + + let mut current_wrapper: Option = None; + let mut current_text: String = String::from(""); + + // Send bill data + let mut response_code: Option = None; + let mut description: Option = None; + + // Send summary data + let mut notes: Vec = Vec::new(); + + fn set_wrapper( + is_start: bool, + name: &OwnedName, + mut current_wrapper: Option, + ) -> Option { + let namespace = name.namespace.as_deref(); + let prefix = name.prefix.as_deref(); + let local_name = name.local_name.as_str(); + + match (namespace, prefix, local_name) { + (Some(CAC_NS), Some("cac"), "Response") => { + current_wrapper = if is_start { + Some(Wrapper::Response) + } else { + None + } + } + (Some(CBC_NS), Some("cbc"), "Note") => { + current_wrapper = if is_start { Some(Wrapper::Note) } else { None } + } + _ => {} + }; + + current_wrapper + } + + for event in event_reader { + match event { + Ok(XmlEvent::StartElement { name, .. }) => { + current_wrapper = set_wrapper(true, &name, current_wrapper); + } + Ok(XmlEvent::EndElement { name }) => { + let namespace = name.namespace.as_deref(); + let prefix = name.prefix.as_deref(); + let local_name = name.local_name.as_str(); + + match (¤t_wrapper, &namespace, &prefix, local_name) { + (Some(Wrapper::Response), Some(CBC_NS), Some("cbc"), "ResponseCode") => { + response_code = Some(current_text.clone()); + } + (Some(Wrapper::Response), Some(CBC_NS), Some("cbc"), "Description") => { + description = Some(current_text.clone()); + } + (Some(Wrapper::Note), Some(CBC_NS), Some("cbc"), "Note") => { + notes.push(current_text.clone()); + } + _ => {} + }; + + current_wrapper = set_wrapper(false, &name, current_wrapper); + } + Ok(XmlEvent::Characters(characters)) => { + current_text = characters; + } + Err(_) => { + break; + } + _ => {} + } + } + + match (response_code, description) { + (Some(response_code), Some(description)) => Ok(Self { + description, + response_code, + notes, + }), + _ => Err(CdrReadError { + message: "Could not find response_code and description inside the xml".to_string(), + }), + } + } +} + +#[cfg(test)] +mod tests { + use std::fs; + use std::str::FromStr; + + use crate::soap::cdr::Cdr; + + const RESOURCES: &str = "resources/test"; + + #[test] + fn read_cdr_without_notes() { + let file_content = + fs::read_to_string(&format!("{RESOURCES}/R-12345678901-01-F001-00000587.xml")) + .expect("Could not read file"); + let cdr = Cdr::from_str(&file_content).expect("Could not read Cdr"); + + assert_eq!(cdr.response_code, "0"); + assert_eq!( + cdr.description, + "La Factura numero F001-00000587, ha sido aceptada" + ); + assert!(cdr.notes.is_empty()); + } + + #[test] + fn read_cdr_with_notes() { + let file_content = + fs::read_to_string(&format!("{RESOURCES}/R-20220557805-01-F001-22Openubl.xml")) + .expect("Could not read file"); + let cdr = Cdr::from_str(&file_content).expect("Could not read Cdr"); + + assert_eq!(cdr.response_code, "0"); + assert_eq!( + cdr.description, + "La Factura numero F001-22, ha sido aceptada" + ); + assert_eq!(cdr.notes.len(), 8); + assert!(cdr + .notes + .last() + .unwrap() + .starts_with("4252 - El dato ingresado como atributo @listName es incorrecto.")); + } +} diff --git a/xsender/src/message_renderer.rs b/xsender/src/soap/envelope.rs similarity index 72% rename from xsender/src/message_renderer.rs rename to xsender/src/soap/envelope.rs index a8f792b7..85063b69 100644 --- a/xsender/src/message_renderer.rs +++ b/xsender/src/soap/envelope.rs @@ -1,13 +1,16 @@ use serde::Serialize; -use tera::{Context, Tera}; +use tera::{Context, Result}; -pub struct SoapMessage { +use crate::global::TEMPLATES; + +pub struct EnvelopeData { pub username: String, pub password: String, - pub body: SoapBody, + pub body: BodyData, } -pub enum SoapBody { +#[allow(dead_code)] +pub enum BodyData { SendBill(FileData), SendSummary(FileData), VerifyTicket(String), @@ -42,50 +45,38 @@ pub struct DocumentCdpData { importe_total: String, } -pub trait RendererSoap { - fn render(&self) -> tera::Result; +pub trait ToStringXml { + fn to_string_xml(&self) -> Result; } -lazy_static::lazy_static! { - pub static ref TEMPLATES: Tera = { - match Tera::new("src/templates/*.xml") { - Ok(t) => t, - Err(e) => { - println!("Parsing error(s): {}", e); - ::std::process::exit(1); - } - } - }; -} - -impl RendererSoap for SoapMessage { - fn render(&self) -> tera::Result { +impl ToStringXml for EnvelopeData { + fn to_string_xml(&self) -> Result { let mut context = Context::new(); context.insert("username", &self.username); context.insert("password", &self.password); match &self.body { - SoapBody::SendBill(file_data) => { + BodyData::SendBill(file_data) => { context.insert("body", &file_data); TEMPLATES.render("send_bill.xml", &context) } - SoapBody::SendSummary(file_data) => { + BodyData::SendSummary(file_data) => { context.insert("body", &file_data); TEMPLATES.render("send_summary.xml", &context) } - SoapBody::VerifyTicket(ticket) => { + BodyData::VerifyTicket(ticket) => { context.insert("ticket", ticket); TEMPLATES.render("verify_ticket.xml", &context) } - SoapBody::GetStatusCrd(document_data) => { + BodyData::GetStatusCrd(document_data) => { context.insert("body", document_data); TEMPLATES.render("get_status_crd.xml", &context) } - SoapBody::ValidateCdpCriterios(document_data) => { + BodyData::ValidateCdpCriterios(document_data) => { context.insert("body", document_data); TEMPLATES.render("validate_cdp_criterios.xml", &context) } - SoapBody::ValidateFile(file_data) => { + BodyData::ValidateFile(file_data) => { context.insert("body", &file_data); TEMPLATES.render("validate_file.xml", &context) } @@ -95,16 +86,14 @@ impl RendererSoap for SoapMessage { #[cfg(test)] mod tests { - use crate::message_renderer::SoapBody::{ + use crate::soap::envelope::BodyData::{ GetStatusCrd, SendBill, SendSummary, ValidateCdpCriterios, ValidateFile, VerifyTicket, }; - use crate::message_renderer::{ - DocumentCdpData, DocumentData, FileData, RendererSoap, SoapMessage, - }; + use crate::soap::envelope::*; #[test] fn send_bill() { - let message = SoapMessage { + let message = EnvelopeData { username: String::from("my_username"), password: String::from("my_password"), body: SendBill(FileData { @@ -113,13 +102,12 @@ mod tests { }), }; - let result = message.render(); - assert!(result.is_ok()); + assert!(message.to_string_xml().unwrap().len() > 1); } #[test] fn send_summary() { - let message = SoapMessage { + let message = EnvelopeData { username: String::from("my_username"), password: String::from("my_password"), body: SendSummary(FileData { @@ -128,25 +116,23 @@ mod tests { }), }; - let result = message.render(); - assert!(result.is_ok()); + assert!(message.to_string_xml().unwrap().len() > 1); } #[test] fn verify_ticket() { - let message = SoapMessage { + let message = EnvelopeData { username: String::from("my_username"), password: String::from("my_password"), body: VerifyTicket(String::from("my_ticket")), }; - let result = message.render(); - assert!(result.is_ok()); + assert!(message.to_string_xml().unwrap().len() > 1); } #[test] fn get_status_crd() { - let message = SoapMessage { + let message = EnvelopeData { username: String::from("my_username"), password: String::from("my_password"), body: GetStatusCrd(DocumentData { @@ -157,13 +143,12 @@ mod tests { }), }; - let result = message.render(); - assert!(result.is_ok()); + assert!(message.to_string_xml().unwrap().len() > 1); } #[test] fn validate_cdp_criterios() { - let message = SoapMessage { + let message = EnvelopeData { username: String::from("my_username"), password: String::from("my_password"), body: ValidateCdpCriterios(DocumentCdpData { @@ -178,13 +163,12 @@ mod tests { }), }; - let result = message.render(); - assert!(result.is_ok()); + assert!(message.to_string_xml().unwrap().len() > 1); } #[test] fn validate_file() { - let message = SoapMessage { + let message = EnvelopeData { username: String::from("my_username"), password: String::from("my_password"), body: ValidateFile(FileData { @@ -193,7 +177,6 @@ mod tests { }), }; - let result = message.render(); - assert!(result.is_ok()); + assert!(message.to_string_xml().unwrap().len() > 1); } } diff --git a/xsender/src/soap/mod.rs b/xsender/src/soap/mod.rs new file mode 100644 index 00000000..3cad0ad5 --- /dev/null +++ b/xsender/src/soap/mod.rs @@ -0,0 +1,4 @@ +pub mod cdr; +pub mod envelope; +pub mod send_file_response; +pub mod verify_ticket_response; diff --git a/xsender/src/soap/send_file_response.rs b/xsender/src/soap/send_file_response.rs new file mode 100644 index 00000000..f512b615 --- /dev/null +++ b/xsender/src/soap/send_file_response.rs @@ -0,0 +1,185 @@ +use std::str::FromStr; + +use xml::name::OwnedName; +use xml::reader::XmlEvent; +use xml::EventReader; + +pub enum SendFileXmlResponse { + /// String contains the base64 CDR zip file + Cdr(String), + + /// String contains the ticket code + Ticket(String), + + /// (Code, Message) + Fault(String, String), +} + +#[derive(Debug)] +pub struct SendFileXmlResponseFromStrError {} + +impl FromStr for SendFileXmlResponse { + type Err = SendFileXmlResponseFromStrError; + + fn from_str(s: &str) -> Result { + let event_reader = EventReader::from_str(s); + + enum Wrapper { + SendBillResponse, + SendSummaryResponse, + Fault, + } + + let mut current_wrapper: Option = None; + let mut current_text: String = String::from(""); + + // Send bill data + let mut application_response: Option = None; + + // Send summary data + let mut ticket: Option = None; + + // Fault data + let mut fault_code: Option = None; + let mut fault_message: Option = None; + + fn set_wrapper( + is_start: bool, + name: &OwnedName, + mut current_wrapper: Option, + ) -> Option { + let prefix = name.prefix.as_deref(); + let local_name = name.local_name.as_str(); + + match (prefix, local_name) { + (Some("br"), "sendBillResponse") => { + current_wrapper = if is_start { + Some(Wrapper::SendBillResponse) + } else { + None + } + } + (Some("br"), "sendSummaryResponse") => { + current_wrapper = if is_start { + Some(Wrapper::SendSummaryResponse) + } else { + None + } + } + (Some("soap-env"), "Fault") => { + current_wrapper = if is_start { Some(Wrapper::Fault) } else { None } + } + _ => {} + }; + + current_wrapper + } + + for event in event_reader { + match event { + Ok(XmlEvent::StartElement { name, .. }) => { + current_wrapper = set_wrapper(true, &name, current_wrapper); + } + Ok(XmlEvent::EndElement { name }) => { + let local_name = name.local_name.as_str(); + + match (¤t_wrapper, local_name) { + (Some(Wrapper::SendBillResponse), "applicationResponse") => { + application_response = Some(current_text.clone()); + } + (Some(Wrapper::SendSummaryResponse), "ticket") => { + ticket = Some(current_text.clone()); + } + (Some(Wrapper::Fault), "faultcode") => { + fault_code = Some(current_text.clone()); + } + (Some(Wrapper::Fault), "faultstring") => { + fault_message = Some(current_text.clone()); + } + _ => {} + }; + + current_wrapper = set_wrapper(false, &name, current_wrapper); + } + Ok(XmlEvent::Characters(characters)) => { + current_text = characters; + } + Err(_) => { + break; + } + _ => {} + } + } + + match (application_response, ticket, fault_code, fault_message) { + (Some(application_response), _, _, _) => Ok(Self::Cdr(application_response)), + (_, Some(ticket), _, _) => Ok(Self::Ticket(ticket)), + (_, _, Some(fault_code), Some(fault_message)) => { + Ok(Self::Fault(fault_code, fault_message)) + } + _ => Err(SendFileXmlResponseFromStrError {}), + } + } +} + +#[cfg(test)] +mod tests { + use std::fs; + use std::str::FromStr; + + use crate::soap::send_file_response::SendFileXmlResponse; + + const RESOURCES: &str = "resources/test"; + + #[test] + fn read_ok_response() { + let file_content = fs::read_to_string(&format!("{RESOURCES}/bill_service_response_ok.xml")) + .expect("Could not read file"); + let response = + SendFileXmlResponse::from_str(&file_content).expect("Could not read response"); + + let cdr = match response { + SendFileXmlResponse::Cdr(cdr_base64) => cdr_base64, + SendFileXmlResponse::Ticket(_) => "".to_string(), + SendFileXmlResponse::Fault(_, _) => "".to_string(), + }; + + let expected_cdr = "UEsDBBQAAgAIAEwklVcAAAAAAgAAAAAAAAAGAAAAZHVtbXkvAwBQSwMEFAACAAgATCSVVwwGkuIxBAAAGQ0AABsAAABSLTEyMzQ1Njc4OTEyLTAxLUYwMDEtMS54bWy1V2FP2zwQ/r5fEZUPk6Y3OElbWKPQqVDGsgFitDC0byY52uhN7WA7peXXv+e4SdMStHbSq/LBuXvuubvHZ1sEXxaz1JqDkAlnJy330GlZwCIeJ2xy0robf7U/t770PwRU+IMsS5OIKgTegsw4k2BhMJMnrVwwn1OZSJ/RGUhfZhAlTyuwnz+mvoymMKP+QsZ+yOY8icD2Wibcp2JPhoZK1mywUHvSnfHZjLPzhQKmVcBPpASm5Jo0eoz+ivQU4VEjIf07wsFkImBCFTSRxrgVU6Uyn5CXl5fDl/YhFxPiOY5DnB5BTCyTyUGJlpxmFd4kkofo0vYiUC8IsDmkPANSJcHkVRgsZKoKsDZLm7LYVgn2UiUp+5Q5o+rdPjMQeb3ZkUY39eqWxIv3enXJw9XlqKAqscgCi6yhaHTkKRU2egVIvfmy1Q9wgvy708tqIGQ55g0+Y6nNDsOV6gejZIId5KI6IjvsCx4zHQZxyJ54/4NlBWeUcYY6pclrodUVqCmPrUE64SJR09m7EriOpsW+IjtyO+zgF6L1AGkNW6TgrircmdTplLXaMy7gQEhqyyntut6K8haeQODtAdbdbajlQiOax4Iy+cTFTBpD3fTHtBsSlcMY27Ks3qTek3QXgZCQbFceDJMJSLWnYqjIQV2niueepjn0H16PRPsBEvXt+ntGw3m4hMvrQXZ+OoB0zKYv98+/w2V4/hS/dm/uur+PB/C8OLvoEX41ypxzGf5KLn9cPF9E38/vczn8OVVikRz9PDkJSD2L3h9SbRCOGtmctfpEmIhPNyKZ4+mz/oWl9fEUFL3Bo4rXGQj10WJcWXn2ydDUooIfsCw4g4eu0xtSRc1KR5kzj8zXeA3EVrQ2rfhNQmSo8W8HF2yhlDmIEYiEpnWLJt6fvhZbcBne63z2CGJ/to3oeoKyXLJWhlRqrXXEdfOdQt5ePm9Msh/gW6VN9+ZND4d979AJyBtrgTvLpeKz1e2CRreEbjsKtAYcO20XJ/mo2z72DLTy6iaHeos8x+3Zrmd7nbHj+MXfClpB1hFjfC76DbDCXsDKN37F7bULbtdgN5wbcEPc8dsd3+tuglfcNPJrqq960ZbR3fVgXOuuAnKxvKFCLY2tWIYxbk71mlU0qEEbf16v210TkfejSoeZQh1QrGqVGA/ZQpL3isPDnyiaVg0OlKLRdFZMkvbrkRGMpus7wUzObdg/2NJA20yihiDyp2RkW2f9CSwG8f9ISRoT3EIEyXznnK7X7nSPjj/3XG/nnA0phjzKtQrl4JW1VF/FUK60xBRf8dWwq8le2zcG+4zHONibE13YCtQQZCSSrKjrklpfaYSyU4thHYJbJsE/1pRaMom5RSPIFI2pYavHlj3VC1+3szEzW4VXUjXBjU5JlqB9x704wvNe/fbZjY0spHk/SPN/Nv3/AFBLAQIAABQAAgAIAEwklVcAAAAAAgAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAABkdW1teS9QSwECAAAUAAIACABMJJVXDAaS4jEEAAAZDQAAGwAAAAAAAAABAAAAAAAmAAAAUi0xMjM0NTY3ODkxMi0wMS1GMDAxLTEueG1sUEsFBgAAAAACAAIAfQAAAJAEAAAAAA=="; + assert_eq!(expected_cdr, cdr); + } + + #[test] + fn read_fault_response() { + let file_content = + fs::read_to_string(&format!("{RESOURCES}/bill_service_response_fault.xml")) + .expect("Could not read file"); + let response = + SendFileXmlResponse::from_str(&file_content).expect("Could not read response"); + + let fault = match response { + SendFileXmlResponse::Cdr(_) => ("".to_string(), "".to_string()), + SendFileXmlResponse::Ticket(_) => ("".to_string(), "".to_string()), + SendFileXmlResponse::Fault(error, description) => (error, description), + }; + + assert_eq!("soap-env:Client.0111", fault.0); + assert_eq!("No tiene el perfil para enviar comprobantes electronicos - Detalle: Rejected by policy.", fault.1); + } + + #[test] + fn read_ticket_response() { + let file_content = + fs::read_to_string(&format!("{RESOURCES}/bill_service_response_ticket.xml")) + .expect("Could not read file"); + let response = + SendFileXmlResponse::from_str(&file_content).expect("Could not read response"); + + let ticket = match response { + SendFileXmlResponse::Cdr(_) => "".to_string(), + SendFileXmlResponse::Ticket(ticket) => ticket, + SendFileXmlResponse::Fault(_, _) => "".to_string(), + }; + + assert_eq!("1703154974517", ticket); + } +} diff --git a/xsender/src/soap/verify_ticket_response.rs b/xsender/src/soap/verify_ticket_response.rs new file mode 100644 index 00000000..5c666432 --- /dev/null +++ b/xsender/src/soap/verify_ticket_response.rs @@ -0,0 +1,136 @@ +use std::str::FromStr; + +use xml::name::OwnedName; +use xml::reader::XmlEvent; +use xml::EventReader; + +pub enum VerifyTicketXmlResponse { + /// String contains the base64 CDR zip file + Cdr(String, String), + + /// (Code, Message) + Fault(String, String), +} + +#[derive(Debug)] +pub struct VerifyTicketXmlResponseFromStrError {} + +impl FromStr for VerifyTicketXmlResponse { + type Err = VerifyTicketXmlResponseFromStrError; + + fn from_str(s: &str) -> Result { + let event_reader = EventReader::from_str(s); + + enum Wrapper { + StatusResponse, + Fault, + } + + let mut current_wrapper: Option = None; + let mut current_text: String = String::from(""); + + // Send bill data + let mut content: Option = None; + let mut status_code: Option = None; + + // Fault data + let mut fault_code: Option = None; + let mut fault_message: Option = None; + + fn set_wrapper( + is_start: bool, + name: &OwnedName, + mut current_wrapper: Option, + ) -> Option { + let prefix = name.prefix.as_deref(); + let local_name = name.local_name.as_str(); + + match (prefix, local_name) { + (Some("ns2"), "getStatusResponse") => { + current_wrapper = if is_start { + Some(Wrapper::StatusResponse) + } else { + None + } + } + (Some("soap-env"), "Fault") => { + current_wrapper = if is_start { Some(Wrapper::Fault) } else { None } + } + _ => {} + }; + + current_wrapper + } + + for event in event_reader { + match event { + Ok(XmlEvent::StartElement { name, .. }) => { + current_wrapper = set_wrapper(true, &name, current_wrapper); + } + Ok(XmlEvent::EndElement { name }) => { + let local_name = name.local_name.as_str(); + + match (¤t_wrapper, local_name) { + (Some(Wrapper::StatusResponse), "content") => { + content = Some(current_text.clone()); + } + (Some(Wrapper::StatusResponse), "statusCode") => { + status_code = Some(current_text.clone()); + } + (Some(Wrapper::Fault), "faultcode") => { + fault_code = Some(current_text.clone()); + } + (Some(Wrapper::Fault), "faultstring") => { + fault_message = Some(current_text.clone()); + } + _ => {} + }; + + current_wrapper = set_wrapper(false, &name, current_wrapper); + } + Ok(XmlEvent::Characters(characters)) => { + current_text = characters; + } + Err(_) => { + break; + } + _ => {} + } + } + + match (content, status_code, fault_code, fault_message) { + (Some(content), Some(status_code), _, _) => Ok(Self::Cdr(content, status_code)), + (_, _, Some(fault_code), Some(fault_message)) => { + Ok(Self::Fault(fault_code, fault_message)) + } + _ => Err(VerifyTicketXmlResponseFromStrError {}), + } + } +} + +#[cfg(test)] +mod tests { + use std::fs; + use std::str::FromStr; + + use crate::soap::verify_ticket_response::VerifyTicketXmlResponse; + + const RESOURCES: &str = "resources/test"; + + #[test] + fn read_ok_response() { + let file_content = fs::read_to_string(&format!("{RESOURCES}/get_status_response_ok.xml")) + .expect("Could not read file"); + let response = + VerifyTicketXmlResponse::from_str(&file_content).expect("Could not read response"); + + let result = match response { + VerifyTicketXmlResponse::Cdr(cdr_base64, status_code) => (cdr_base64, status_code), + VerifyTicketXmlResponse::Fault(_, _) => ("".to_string(), "".to_string()), + }; + + let expected_cdr = "UEsDBBQAAgAIAK07lVcAAAAAAgAAAAAAAAAGAAAAZHVtbXkvAwBQSwMEFAACAAgArTuVV2PumuIyBAAACA0AAB8AAABSLTEyMzQ1Njc4OTEyLVJBLTIwMjAwMzI4LTEueG1stVdRT+M4EH7fXxGVh5X2LjhJoYUoZFUo3FULPba0gPbNJEMbLrFzttMWfv2OkyZNS9C2K53gwZn55puZz2MbvK/LJDbmIGTE2VnLPrRaBrCAhxGbnrUm4yvzpPXV/+RR4fbSNI4CqhA4AplyJsHAYCbPWplgLqcyki6jCUhXphBEzyuwmz3FrgxmkFB3KUN3wOY8CsB0WkW4S8WeDA2VrNlgqfaku+BJwtnlUgHTKuAnUgJTck0aPAW/RXqO8KCRkP4eYW86FTClCppIQ9yKmVKpS8hisThctA+5mBLHsixinRLEhDKaHpRoyWla4YtE8hBd2p4H6gUBNoeYp0CqJJi8CoOljFUO1mZpUhaaKsJeqiRlnzJjVH3YZwoiqzd7p9FNvdol8fKjXm3yeHN9l1OVWGSBZdpQNDqymAoTvQKk3nzZ8j2cIHdyfl0NhCzHvMFXWGqzw3ClfO8ummIHmaiOyA77gsdMh0E4YM/c/2QY3gVlnKFOcfSWa3UDasZDoxdPuYjULPlQAtvStNhXYAb2ETt4QLQeIK1hi+TcVYU7k1pHZa1mwgUcCElNOaPHtrOiHMEzCLw9wJiMBlouNKJ5LCiTz1wksjDUTb9MuyFROYyhKcvqi9R7ku4iEBKS7cq9fjQFqfZUDBU5qOtU8dzTOAP/hS7D/iWx5peB9eMvMumyf9KnvmN/z0bf528L/kYerkZXr7Ph+ViSm5voPusOX5aTq6Pn3n8PCfs7kT+GycPL203YW9BR6jyO/7gNF2dnHqln0ftDqg3CUSObs1afiCLiy62I5nj6jH/h1fh8Dore4lHF6wyE+mwwrows/VLQ1KK8b/Cac3qPx9ZpnyparHRUceaReYjXQGgEa9OKv0iIDDX+7eCcbSBlBuIORETjukUT709fi825Ct5hljyB2J9tI7qeoCyXrJUhlVprHXHdfKeQ95fPO5P0PXyrtOm+eNMHfd85tDzyzprjLjKpeLK6XdBol9BtR47WgK7Vtjt2t9M5alt2ga3cukvsC0jte4yvgW9Zbv67wlf2HFY+4TrSdyynbdqO6ay4N5wb8IK46zqnrtPZBK+4aeDWRF1Vqi13k2FvXKu9AnLxekuFei1s+XIQovbVY1XROJbdxh/n9Ph4TUQ+jiodxZDpgHxVq6TwkC0k+ag4PNuRonHVYE8pGsySfFC0X0+EYDReH/liMEYD/2BLA20rEjUEkV8lI9s6609gIYj/R0rSmGAEAUTznXPaTvvouNM9ObWdnXM2pOjzINMqlINX1lJ95UO50hJTjHomDrhltZ0TsxrwtXtjvi94iPO9Odi5LUf1QQYiSvPyrqmBT1em/1AI8NsIwXiiL9TYyPanMaOGjEJu0ABSRUNaUNeJyj7rzaxb3Jij5mYqFZuiCgmjNEL7jtvUMffZnQ1q0rw/pPkfGf8nUEsBAgAAFAACAAgArTuVVwAAAAACAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAGR1bW15L1BLAQIAABQAAgAIAK07lVdj7priMgQAAAgNAAAfAAAAAAAAAAEAAAAAACYAAABSLTEyMzQ1Njc4OTEyLVJBLTIwMjAwMzI4LTEueG1sUEsFBgAAAAACAAIAgQAAAJUEAAAAAA=="; + assert_eq!(expected_cdr, result.0); + assert_eq!("0", result.1); + } +} diff --git a/xsender/src/metadata.rs b/xsender/src/ubl_file.rs similarity index 74% rename from xsender/src/metadata.rs rename to xsender/src/ubl_file.rs index 594447b6..e51a4e41 100644 --- a/xsender/src/metadata.rs +++ b/xsender/src/ubl_file.rs @@ -1,26 +1,41 @@ +use std::fs; +use std::path::Path; + use xml::reader::XmlEvent; use xml::EventReader; -use crate::xml::Xml; +use crate::global::{CAC_NS, CBC_NS, SAC_NS}; -const CBC_NS: &str = "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"; -const CAC_NS: &str = "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"; -const SAC_NS: &str = "urn:sunat:names:specification:ubl:peru:schema:xsd:SunatAggregateComponents-1"; +pub struct UblFile { + pub file_content: String, +} + +pub trait FromPath { + fn from_path(path: &Path) -> std::io::Result; +} + +impl FromPath for UblFile { + fn from_path(path: &Path) -> std::io::Result { + let file_content = fs::read_to_string(path)?; + Ok(UblFile { file_content }) + } +} #[derive(Debug)] -pub struct Metadata { +pub struct UblMetadata { pub document_type: String, - pub document_id: Option, - pub ruc: Option, + pub document_id: String, + pub ruc: String, pub voided_line_document_type_code: Option, } -pub trait MetadataProvider { - fn get_metadata(&self) -> Result; +#[derive(Debug)] +pub struct UblMetadataError { + pub message: String, } -impl MetadataProvider for Xml { - fn get_metadata(&self) -> Result { +impl UblFile { + pub fn metadata(&self) -> Result { let event_reader = EventReader::from_str(&self.file_content); enum Wrapper { @@ -104,14 +119,16 @@ impl MetadataProvider for Xml { } } - match document_type { - Some(document_type) => Ok(Metadata { + match (document_type, document_id, ruc) { + (Some(document_type), Some(document_id), Some(ruc)) => Ok(UblMetadata { document_type, document_id, ruc, voided_line_document_type_code, }), - None => Err("document_type could not be identified"), + _ => Err(UblMetadataError { + message: "document_type, document_id, and ruc were not found".to_string(), + }), } } } @@ -120,25 +137,24 @@ impl MetadataProvider for Xml { mod tests { use std::path::Path; - use crate::metadata::MetadataProvider; - use crate::xml::{FromPath, Xml}; + use crate::ubl_file::{FromPath, UblFile}; const RESOURCES: &str = "resources/test"; #[test] fn metadata() { - let file1 = Xml::from_path(Path::new(&format!("{RESOURCES}/F001-1.xml"))); - let metadata1 = file1.unwrap().get_metadata().unwrap(); + let file1 = UblFile::from_path(Path::new(&format!("{RESOURCES}/F001-1.xml"))); + let metadata1 = file1.unwrap().metadata().unwrap(); assert_eq!(metadata1.document_type, "Invoice"); - assert_eq!(metadata1.document_id.as_deref(), Some("F001-1")); - assert_eq!(metadata1.ruc.as_deref(), Some("12345678912")); + assert_eq!(metadata1.document_id, "F001-1"); + assert_eq!(metadata1.ruc, "12345678912"); assert_eq!(metadata1.voided_line_document_type_code, None); - let file2 = Xml::from_path(Path::new(&format!("{RESOURCES}/RA-20200328-1.xml"))); - let metadata2 = file2.unwrap().get_metadata().unwrap(); + let file2 = UblFile::from_path(Path::new(&format!("{RESOURCES}/RA-20200328-1.xml"))); + let metadata2 = file2.unwrap().metadata().unwrap(); assert_eq!(metadata2.document_type, "VoidedDocuments"); - assert_eq!(metadata2.document_id.as_deref(), Some("RA-20200328-1")); - assert_eq!(metadata2.ruc.as_deref(), Some("12345678912")); + assert_eq!(metadata2.document_id, "RA-20200328-1"); + assert_eq!(metadata2.ruc, "12345678912"); assert_eq!( metadata2.voided_line_document_type_code.as_deref(), Some("01") diff --git a/xsender/src/xml.rs b/xsender/src/xml.rs deleted file mode 100644 index 134238ed..00000000 --- a/xsender/src/xml.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::fs; -use std::path::Path; - -pub struct Xml { - pub filename: Option, - pub file_content: String, -} - -pub trait FromPath { - fn from_path(path: &Path) -> std::io::Result; -} - -impl FromPath for Xml { - fn from_path(path: &Path) -> std::io::Result { - let filename = path - .file_name() - .and_then(|v| v.to_str()) - .map(|v| v.to_string()); - let file_content = fs::read_to_string(path)?; - Ok(Xml { - filename, - file_content, - }) - } -} - -#[cfg(test)] -mod tests { - use std::path::Path; - - use crate::xml::{FromPath, Xml}; - - #[test] - fn from_file() { - let xml_file = Xml::from_path(Path::new("resources/test/F001-1.xml")); - - assert_eq!(xml_file.unwrap().filename.unwrap(), "F001-1.xml"); - } -} diff --git a/xsender/src/zip_manager.rs b/xsender/src/zip_manager.rs new file mode 100644 index 00000000..eb8a0266 --- /dev/null +++ b/xsender/src/zip_manager.rs @@ -0,0 +1,82 @@ +use std::io::{Cursor, Read, Write}; + +use base64::engine::general_purpose; +use base64::{DecodeError, Engine}; +use zip::result::{ZipError, ZipResult}; +use zip::write::FileOptions; +use zip::{ZipArchive, ZipWriter}; + +pub fn create_zip(filename_without_extension: &str, content: &str) -> ZipResult> { + let mut data = Vec::new(); + + { + let buff = Cursor::new(&mut data); + let mut zip = ZipWriter::new(buff); + + let file_options = FileOptions::default(); + zip.start_file(format!("{filename_without_extension}.xml"), file_options)?; + zip.write_all(content.as_bytes())?; + zip.finish()?; + } + + Ok(data) +} + +#[derive(Debug)] +pub struct ExtractCdrFromBase64ZipError { + pub message: String, +} + +pub fn extract_cdr_from_base64_zip(base64: &str) -> Result { + let zip_buf = general_purpose::STANDARD.decode(base64)?; + let reader = Cursor::new(zip_buf); + let mut archive = ZipArchive::new(reader)?; + + let mut cdr_xml_content = "".to_string(); + for index in 0..archive.len() { + let mut entry = archive.by_index(index)?; + if entry.is_file() && entry.name().ends_with(".xml") { + entry.read_to_string(&mut cdr_xml_content)?; + break; + } + } + + Ok(cdr_xml_content) +} + +impl From for ExtractCdrFromBase64ZipError { + fn from(e: DecodeError) -> Self { + ExtractCdrFromBase64ZipError { + message: e.to_string(), + } + } +} + +impl From for ExtractCdrFromBase64ZipError { + fn from(e: ZipError) -> Self { + ExtractCdrFromBase64ZipError { + message: e.to_string(), + } + } +} + +impl From for ExtractCdrFromBase64ZipError { + fn from(e: std::io::Error) -> Self { + ExtractCdrFromBase64ZipError { + message: e.to_string(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::zip_manager::extract_cdr_from_base64_zip; + + #[test] + fn read_base64_zip() { + let cdr = "UEsDBBQAAgAIAEwklVcAAAAAAgAAAAAAAAAGAAAAZHVtbXkvAwBQSwMEFAACAAgATCSVVwwGkuIxBAAAGQ0AABsAAABSLTEyMzQ1Njc4OTEyLTAxLUYwMDEtMS54bWy1V2FP2zwQ/r5fEZUPk6Y3OElbWKPQqVDGsgFitDC0byY52uhN7WA7peXXv+e4SdMStHbSq/LBuXvuubvHZ1sEXxaz1JqDkAlnJy330GlZwCIeJ2xy0robf7U/t770PwRU+IMsS5OIKgTegsw4k2BhMJMnrVwwn1OZSJ/RGUhfZhAlTyuwnz+mvoymMKP+QsZ+yOY8icD2Wibcp2JPhoZK1mywUHvSnfHZjLPzhQKmVcBPpASm5Jo0eoz+ivQU4VEjIf07wsFkImBCFTSRxrgVU6Uyn5CXl5fDl/YhFxPiOY5DnB5BTCyTyUGJlpxmFd4kkofo0vYiUC8IsDmkPANSJcHkVRgsZKoKsDZLm7LYVgn2UiUp+5Q5o+rdPjMQeb3ZkUY39eqWxIv3enXJw9XlqKAqscgCi6yhaHTkKRU2egVIvfmy1Q9wgvy708tqIGQ55g0+Y6nNDsOV6gejZIId5KI6IjvsCx4zHQZxyJ54/4NlBWeUcYY6pclrodUVqCmPrUE64SJR09m7EriOpsW+IjtyO+zgF6L1AGkNW6TgrircmdTplLXaMy7gQEhqyyntut6K8haeQODtAdbdbajlQiOax4Iy+cTFTBpD3fTHtBsSlcMY27Ks3qTek3QXgZCQbFceDJMJSLWnYqjIQV2niueepjn0H16PRPsBEvXt+ntGw3m4hMvrQXZ+OoB0zKYv98+/w2V4/hS/dm/uur+PB/C8OLvoEX41ypxzGf5KLn9cPF9E38/vczn8OVVikRz9PDkJSD2L3h9SbRCOGtmctfpEmIhPNyKZ4+mz/oWl9fEUFL3Bo4rXGQj10WJcWXn2ydDUooIfsCw4g4eu0xtSRc1KR5kzj8zXeA3EVrQ2rfhNQmSo8W8HF2yhlDmIEYiEpnWLJt6fvhZbcBne63z2CGJ/to3oeoKyXLJWhlRqrXXEdfOdQt5ePm9Msh/gW6VN9+ZND4d979AJyBtrgTvLpeKz1e2CRreEbjsKtAYcO20XJ/mo2z72DLTy6iaHeos8x+3Zrmd7nbHj+MXfClpB1hFjfC76DbDCXsDKN37F7bULbtdgN5wbcEPc8dsd3+tuglfcNPJrqq960ZbR3fVgXOuuAnKxvKFCLY2tWIYxbk71mlU0qEEbf16v210TkfejSoeZQh1QrGqVGA/ZQpL3isPDnyiaVg0OlKLRdFZMkvbrkRGMpus7wUzObdg/2NJA20yihiDyp2RkW2f9CSwG8f9ISRoT3EIEyXznnK7X7nSPjj/3XG/nnA0phjzKtQrl4JW1VF/FUK60xBRf8dWwq8le2zcG+4zHONibE13YCtQQZCSSrKjrklpfaYSyU4thHYJbJsE/1pRaMom5RSPIFI2pYavHlj3VC1+3szEzW4VXUjXBjU5JlqB9x704wvNe/fbZjY0spHk/SPN/Nv3/AFBLAQIAABQAAgAIAEwklVcAAAAAAgAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAABkdW1teS9QSwECAAAUAAIACABMJJVXDAaS4jEEAAAZDQAAGwAAAAAAAAABAAAAAAAmAAAAUi0xMjM0NTY3ODkxMi0wMS1GMDAxLTEueG1sUEsFBgAAAAACAAIAfQAAAJAEAAAAAA=="; + + let result = extract_cdr_from_base64_zip(cdr).expect("Could not extract cdr file"); + assert!(!result.is_empty()); + } +} diff --git a/xsender/tests/resources/e2e/12345678912-01-F001-1.xml b/xsender/tests/resources/e2e/12345678912-01-F001-1.xml new file mode 100644 index 00000000..06818be9 --- /dev/null +++ b/xsender/tests/resources/e2e/12345678912-01-F001-1.xml @@ -0,0 +1,171 @@ + + + + dUdjbsfAegqmI/hBripxnCim3XM=Zro7GhJO4GASJgl7urUVy/mAOtfqO4lZMrHbdyFHPpk5ww7B/1fA85ykp/w0rvLP4hFrXPIqVkDf +Z/aDjfWnOyvlNGR0Gha/wNGrompWEdPFcAp3Wuz/LPdhfSYhx0SpouG+S3HxIvL82FiL8XOjZQbz +2N2h3/IFc6Ya6wlFNaeJdmaSxHgPIvTxUzuFjNCVorC2SCwnG+G7K5n1RLwwS/QHH8BJGaICsLH4 +faEqfdmXNMBJPf4u0Ya2EHPEi5EDzHZIkwWj11icptQIPa0mb6Q0S5zVcMgCWiWkvaxYlnrUvRd4 +j3HAIBamLkObLHirrHpGu7aE3R8WtY7R31fpXA==MIIE+DCCA+CgAwIBAgIJALURz7AYmJ5+MA0GCSqGSIb3DQEBBQUAMIIBDTEbMBkGCgmSJomT8ixk +ARkWC0xMQU1BLlBFIFNBMQswCQYDVQQGEwJQRTENMAsGA1UECAwETElNQTENMAsGA1UEBwwETElN +QTEYMBYGA1UECgwPVFUgRU1QUkVTQSBTLkEuMUUwQwYDVQQLDDxETkkgOTk5OTk5OSBSVUMgMTA0 +Njc3OTM1NDkgLSBDRVJUSUZJQ0FETyBQQVJBIERFTU9TVFJBQ0nDk04xRDBCBgNVBAMMO05PTUJS +RSBSRVBSRVNFTlRBTlRFIExFR0FMIC0gQ0VSVElGSUNBRE8gUEFSQSBERU1PU1RSQUNJw5NOMRww +GgYJKoZIhvcNAQkBFg1kZW1vQGxsYW1hLnBlMB4XDTE5MTEwODEyNTY1MFoXDTIxMTEwNzEyNTY1 +MFowggENMRswGQYKCZImiZPyLGQBGRYLTExBTUEuUEUgU0ExCzAJBgNVBAYTAlBFMQ0wCwYDVQQI +DARMSU1BMQ0wCwYDVQQHDARMSU1BMRgwFgYDVQQKDA9UVSBFTVBSRVNBIFMuQS4xRTBDBgNVBAsM +PEROSSA5OTk5OTk5IFJVQyAxMDQ2Nzc5MzU0OSAtIENFUlRJRklDQURPIFBBUkEgREVNT1NUUkFD +ScOTTjFEMEIGA1UEAww7Tk9NQlJFIFJFUFJFU0VOVEFOVEUgTEVHQUwgLSBDRVJUSUZJQ0FETyBQ +QVJBIERFTU9TVFJBQ0nDk04xHDAaBgkqhkiG9w0BCQEWDWRlbW9AbGxhbWEucGUwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCuE7cfjyUaRE1w82EDq5N3zTg2czEGRcopOBHwOV1fy9Oa +t/Qmo0YwvFSWpIbUqVF6UdBe8jPsZY7Q2ATQNn14wRQtLtLxgy6PpFkUK50Nv9pqzEfIK+NV90ol +QXgEg88o3Z2+MUEBDPh8j/f89x6m0irPDOX7xKvJcRsmABSaNpLY84TrlF49Egg05XVWLuXC8vMf +RmXWyv6jK06dcAjVhGUpmGBxLm/ld5f+p9GlshB2rFyWhUBBf++g3xCwtPpyFmv+ARltScOzYcPb +zr9fODbtHDwHDLschuGEpg8rZOwVqekyGxNW0LeDGnq6YtvcW5qVrtEELvqcNkL0D73lAgMBAAGj +VzBVMB0GA1UdDgQWBBTe18LHVKkeRrWs3Bxp1ikK50l96jAfBgNVHSMEGDAWgBTe18LHVKkeRrWs +3Bxp1ikK50l96jATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQUFAAOCAQEASBWcP4Ai +qUUZSG2/Z3RU3BgvOVV3if8xYAaZT99n5PsvyBiZ3Gh6VpAW9ezoe25ZNSqGMmGfq+R4mEuiqK4h +6jDJp0fN47IwRhWjttB9dwpxIDEkWW7zPdueGx+BY8EuyfNDWR/C7GPfu6azSHiapzeKC57AAZ8x +o8kDdhXxDy8hTqNGolkqnc6QutW8cYPeonqyhi2THN163lZ3Cx5OV8vGFQ3ou2msF0klY9qXopI9 +i8wQjEeOG6bCvVxdID9ZjTbuGbO9pAN9hH7hZ41XEG/CspSWbFf1/Wbnlfusne9v9NgRj0MM+LAH +M7AO5/7j1XwRq4x+U9TSVPgpoU9l5Q== + + + 2.1 + 2.0 + F001-1 + 2019-12-24 + 01 + PEN + + 12345678912 + + + 12345678912 + + + + + + + + #PROJECT-OPENUBL-SIGN + + + + + + + 12345678912 + + + + + 0000 + + + + + + + + 12121212121 + + + + + + + + FormaPago + Contado + + + 360.00 + + 2000.00 + 360.00 + + S + + 1000 + IGV + VAT + + + + + + 2000.00 + 2360.00 + 0 + 2360.00 + + + 1 + 10 + 1000.00 + + + 118.00 + 01 + + + + 180.00 + + 1000.00 + 180.00 + + S + 18.00 + 10 + + 1000 + IGV + VAT + + + + + + + + + 100.00 + + + + 2 + 10 + 1000.00 + + + 118.00 + 01 + + + + 180.00 + + 1000.00 + 180.00 + + S + 18.00 + 10 + + 1000 + IGV + VAT + + + + + + + + + 100.00 + + + \ No newline at end of file diff --git a/xsender/tests/resources/e2e/12345678912-RA-20200328-1.xml b/xsender/tests/resources/e2e/12345678912-RA-20200328-1.xml new file mode 100644 index 00000000..9222643a --- /dev/null +++ b/xsender/tests/resources/e2e/12345678912-RA-20200328-1.xml @@ -0,0 +1,81 @@ + + + + + + JGeIFq43Y1Ajg3sJsOCD8faxoIM=O0J36hkWE/tdwuG11wC9PUmmz7k02vcjeQuLf2RrnIV6SeHr56VTzQbYcf68XTCTOB9owyJ1HYZ5 +7r4LVTraKi5ScfAXCsPyacEF2028c/nsx4/HA4SbhAaqJSIYZ+Rz7plk1mmOE2XURyiji7EpLP5G +bmwteXm9ZeWQTYxTT8XTHSDC7jl13GGugaYFGi5gLGMxp4AUsY+CmSgaFXIwAX4etiPjj0yIJD9V +JrDTeTvQs6GtXAMwpW9uj6FO4KBGFg0q54u6gCl616UcLexJCL26CYHk/Pe8Vlh14OGrd184Eorb +ZckBe3hM5mWVep5SmPTtDl7TwCWKioz0pIIxQg==MIICmzCCAYMCBgFxQLzLUjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjAw +NDAzMTU0OTA2WhcNMzAwNDAzMTU1MDQ2WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCBFvkmIdnA8xd4LlEPigj/bcSYJg1KKgARd/QmTlcW80TLuYJ0 +FZuvXpf26iIT9HbkKTEkbaCqA+vCz5gIXhSiKaKuwy8FE0sagxVr93mfT/MK0x4yxijE4gyhQcY5 +4n9wQ0XaEygZT3tSm5iEgZQrcjWHAxYvfS9zGJcdumU2Q3A/wvdjiBQtMyRdYnH9bZje6EIlES/M +SK/GwHEGNgi4j0rkpVFRSGxlYYlAj97DrX681eg6FzADWsVfFwgmHFAO4uDuT1E1SbHay3yC7d6f +tWhD0BCe8ZC6XikVVgdcLRn8oIYNjIv9QxmnbbP0OsKUHTonj/5trxWezhwQIPuHAgMBAAEwDQYJ +KoZIhvcNAQELBQADggEBACnf3I29sFwIV5xpxOvDNYHDapvH366kX1yBGKqnTIyWvz0bEeRuYmOa +v0UN8gCJg+pJxoYc5NXURxTlqsj3XLirRTBW3rZJZWoING3RthU+lAMyC5Ao9rJEhNftxgSAc+DW +5T31JR+Du6V8BnFjyTML3ZPz3QtJwC2IpaFExqjrxodWuiC6c8FtHoPZRp5QfBjdVbDN9kC9YzrP +UkvotM3GiZNB1XrAsvxYmDa3J8ASCRJ11aGMoGkoGYQ4hGfCQBG7DDkq1REbetwE9oqUaHuTTPGi +z3QCqD69SPpu1QuTS86iQ5dYxK70uE47yH0erb0UQ5+dNH5OMSjO8mQdGtw= + + + 2.0 + 1.0 + RA-20200328-1 + 2020-03-28 + 2020-03-28 + + 12312312312 + + + 12312312312 + + + + + + + + #PROJECT-OPENUBL-SIGN + + + + + 12345678912 + 6 + + + + + + + + + + + 1 + 01 + F001 + 1 + + + \ No newline at end of file diff --git a/xsender/tests/send_files.rs b/xsender/tests/send_files.rs new file mode 100644 index 00000000..33e18f4f --- /dev/null +++ b/xsender/tests/send_files.rs @@ -0,0 +1,89 @@ +use std::path::Path; + +use xsender::prelude::*; + +const BASE: &str = "tests/resources/e2e"; + +lazy_static::lazy_static! { + pub static ref CLIENT: FileSender = FileSender { + urls: Urls { + invoice: "https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService".to_string(), + perception_retention:"https://e-beta.sunat.gob.pe/ol-ti-itemision-otroscpe-gem-beta/billService".to_string(), + despatch: "https://api-cpe.sunat.gob.pe/v1/contribuyente/gem".to_string(), + }, + credentials: Credentials { + username: "12345678959MODDATOS".to_string(), + password: "MODDATOS".to_string(), + }, + }; +} + +#[tokio::test] +async fn send_invoice() { + let file_path = format!("{BASE}/12345678912-01-F001-1.xml"); + let xml_file = UblFile::from_path(Path::new(&file_path)).expect("File not found"); + + let response = CLIENT + .send_file(&xml_file) + .await + .expect("Could not get a valid response"); + + let result = match response.data { + SendFileResponse::Ok(_, cdr) => { + assert_eq!("0", cdr.response_code); + assert_eq!( + "La Factura numero F001-1, ha sido aceptada", + cdr.description + ); + assert_eq!(Vec::::new(), cdr.notes); + true + } + SendFileResponse::Ticket(_) => false, + SendFileResponse::Error(_, _) => false, + }; + + assert!(result); +} + +#[tokio::test] +async fn send_voided_documents() { + let file_path = format!("{BASE}/12345678912-RA-20200328-1.xml"); + let xml_file = UblFile::from_path(Path::new(&file_path)).expect("File not found"); + + let send_file_response = CLIENT + .send_file(&xml_file) + .await + .expect("Could not get a valid response"); + + // Send file + let verify_ticket_target = send_file_response + .verify_ticket_target + .expect("Could not determine the verify_ticket target"); + let ticket = match send_file_response.data { + SendFileResponse::Ok(_, _) => "".to_string(), + SendFileResponse::Ticket(ticket) => ticket, + SendFileResponse::Error(_, _) => "".to_string(), + }; + assert!(ticket.len() > 0); + + // Verify ticket + let verify_ticket_response = CLIENT + .verify_ticket(&verify_ticket_target, &ticket) + .await + .expect("Could not verify ticket"); + let result = match verify_ticket_response { + VerifyTicketResponse::Ok(_, cdr, status_code) => { + assert_eq!("0", cdr.response_code); + assert_eq!( + "La Comunicacion de baja RA-20200328-1, ha sido aceptada", + cdr.description + ); + assert_eq!(Vec::::new(), cdr.notes); + + assert_eq!("0", status_code); + true + } + VerifyTicketResponse::Error(_, _) => false, + }; + assert!(result); +}