diff --git a/Cargo.lock b/Cargo.lock index 9a2f9d9..7966de7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,342 +1,30 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "actix-codec" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-sink", - "log", - "tokio", - "tokio-util", -] - -[[package]] -name = "actix-connect" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95cc9569221e9802bf4c377f6c18b90ef10227d787611decf79fd47d2a8e76c" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "derive_more", - "either", - "futures", - "http", - "log", - "trust-dns-proto", - "trust-dns-resolver", -] - -[[package]] -name = "actix-http" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c16664cc4fdea8030837ad5a845eb231fb93fc3c5c171edfefb52fad92ce9019" -dependencies = [ - "actix-codec", - "actix-connect", - "actix-rt", - "actix-service", - "actix-threadpool", - "actix-utils", - "base64 0.11.0", - "bitflags", - "brotli2", - "bytes", - "chrono", - "copyless", - "derive_more", - "either", - "encoding_rs", - "failure", - "flate2", - "futures-channel", - "futures-core", - "futures-util", - "fxhash", - "h2", - "http", - "httparse", - "indexmap", - "language-tags", - "lazy_static", - "log", - "mime", - "percent-encoding", - "pin-project", - "rand", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "sha1", - "slab", - "time", -] - -[[package]] -name = "actix-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21705adc76bbe4bc98434890e73a89cd00c6015e5704a60bb6eea6c3b72316b6" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "actix-router" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7a10ca4d94e8c8e7a87c5173aba1b97ba9a6563ca02b0e1cd23531093d3ec8" -dependencies = [ - "bytestring", - "http", - "log", - "regex", - "serde", -] - -[[package]] -name = "actix-rt" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6a0a55507046441a496b2f0d26a84a65e67c8cafffe279072412f624b5fb6d" -dependencies = [ - "actix-macros", - "actix-threadpool", - "copyless", - "futures", - "tokio", -] - -[[package]] -name = "actix-server" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "582a7173c281a4f46b5aa168a11e7f37183dcb71177a39312cc2264da7a632c9" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "futures", - "log", - "mio", - "mio-uds", - "net2", - "num_cpus", - "slab", -] - -[[package]] -name = "actix-service" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e4fc95dfa7e24171b2d0bb46b85f8ab0e8499e4e3caec691fc4ea65c287564" -dependencies = [ - "futures-util", - "pin-project", -] - -[[package]] -name = "actix-swagger" -version = "0.2.1" -dependencies = [ - "actix-http", - "actix-rt", - "actix-web", - "futures", - "serde", - "serde_json", - "serde_urlencoded", - "take_mut", -] - -[[package]] -name = "actix-swagger" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1268eb09efa2a57fe1e877552bf3238b074da1adfa152f09f98e9102a114ab4b" -dependencies = [ - "actix-http", - "actix-rt", - "actix-web", - "futures", - "serde", - "serde_json", - "serde_urlencoded", - "take_mut", -] - -[[package]] -name = "actix-testing" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48494745b72d0ea8ff0cf874aaf9b622a3ee03d7081ee0c04edea4f26d32c911" -dependencies = [ - "actix-macros", - "actix-rt", - "actix-server", - "actix-service", - "futures", - "log", - "net2", -] - -[[package]] -name = "actix-threadpool" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4082192601de5f303013709ff84d81ca6a1bc4af7fb24f367a500a23c6e84e" -dependencies = [ - "derive_more", - "futures-channel", - "lazy_static", - "log", - "num_cpus", - "parking_lot", - "threadpool", -] - -[[package]] -name = "actix-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e5b4faaf105e9a6d389c606c298dcdb033061b00d532af9df56ff3a54995a8" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "derive_more", - "either", - "futures", - "log", -] - -[[package]] -name = "actix-utils" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf8f5631bf01adec2267808f00e228b761c60c0584cc9fa0b5364f41d147f4e" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "bitflags", - "bytes", - "either", - "futures", - "log", - "pin-project", - "slab", -] - -[[package]] -name = "actix-web" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3158e822461040822f0dbf1735b9c2ce1f95f93b651d7a7aded00b1efbb1f635" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros", - "actix-router", - "actix-rt", - "actix-server", - "actix-service", - "actix-testing", - "actix-threadpool", - "actix-tls", - "actix-utils", - "actix-web-codegen", - "awc", - "bytes", - "derive_more", - "encoding_rs", - "futures", - "fxhash", - "log", - "mime", - "net2", - "pin-project", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "time", - "url", -] - -[[package]] -name = "actix-web-codegen" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f00371942083469785f7e28c540164af1913ee7c96a4534acb9cea92c39f057" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "adler32" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" - [[package]] name = "aho-corasick" -version = "0.7.10" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" dependencies = [ "memchr", ] [[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "arc-swap" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825" - -[[package]] -name = "async-trait" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "991d0a1a3e790c835fd54ab41742a59251338d8c7577fe7d7f0170c7072be708" +name = "alternate" +version = "0.1.0" dependencies = [ + "indexmap", + "insta", + "openapi-actix", + "openapi-hooks", + "openapi-resolver", + "openapiv3", "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi 0.3.8", + "serde", + "serde_json", + "serde_yaml", + "tempfile", ] [[package]] @@ -345,65 +33,11 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -[[package]] -name = "awc" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7601d4d1d7ef2335d6597a41b5fe069f6ab799b85f53565ab390e7b7065aac5" -dependencies = [ - "actix-codec", - "actix-http", - "actix-rt", - "actix-service", - "base64 0.11.0", - "bytes", - "derive_more", - "futures-core", - "log", - "mime", - "percent-encoding", - "rand", - "serde", - "serde_json", - "serde_urlencoded", -] - -[[package]] -name = "backtrace" -version = "0.3.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" -dependencies = [ - "backtrace-sys", - "cfg-if", - "libc", - "rustc-demangle", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] - [[package]] name = "base64" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bitflags" @@ -412,185 +46,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] -name = "brotli-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "brotli2" -version = "0.3.2" +name = "block-buffer" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ - "brotli-sys", - "libc", + "block-padding", + "byte-tools", + "byteorder", + "generic-array", ] [[package]] -name = "byteorder" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" - -[[package]] -name = "bytes" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" - -[[package]] -name = "bytestring" +name = "block-padding" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363" -dependencies = [ - "bytes", -] - -[[package]] -name = "cargo-swagg" -version = "0.3.0" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" dependencies = [ - "clap", - "env_logger", - "serde", - "serde_json", - "serde_yaml", - "swagg", + "byte-tools", ] [[package]] -name = "cc" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "chrono" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" -dependencies = [ - "num-integer", - "num-traits", - "time", -] - -[[package]] -name = "clap" -version = "2.33.1" +name = "byte-tools" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim", - "term_size", - "textwrap", - "unicode-width", - "vec_map", -] +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] -name = "clicolors-control" -version = "1.0.1" +name = "byteorder" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90082ee5dcdd64dc4e9e0d37fbf3ee325419e39c0092191e0393df65518f741e" -dependencies = [ - "atty", - "lazy_static", - "libc", - "winapi 0.3.8", -] +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] -name = "cloudabi" -version = "0.0.3" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "console" -version = "0.10.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6728a28023f207181b193262711102bfbaf47cc9d13bc71d0736607ef8efe88c" +checksum = "7cc80946b3480f421c2f17ed1cb841753a371c7c5104f51d507e13f532c856aa" dependencies = [ - "clicolors-control", "encode_unicode", "lazy_static", "libc", - "termios", - "winapi 0.3.8", -] - -[[package]] -name = "copyless" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127" - -[[package]] -name = "crc32fast" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" -dependencies = [ - "cfg-if", + "terminal_size", + "winapi", ] [[package]] -name = "demo" -version = "0.1.0" -dependencies = [ - "actix-swagger 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-web", - "serde", -] - -[[package]] -name = "derive_more" -version = "0.99.5" +name = "digest" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2323f3f47db9a0e77ce7a300605d8d2098597fc451ed1a97bb1f6411bb550a7" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" dependencies = [ - "proc-macro2", - "quote", - "syn", + "generic-array", ] -[[package]] -name = "difference" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" - [[package]] name = "dtoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" -[[package]] -name = "either" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" - [[package]] name = "encode_unicode" version = "0.3.6" @@ -598,291 +119,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] -name = "encoding_rs" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d03faa7fe0c1431609dfad7bbe827af30f82e1e2ae6f7ee4fca6bd764bc28" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "enum-as-inner" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc4bfcfacb61d231109d1d55202c1f33263319668b168843e02ad4652725ec9c" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "failure" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "flate2" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" -dependencies = [ - "cfg-if", - "crc32fast", - "libc", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - -[[package]] -name = "futures" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" - -[[package]] -name = "futures-executor" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" - -[[package]] -name = "futures-macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" - -[[package]] -name = "futures-task" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" - -[[package]] -name = "futures-util" -version = "0.3.4" +name = "fake-simd" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-utils", - "proc-macro-hack", - "proc-macro-nested", - "slab", -] +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] -name = "fxhash" -version = "0.2.1" +name = "generic-array" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" dependencies = [ - "byteorder", + "typenum", ] [[package]] name = "getrandom" -version = "0.1.14" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" dependencies = [ "cfg-if", "libc", "wasi", ] -[[package]] -name = "h2" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7938e6aa2a31df4e21f224dc84704bd31c089a6d1355c535b03667371cccc843" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "log", - "slab", - "tokio", - "tokio-util", -] - -[[package]] -name = "heck" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" -dependencies = [ - "libc", -] - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi 0.3.8", -] - -[[package]] -name = "http" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "httparse" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" - -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error", -] - -[[package]] -name = "idna" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "indexmap" version = "1.3.2" @@ -901,38 +162,20 @@ checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" [[package]] name = "insta" -version = "0.15.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de3f029212a3fe78a6090f1f2b993877ca245a9ded863f3fcbd6eae084fc1ed" +checksum = "6b0d4f10636e7b40bf9eb71ecaf660498a120a86e9251bd4dea72a64ce9b8a93" dependencies = [ "console", - "difference", "lazy_static", + "pest", + "pest_derive", "ron", "serde", "serde_json", "serde_yaml", -] - -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - -[[package]] -name = "ipconfig" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa79fa216fbe60834a9c0737d7fcd30425b32d1c58854663e24d4c4b328ed83f" -dependencies = [ - "socket2", - "widestring", - "winapi 0.3.8", - "winreg", + "similar", + "uuid", ] [[package]] @@ -941,22 +184,6 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - [[package]] name = "lazy_static" version = "1.4.0" @@ -967,161 +194,73 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" name = "libc" version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" - -[[package]] -name = "linked-hash-map" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" - -[[package]] -name = "lock_api" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" - -[[package]] -name = "memchr" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "miniz_oxide" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" -dependencies = [ - "adler32", -] +checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" [[package]] -name = "mio" -version = "0.6.21" +name = "linked-hash-map" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" -dependencies = [ - "cfg-if", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", -] +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] -name = "mio-uds" -version = "0.6.7" +name = "maplit" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" -dependencies = [ - "iovec", - "libc", - "mio", -] +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] -name = "miow" -version = "0.2.1" +name = "memchr" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] -name = "net2" -version = "0.2.33" +name = "once_cell" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" -dependencies = [ - "cfg-if", - "libc", - "winapi 0.3.8", -] +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" [[package]] -name = "num-integer" -version = "0.1.42" +name = "opaque-debug" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "openapi-actix" +version = "0.1.0" dependencies = [ - "autocfg", - "num-traits", + "inflections", + "insta", + "openapi-hooks", + "proc-macro2", + "quote", + "regex", + "serde", + "tempfile", ] [[package]] -name = "num-traits" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +name = "openapi-hooks" +version = "0.1.0" dependencies = [ - "autocfg", + "openapi-resolver", + "openapiv3", ] [[package]] -name = "num_cpus" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" +name = "openapi-resolver" +version = "0.1.0" dependencies = [ - "hermit-abi", - "libc", + "insta", + "openapiv3", ] [[package]] name = "openapiv3" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8776c7d6a58a03d30ba278adfd54d923eb0a24e26cbf39d2b97648306935d65" +checksum = "3ee9a17eebdd923c4cf74145cd31b2b1d3aa019262c9e8669a843b1d629a9a3c" dependencies = [ "indexmap", "serde", @@ -1130,100 +269,63 @@ dependencies = [ ] [[package]] -name = "parking_lot" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.7.0" +name = "pest" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" dependencies = [ - "cfg-if", - "cloudabi", - "libc", - "redox_syscall", - "smallvec", - "winapi 0.3.8", + "ucd-trie", ] [[package]] -name = "percent-encoding" +name = "pest_derive" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pin-project" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" dependencies = [ - "pin-project-internal", + "pest", + "pest_generator", ] [[package]] -name = "pin-project-internal" -version = "0.4.8" +name = "pest_generator" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" dependencies = [ + "pest", + "pest_meta", "proc-macro2", "quote", "syn", ] [[package]] -name = "pin-project-lite" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" - -[[package]] -name = "pin-utils" -version = "0.1.0-alpha.4" +name = "pest_meta" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] [[package]] name = "ppv-lite86" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" - -[[package]] -name = "proc-macro-hack" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcfdefadc3d57ca21cf17990a28ef4c0f7c61383a28cb7604cf4a18e6ede1420" - -[[package]] -name = "proc-macro-nested" -version = "0.1.4" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "proc-macro2" -version = "1.0.9" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ "unicode-xid", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quote" version = "1.0.3" @@ -1235,11 +337,10 @@ dependencies = [ [[package]] name = "rand" -version = "0.7.3" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ - "getrandom", "libc", "rand_chacha", "rand_core", @@ -1248,9 +349,9 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", "rand_core", @@ -1258,33 +359,36 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" dependencies = [ "rand_core", ] [[package]] name = "redox_syscall" -version = "0.1.56" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570" +dependencies = [ + "bitflags", +] [[package]] name = "regex" -version = "1.3.6" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" +checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" dependencies = [ "aho-corasick", "memchr", @@ -1294,9 +398,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.17" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" +checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" [[package]] name = "remove_dir_all" @@ -1304,62 +408,40 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "resolv-conf" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a" -dependencies = [ - "hostname", - "quick-error", + "winapi", ] [[package]] name = "ron" -version = "0.5.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5" +checksum = "064ea8613fb712a19faf920022ec8ddf134984f100090764a4e1d768f3827f1f" dependencies = [ - "base64 0.10.1", + "base64", "bitflags", "serde", ] -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" - [[package]] name = "ryu" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "serde" -version = "1.0.105" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.105" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" dependencies = [ "proc-macro2", "quote", @@ -1368,32 +450,20 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.50" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78a7a12c167809363ec3bd7329fc0a3369056996de43c4b37ef3cd54a6ce4867" +checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" dependencies = [ "itoa", "ryu", "serde", ] -[[package]] -name = "serde_urlencoded" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" -dependencies = [ - "dtoa", - "itoa", - "serde", - "url", -] - [[package]] name = "serde_yaml" -version = "0.8.11" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" +checksum = "bdd2af560da3c1fdc02cb80965289254fc35dff869810061e2d8290ee48848ae" dependencies = [ "dtoa", "linked-hash-map", @@ -1402,282 +472,78 @@ dependencies = [ ] [[package]] -name = "sha1" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" - -[[package]] -name = "signal-hook-registry" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" -dependencies = [ - "arc-swap", - "libc", -] - -[[package]] -name = "slab" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" - -[[package]] -name = "smallvec" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" - -[[package]] -name = "socket2" -version = "0.3.11" +name = "sha-1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "winapi 0.3.8", + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", ] [[package]] -name = "strsim" -version = "0.8.0" +name = "similar" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "swagg" -version = "0.3.0" -dependencies = [ - "indexmap", - "inflections", - "insta", - "log", - "openapiv3", - "proc-macro2", - "quote", - "regex", - "serde", - "serde_json", - "serde_yaml", - "tempfile", -] +checksum = "da916d7c5876bff6fbf5794bd1e64aba8f5f110b76b192d80bb264423c0736f6" [[package]] name = "syn" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "synstructure" -version = "0.12.3" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" dependencies = [ "proc-macro2", "quote", - "syn", "unicode-xid", ] -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" - [[package]] name = "tempfile" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if", "libc", "rand", "redox_syscall", "remove_dir_all", - "winapi 0.3.8", -] - -[[package]] -name = "term_size" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" -dependencies = [ - "libc", - "winapi 0.3.8", -] - -[[package]] -name = "termcolor" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" -dependencies = [ - "winapi-util", + "winapi", ] [[package]] -name = "termios" -version = "0.3.1" +name = "terminal_size" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625" +checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406" dependencies = [ "libc", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "term_size", - "unicode-width", + "winapi", ] [[package]] name = "thread_local" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "threadpool" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" -dependencies = [ - "num_cpus", -] - -[[package]] -name = "time" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -dependencies = [ - "libc", - "redox_syscall", - "winapi 0.3.8", -] - -[[package]] -name = "tokio" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa5e81d6bc4e67fe889d5783bd2a128ab2e0cfa487e0be16b6a8d177b101616" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "iovec", - "lazy_static", - "libc", - "memchr", - "mio", - "mio-uds", - "pin-project-lite", - "signal-hook-registry", - "slab", - "winapi 0.3.8", -] - -[[package]] -name = "tokio-util" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "trust-dns-proto" -version = "0.18.0-alpha.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a7f3a2ab8a919f5eca52a468866a67ed7d3efa265d48a652a9a3452272b413f" -dependencies = [ - "async-trait", - "enum-as-inner", - "failure", - "futures", - "idna", - "lazy_static", - "log", - "rand", - "smallvec", - "socket2", - "tokio", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.18.0-alpha.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f90b1502b226f8b2514c6d5b37bafa8c200d7ca4102d57dc36ee0f3b7a04a2f" -dependencies = [ - "cfg-if", - "failure", - "futures", - "ipconfig", - "lazy_static", - "log", - "lru-cache", - "resolv-conf", - "smallvec", - "tokio", - "trust-dns-proto", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" dependencies = [ - "matches", + "once_cell", ] [[package]] -name = "unicode-normalization" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" -dependencies = [ - "smallvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.6.0" +name = "typenum" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" [[package]] -name = "unicode-width" -version = "0.1.7" +name = "ucd-trie" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "unicode-xid" @@ -1686,39 +552,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] -name = "url" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" -dependencies = [ - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "vec_map" +name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "widestring" -version = "0.4.0" +version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6" - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "winapi" @@ -1730,57 +573,23 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi 0.3.8", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "yaml-rust" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] diff --git a/Cargo.toml b/Cargo.toml index fc55e26..ce5bef8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,9 @@ [workspace] -members = ["actix-swagger", "cargo-swagg", "swagg", "demo"] +# members = ["actix-swagger", "cargo-swagg", "swagg", "demo"] +members = ["alternate", "openapi-hooks", "openapi-actix", "openapi-resolver"] [patch.crates-io] -swagg = { path = "./swagg" } +# swagg = { path = "./swagg" } +openapi-actix = {path = './openapi-actix'} +openapi-hooks = {path = './openapi-hooks'} +openapi-resolver = {path = './openapi-resolver'} diff --git a/README.md b/README.md index c7010c8..2c96dc2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # actix swagger +## Ideas + +- run function for each path and component with custom mutable state +- run hooks before and after each/all path and components to compute data +- run hooks immediately before rendering to file, to structure and validate data + ## Usage > Not for production use yet diff --git a/alternate/Cargo.toml b/alternate/Cargo.toml new file mode 100644 index 0000000..cb6003b --- /dev/null +++ b/alternate/Cargo.toml @@ -0,0 +1,28 @@ +[package] +authors = ["Sergey Sova "] +edition = "2018" +name = "alternate" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +indexmap = {version = "1.0", features = ["serde-1"]} +openapi-actix = {version = "0.1.0", optional = true} +openapi-hooks = "0.1.0" +openapi-resolver = "0.1.0" +openapiv3 = "0.3.2" +proc-macro2 = "1.0" +quote = "1.0" +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" +serde_yaml = "0.8" + +[dev-dependencies] +insta = {version = "1.6", features = ["ron", "redactions"]} +tempfile = "3.2" + +[features] +default = ["actix"] + +actix = ["openapi-actix"] diff --git a/alternate/src/main.rs b/alternate/src/main.rs new file mode 100644 index 0000000..2ac1b52 --- /dev/null +++ b/alternate/src/main.rs @@ -0,0 +1,139 @@ +use openapiv3::{OpenAPI, ReferenceOr}; + +#[cfg(feature = "actix")] +use openapi_actix::ActixPlugin; + +use openapi_hooks::{Internal, Method, Plugin}; +use openapi_resolver::{resolve_reference, RefResolve}; +pub use openapiv3 as openapi; + +struct DefaultPlugin {} + +impl<'a, I: Internal<'a>> Plugin<'a, I> for DefaultPlugin {} + +pub struct InternalApi<'a> { + files: std::collections::HashMap, + api: &'a OpenAPI, +} + +impl<'a> InternalApi<'a> { + fn new(api: &'a OpenAPI) -> Self { + Self { + api, + files: Default::default(), + } + } +} + +impl<'a> Internal<'a> for InternalApi<'a> { + fn create_file(&mut self, name: String, content: String) { + self.files.insert(name, content); + } + + fn resolve(&'a self, source: &'a ReferenceOr) -> Option<&'a T> + where + &'a T: RefResolve<'a>, + { + resolve_reference(source, &self.api) + } + + fn root(&'a self) -> &'a OpenAPI { + self.api + } +} + +fn main() { + let schema = std::path::Path::new("./demo/openapi.yaml"); + let content = std::fs::read_to_string(&schema).expect("file found"); + let root: OpenAPI = serde_yaml::from_str(&content).expect("parsed to struct"); + + let mut internal = InternalApi::new(&root); + + #[cfg(feature = "actix")] + let mut plugin = ActixPlugin::new(); + + #[cfg(not(feature = "actix"))] + let mut plugin = DefaultPlugin {}; + + plugin.pre_components(&internal); + if let Some(ref components) = root.components { + for (name, security_scheme) in components.security_schemes.iter() { + match resolve_reference(security_scheme, &root) { + None => panic!("failed to resolve reference for security scheme '{}'", name), + Some(schema) => plugin.on_security_scheme(name, schema, &internal), + } + } + + for (name, response) in components.responses.iter() { + match resolve_reference(response, &root) { + None => panic!("failed to resolve reference for response '{}'", name), + Some(schema) => plugin.on_response(name, schema, &internal), + } + } + + for (name, parameter) in components.parameters.iter() { + match resolve_reference(parameter, &root) { + None => panic!("failed to resolve reference for parameter '{}'", name), + Some(schema) => plugin.on_parameter(name, schema, &internal), + } + } + + for (name, request_body) in components.request_bodies.iter() { + match resolve_reference(request_body, &root) { + None => panic!("failed to resolve reference for request body '{}'", name), + Some(schema) => plugin.on_request_body(name, schema, &internal), + } + } + + for (name, header) in components.headers.iter() { + match resolve_reference(header, &root) { + None => panic!("failed to resolve reference for header '{}'", name), + Some(schema) => plugin.on_header(name, schema, &internal), + } + } + + for (name, schema) in components.schemas.iter() { + match resolve_reference(schema, &root) { + None => panic!("failed to resolve reference for schema '{}'", name), + Some(schema) => plugin.on_schema(name, schema, &internal), + } + } + } + plugin.post_components(&internal); + + plugin.pre_paths(&internal); + for (name, path) in root.paths.iter() { + match resolve_reference(path, &root) { + None => panic!("failed to resolve reference for '{}'", name), + Some(path) => { + if let Some(ref operation) = path.get { + plugin.on_operation(Method::Get, name, &operation, &internal); + } + if let Some(ref operation) = path.put { + plugin.on_operation(Method::Put, name, &operation, &internal); + } + if let Some(ref operation) = path.post { + plugin.on_operation(Method::Post, name, &operation, &internal); + } + if let Some(ref operation) = path.delete { + plugin.on_operation(Method::Delete, name, &operation, &internal); + } + if let Some(ref operation) = path.options { + plugin.on_operation(Method::Options, name, &operation, &internal); + } + if let Some(ref operation) = path.head { + plugin.on_operation(Method::Head, name, &operation, &internal); + } + if let Some(ref operation) = path.patch { + plugin.on_operation(Method::Patch, name, &operation, &internal); + } + if let Some(ref operation) = path.trace { + plugin.on_operation(Method::Trace, name, &operation, &internal); + } + } + } + } + plugin.post_paths(&internal); + + plugin.proceed(&mut internal); +} diff --git a/demo/openapi.yaml b/demo/openapi.yaml index c906394..970783c 100644 --- a/demo/openapi.yaml +++ b/demo/openapi.yaml @@ -364,3 +364,4 @@ components: required: - baz - bar + diff --git a/openapi-actix/Cargo.toml b/openapi-actix/Cargo.toml new file mode 100644 index 0000000..df500fd --- /dev/null +++ b/openapi-actix/Cargo.toml @@ -0,0 +1,19 @@ +[package] +authors = ["Sergey Sova "] +edition = "2018" +name = "openapi-actix" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +inflections = "1.1.1" +openapi-hooks = "0.1.0" +proc-macro2 = "1.0.8" +quote = "1.0.2" +regex = "1.3.3" +serde = "1.0.123" +tempfile = "3.2.0" + +[dev-dependencies] +insta = {version = "1.6", features = ["ron", "redactions"]} diff --git a/openapi-actix/src/lib.rs b/openapi-actix/src/lib.rs new file mode 100644 index 0000000..f4b7fad --- /dev/null +++ b/openapi-actix/src/lib.rs @@ -0,0 +1,164 @@ +use inflections::Inflect; +use openapi_hooks::v3::{ + self, Header, Operation, Parameter, ParameterData, ParameterSchemaOrContent, RequestBody, + Response, Schema, SchemaKind, +}; +use openapi_hooks::{Internal, Method, Plugin}; +use printer::{ + components::{ + component::Component, parameters::ParametersModule, Field, FieldType, FormatFloat, + FormatInteger, FormatString, NativeType, + }, + Printable, +}; +use v3::VariantOrUnknownOrEmpty; + +mod printer; +#[cfg(test)] +pub mod test; + +#[derive(Default)] +pub struct ActixPlugin { + schemas: Vec<(String, String)>, + parameters_module: ParametersModule, +} + +impl ActixPlugin { + pub fn new() -> Self { + Default::default() + } + + fn add_parameter<'i, I: Internal<'i>>(&'i mut self, parameter: &'i Parameter, internal: &'i I) { + match parameter { + Parameter::Query { parameter_data, .. } => { + let ParameterData { + name, + description, + required, + deprecated, + format, + .. + } = parameter_data; + + let schema = self + .resolve_schema_from_parameter_format(format, internal) + .expect( + format!( + "Failed to resolve schema reference for parameter '{}'", + name, + ) + .as_str(), + ); + + match &schema.schema_kind { + SchemaKind::OneOf { .. } => {} + SchemaKind::AllOf { .. } => {} + SchemaKind::AnyOf { .. } => {} + SchemaKind::Any(_) => {} + SchemaKind::Type(schema_type) => { + use v3::VariantOrUnknownOrEmpty::{Empty, Item, Unknown}; + + let content_type = match schema_type { + v3::Type::Number(s) => match s.format { + Empty | Unknown(_) => FieldType::native_float(Default::default()), + Item(v3::NumberFormat::Float) => { + FieldType::native_float(FormatFloat::Float) + } + Item(v3::NumberFormat::Double) => { + FieldType::native_float(FormatFloat::Double) + } + }, + v3::Type::Integer(s) => match s.format { + Empty | Unknown(_) => FieldType::native_integer(Default::default()), + Item(v3::IntegerFormat::Int32) => { + FieldType::native_integer(FormatInteger::Int32) + } + Item(v3::IntegerFormat::Int64) => { + FieldType::native_integer(FormatInteger::Int64) + } + }, + v3::Type::String(_) => FieldType::native_string(Default::default()), + v3::Type::Boolean {} => FieldType::native_boolean(), + v3::Type::Array(list) => unimplemented!(), + v3::Type::Object(object) => { + unimplemented!() + } + }; + + let component_name = name.to_pascal_case(); + let component = match content_type { + FieldType::Native(_) => Component::Type { + name: component_name.clone(), + description: None, + type_value: content_type, + }, + FieldType::Array(_) => Component::Type { + name: component_name.clone(), + description: None, + type_value: content_type, + }, + _ => unimplemented!(), + // FieldType::Object(_) => Component::Object { + // name: component_name.clone(), + // description: None, + // fields: vec![] + // } + }; + + self.parameters_module.list.push(component); + + // self.parameters_module.list.push(Component::Type { + // name: name.clone().to_pascal_case(), + // description: None, + // type_value: FieldType::Internal(format!( + // "actix_web::web::Query<{}>", + // component_name + // )), + // }); + } + } + } + rest => println!("Parameter type is not supported yet {:#?}", rest), + } + } + + fn resolve_schema_from_parameter_format<'i, I: Internal<'i>>( + &self, + param: &'i ParameterSchemaOrContent, + internal: &'i I, + ) -> Option<&'i Schema> { + match param { + ParameterSchemaOrContent::Schema(refor) => internal.resolve(refor), + ParameterSchemaOrContent::Content(content_map) => { + let value = content_map.get("application/json")?; + match value.schema { + Some(ref value) => internal.resolve(value), + None => return None, + } + } + } + } +} + +impl<'i, I: Internal<'i>> Plugin<'i, I> for ActixPlugin { + fn on_parameter(&'i mut self, _name: &'i str, parameter: &'i Parameter, internal: &'i I) { + self.add_parameter(parameter, internal); + } + + fn pre_components(&'i mut self, internal: &'i I) { + println!( + "{} {}", + internal.root().info.version, + internal.root().info.title, + ); + } + + fn post_components(&'i mut self, internal: &'i I) { + let result = self.parameters_module.print().to_string(); + println!("{}", result); + } + + fn proceed(&'i mut self, internal: &'i mut I) { + internal.create_file("lib.rs".to_owned(), "".to_owned()); + } +} diff --git a/openapi-actix/src/printer/api/methods.rs b/openapi-actix/src/printer/api/methods.rs new file mode 100644 index 0000000..805105e --- /dev/null +++ b/openapi-actix/src/printer/api/methods.rs @@ -0,0 +1,282 @@ +use super::structure::to_struct_name; +use crate::printer::Printable; +use inflections::Inflect; +use quote::{format_ident, quote}; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub enum HttpMethod { + Delete, + Get, + Patch, + Post, + Put, +} + +impl ToString for HttpMethod { + fn to_string(&self) -> String { + match self { + HttpMethod::Delete => "DELETE", + HttpMethod::Get => "GET", + HttpMethod::Patch => "PATCH", + HttpMethod::Post => "POST", + HttpMethod::Put => "PUT", + } + .to_owned() + } +} + +#[derive(Debug, Serialize)] +pub struct BindApiMethod { + pub method: HttpMethod, + pub path: String, + pub name: String, + pub request_body: Option, +} + +impl Printable for BindApiMethod { + fn print(&self) -> proc_macro2::TokenStream { + let request_path = self.path.clone(); + let http_method = format_ident!("{}", self.method.to_string()); + let path_name = format_ident!("{}", self.name.to_snake_case()); + let bind_method_name = format_ident!("bind_{}", self.name.to_snake_case()); + let request_body_stream = match &self.request_body { + Some(request_body) => { + let body = request_body.to_pascal_case(); + let doc = format!("Request body - super::requst_bodies::{}", body); + quote! { #[doc = #doc] } + } + None => quote! {}, + }; + + quote! { + #request_body_stream + pub fn #bind_method_name(mut self, handler: F) -> Self + where + F: Factory>, + T: FromRequest + 'static, + R: Future> + 'static, + { + self.api = self.api.bind(#request_path.to_owned(), Method::#http_method, handler); + self + } + } + } +} + +pub struct ImplApi { + pub api_name: String, + pub methods: Vec, +} + +impl Default for ImplApi { + fn default() -> Self { + Self { + api_name: "Api".to_owned(), + methods: vec![], + } + } +} + +impl Printable for ImplApi { + fn print(&self) -> proc_macro2::TokenStream { + let api_name = format_ident!("{}", to_struct_name(self.api_name.to_owned())); + let methods = self.methods.print(); + + quote! { + use actix_web::{FromRequest, dev::Factory}; + use actix_swagger::{Answer, Method}; + use std::future::Future; + use super::paths; + + impl #api_name { + #methods + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::shot; + use insta::assert_snapshot; + + fn api(methods: Vec) -> ImplApi { + ImplApi { + api_name: "test_api".to_owned(), + methods, + } + } + + #[test] + fn default_api() { + assert_snapshot!(shot(ImplApi::default()), @r###" + use super::paths; + use actix_swagger::{Answer, Method}; + use actix_web::{dev::Factory, FromRequest}; + use std::future::Future; + impl Api {} + "###); + } + + #[test] + fn impl_api_without_methods() { + let api = ImplApi { + api_name: "Hello".to_owned(), + methods: vec![], + }; + + assert_snapshot!(shot(api), @r###" + use super::paths; + use actix_swagger::{Answer, Method}; + use actix_web::{dev::Factory, FromRequest}; + use std::future::Future; + impl Hello {} + "###); + } + + #[test] + fn impl_api_pascal_naming() { + let api1 = ImplApi { + api_name: "HelloGoof".to_owned(), + methods: vec![], + }; + let api2 = ImplApi { + api_name: "thats_my_name".to_owned(), + methods: vec![], + }; + let api3 = ImplApi { + api_name: "RANDOMIZE_THIS_F_WOOORLD".to_owned(), + methods: vec![], + }; + + assert_snapshot!(shot(vec![api1, api2, api3]), @r###" + use super::paths; + use actix_swagger::{Answer, Method}; + use actix_web::{dev::Factory, FromRequest}; + use std::future::Future; + impl HelloGoof {} + use super::paths; + use actix_swagger::{Answer, Method}; + use actix_web::{dev::Factory, FromRequest}; + use std::future::Future; + impl ThatsMyName {} + use super::paths; + use actix_swagger::{Answer, Method}; + use actix_web::{dev::Factory, FromRequest}; + use std::future::Future; + impl RandomizeThisFWooorld {} + "###); + } + + #[test] + fn bind_api_method_without_body() { + let method = BindApiMethod { + method: HttpMethod::Post, + name: "hey_make_my_day".to_owned(), + path: "/hey-make/my-day".to_owned(), + request_body: None, + }; + + assert_snapshot!(shot(api(vec![method])), @r###" + use super::paths; + use actix_swagger::{Answer, Method}; + use actix_web::{dev::Factory, FromRequest}; + use std::future::Future; + impl TestApi { + pub fn bind_hey_make_my_day(mut self, handler: F) -> Self + where + F: Factory>, + T: FromRequest + 'static, + R: Future> + 'static, + { + self.api = self + .api + .bind("/hey-make/my-day".to_owned(), Method::POST, handler); + self + } + } + "###); + } + + #[test] + fn two_methods() { + let method1 = BindApiMethod { + method: HttpMethod::Post, + name: "hey_make_my_day".to_owned(), + path: "/hey-make/my-day".to_owned(), + request_body: None, + }; + + let method2 = BindApiMethod { + method: HttpMethod::Delete, + name: "ThisIsMyTestNameInPascalCase".to_owned(), + path: "/Very/Very/VEry/Loo000ng/Path".to_owned(), + request_body: None, + }; + + assert_snapshot!(shot(api(vec![method1, method2])), @r###" + use super::paths; + use actix_swagger::{Answer, Method}; + use actix_web::{dev::Factory, FromRequest}; + use std::future::Future; + impl TestApi { + pub fn bind_hey_make_my_day(mut self, handler: F) -> Self + where + F: Factory>, + T: FromRequest + 'static, + R: Future> + 'static, + { + self.api = self + .api + .bind("/hey-make/my-day".to_owned(), Method::POST, handler); + self + } + pub fn bind_this_is_my_test_name_in_pascal_case(mut self, handler: F) -> Self + where + F: Factory>, + T: FromRequest + 'static, + R: Future> + + 'static, + { + self.api = self.api.bind( + "/Very/Very/VEry/Loo000ng/Path".to_owned(), + Method::DELETE, + handler, + ); + self + } + } + "###); + } + + #[test] + fn with_request_body() { + let method = BindApiMethod { + method: HttpMethod::Post, + name: "sessionCreate".to_owned(), + path: "/session".to_owned(), + request_body: Some("SessionCreateBody".to_owned()), + }; + + assert_snapshot!(shot(api(vec![method])), @r###" + use super::paths; + use actix_swagger::{Answer, Method}; + use actix_web::{dev::Factory, FromRequest}; + use std::future::Future; + impl TestApi { + #[doc = "Request body - super::requst_bodies::SessionCreateBody"] + pub fn bind_session_create(mut self, handler: F) -> Self + where + F: Factory>, + T: FromRequest + 'static, + R: Future> + 'static, + { + self.api = self.api.bind("/session".to_owned(), Method::POST, handler); + self + } + } + "###); + } +} diff --git a/openapi-actix/src/printer/api/mod.rs b/openapi-actix/src/printer/api/mod.rs new file mode 100644 index 0000000..3e0ca40 --- /dev/null +++ b/openapi-actix/src/printer/api/mod.rs @@ -0,0 +1,7 @@ +pub use methods::*; +pub use module::*; +pub use structure::*; + +pub mod methods; +pub mod module; +pub mod structure; diff --git a/openapi-actix/src/printer/api/module.rs b/openapi-actix/src/printer/api/module.rs new file mode 100644 index 0000000..994aecd --- /dev/null +++ b/openapi-actix/src/printer/api/module.rs @@ -0,0 +1,80 @@ +use super::methods::ImplApi; +use super::structure::ApiStruct; +use crate::printer::Printable; +use quote::quote; + +#[derive(Default)] +pub struct ApiModule { + pub structure: ApiStruct, + pub methods: ImplApi, +} + +impl ApiModule { + pub fn set_name(&mut self, name: String) { + self.structure.api_name = name.clone(); + self.methods.api_name = name; + } + + pub fn set_description(&mut self, description: Option) { + self.structure.description = description; + } + + pub fn set_terms_of_service(&mut self, terms: Option) { + self.structure.terms_of_service = terms; + } +} + +impl Printable for ApiModule { + fn print(&self) -> proc_macro2::TokenStream { + let api_struct = self.structure.print(); + let methods_impl = self.methods.print(); + quote! { + pub mod api { + #api_struct + + #methods_impl + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::shot; + use insta::assert_snapshot; + + #[test] + fn default_api_module() { + assert_snapshot!(shot(ApiModule::default()), @r###" + pub mod api { + pub struct Api { + api: actix_swagger::Api, + } + impl Api { + pub fn new() -> Self { + Self { + api: actix_swagger::Api::new(), + } + } + } + impl Default for Api { + fn default() -> Self { + let api = Self::new(); + api + } + } + impl actix_web::dev::HttpServiceFactory for Api { + fn register(self, config: &mut actix_web::dev::AppService) { + self.api.register(config); + } + } + use super::paths; + use actix_swagger::{Answer, Method}; + use actix_web::{dev::Factory, FromRequest}; + use std::future::Future; + impl Api {} + } + "###); + } +} diff --git a/openapi-actix/src/printer/api/structure.rs b/openapi-actix/src/printer/api/structure.rs new file mode 100644 index 0000000..ed729e4 --- /dev/null +++ b/openapi-actix/src/printer/api/structure.rs @@ -0,0 +1,210 @@ +use crate::printer::Printable; +use inflections::Inflect; +use quote::{format_ident, quote}; +use regex::Regex; + +/// Create PascalName from string +pub fn to_struct_name(string: String) -> String { + let re_name = Regex::new(r"[^\w_\-\d]+").expect("re_name invalid regex"); + + re_name + .replace_all(string.to_pascal_case().as_ref(), "") + .to_string() +} + +/// Object describing main api structure and useful impls +pub struct ApiStruct { + pub api_name: String, + pub terms_of_service: Option, + pub description: Option, + // pub license: Option, + // pub contact: Option, + // pub version: semver::Version, +} + +impl Default for ApiStruct { + fn default() -> Self { + Self { + api_name: "Api".to_owned(), + terms_of_service: None, + description: None, + } + } +} + +impl Printable for ApiStruct { + fn print(&self) -> proc_macro2::TokenStream { + let api_name = format_ident!("{}", to_struct_name(self.api_name.to_owned())); + let terms = self + .terms_of_service + .to_owned() + .map_or(String::default(), |terms| format!("@see {}", terms)); + let description = self.description.to_owned().unwrap_or_default(); + + let doc_comment = format!("{}\n{}", description, terms); + let doc = doc_comment.trim(); + + let doc_stream = match doc.len() > 0 { + true => quote! { #[doc = #doc] }, + false => quote! {}, + }; + + quote! { + #doc_stream + pub struct #api_name { + api: actix_swagger::Api, + } + + impl #api_name { + pub fn new() -> Self { + Self { + api: actix_swagger::Api::new() + } + } + } + + impl Default for #api_name { + fn default() -> Self { + let api = Self::new(); + api + } + } + + impl actix_web::dev::HttpServiceFactory for #api_name { + fn register(self, config: &mut actix_web::dev::AppService) { + self.api.register(config); + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::shot; + use insta::assert_snapshot; + + #[test] + fn default_struct() { + assert_snapshot!(shot(ApiStruct::default()), @r###" + pub struct Api { + api: actix_swagger::Api, + } + impl Api { + pub fn new() -> Self { + Self { + api: actix_swagger::Api::new(), + } + } + } + impl Default for Api { + fn default() -> Self { + let api = Self::new(); + api + } + } + impl actix_web::dev::HttpServiceFactory for Api { + fn register(self, config: &mut actix_web::dev::AppService) { + self.api.register(config); + } + } + "###); + } + + #[test] + fn with_terms() { + assert_snapshot!(shot(ApiStruct { + api_name: "test_api".to_owned(), + description: None, + terms_of_service: Some("https://example.com/terms".to_owned()) + }), @r###" + #[doc = "@see https://example.com/terms"] + pub struct TestApi { + api: actix_swagger::Api, + } + impl TestApi { + pub fn new() -> Self { + Self { + api: actix_swagger::Api::new(), + } + } + } + impl Default for TestApi { + fn default() -> Self { + let api = Self::new(); + api + } + } + impl actix_web::dev::HttpServiceFactory for TestApi { + fn register(self, config: &mut actix_web::dev::AppService) { + self.api.register(config); + } + } + "###); + } + + #[test] + fn with_description() { + assert_snapshot!(shot(ApiStruct { + api_name: "test_api".to_owned(), + description: Some("My super simple description.\nAnother back".to_owned()), + terms_of_service: None, + }), @r###" + #[doc = "My super simple description.\nAnother back"] + pub struct TestApi { + api: actix_swagger::Api, + } + impl TestApi { + pub fn new() -> Self { + Self { + api: actix_swagger::Api::new(), + } + } + } + impl Default for TestApi { + fn default() -> Self { + let api = Self::new(); + api + } + } + impl actix_web::dev::HttpServiceFactory for TestApi { + fn register(self, config: &mut actix_web::dev::AppService) { + self.api.register(config); + } + } + "###); + } + + #[test] + fn with_description_and_terms() { + assert_snapshot!(shot(ApiStruct { + api_name: "test_api".to_owned(), + description: Some("My super simple description.\nAnother back".to_owned()), + terms_of_service: Some("https://example.com/terms".to_owned()), + }), @r###" + #[doc = "My super simple description.\nAnother back\n@see https://example.com/terms"] + pub struct TestApi { + api: actix_swagger::Api, + } + impl TestApi { + pub fn new() -> Self { + Self { + api: actix_swagger::Api::new(), + } + } + } + impl Default for TestApi { + fn default() -> Self { + let api = Self::new(); + api + } + } + impl actix_web::dev::HttpServiceFactory for TestApi { + fn register(self, config: &mut actix_web::dev::AppService) { + self.api.register(config); + } + } + "###); + } +} diff --git a/openapi-actix/src/printer/components/component.rs b/openapi-actix/src/printer/components/component.rs new file mode 100644 index 0000000..889ecf9 --- /dev/null +++ b/openapi-actix/src/printer/components/component.rs @@ -0,0 +1,590 @@ +use crate::printer::Printable; +use inflections::Inflect; +use quote::{format_ident, quote}; + +pub enum Component { + Object { + name: String, + description: Option, + fields: Vec, + }, + Enum { + name: String, + description: Option, + variants: Vec, + }, + Type { + name: String, + description: Option, + type_value: FieldType, + }, +} + +impl Component { + fn description(&self) -> Option { + match self { + Component::Object { description, .. } => description.clone(), + Component::Enum { description, .. } => description.clone(), + Component::Type { description, .. } => description.clone(), + } + } + + fn name(&self) -> String { + match self { + Component::Object { name, .. } => name.clone(), + Component::Enum { name, .. } => name.clone(), + Component::Type { name, .. } => name.clone(), + } + } +} + +impl Printable for Component { + fn print(&self) -> proc_macro2::TokenStream { + let name_ident = format_ident!("{}", self.name().to_pascal_case()); + let description = match self.description() { + Some(description) => quote! { #[doc = #description] }, + None => quote! {}, + }; + + // implement validator::Validate + + match self { + Component::Object { fields, .. } => { + let fields_stream = fields.print(); + + quote! { + #description + #[derive(Debug, Serialize, Deserialize)] + pub struct #name_ident { + #fields_stream + } + } + } + Component::Enum { variants, .. } => { + let variants_stream = variants.print(); + + quote! { + #description + #[derive(Debug, Serialize, Deserialize)] + pub enum #name_ident { + #variants_stream + } + } + } + Component::Type { type_value, .. } => { + let type_stream = type_value.print(); + + quote! { + #description + pub type #name_ident = #type_stream; + } + } + } + } +} + +pub struct EnumVariant { + pub name: String, + pub description: Option, +} + +impl Printable for EnumVariant { + fn print(&self) -> proc_macro2::TokenStream { + let name_original = self.name.clone(); + let name_pascal = name_original.to_pascal_case(); + let name_ident = format_ident!("{}", name_pascal); + + let is_pascal_diffs = name_pascal != name_original; + let rename = match is_pascal_diffs { + true => quote! { #[serde(rename = #name_original)] }, + false => quote! {}, + }; + + let description = match &self.description { + Some(descr) => quote! { #[doc = #descr]}, + None => quote! {}, + }; + + quote! { + #description + #rename + #name_ident, + } + } +} + +/// Field in object definition +pub struct Field { + /// field name in any case + pub name: String, + + pub required: bool, + + // Add support for nullable values + // https://swagger.io/docs/specification/data-models/data-types/#null + // pub nullable: bool, + pub description: Option, + + pub field_type: FieldType, +} + +impl Printable for Field { + fn print(&self) -> proc_macro2::TokenStream { + let name_original = self.name.clone(); + let name_snake = name_original.to_snake_case(); + let name_ident = format_ident!("{}", name_snake); + + let is_snake_diffs = name_snake != name_original; + let rename = match is_snake_diffs { + true => quote! { #[serde(rename = #name_original)] }, + false => quote! {}, + }; + + let description = match &self.description { + Some(descr) => quote! { #[doc = #descr]}, + None => quote! {}, + }; + let type_stream = self.field_type.print(); + let type_value = match self.required { + false => quote! { Option<#type_stream> }, + true => type_stream, + }; + + quote! { + #description + #rename + pub #name_ident: #type_value, + } + } +} + +pub enum FieldType { + Native(NativeType), + + /// Name of the custom type + Custom(String), + + Array(Box), + + /// Should be used with `x-rust-type: crate::app::MyType` + /// MyType must implement Debug, Serialize, Deserialize + Internal(String), +} + +impl FieldType { + pub fn native_float(format: FormatFloat) -> Self { + Self::Native(NativeType::Float { format }) + } + + pub fn native_integer(format: FormatInteger) -> Self { + Self::Native(NativeType::Integer { format }) + } + + pub fn native_string(format: FormatString) -> Self { + Self::Native(NativeType::String { format }) + } + + pub fn native_boolean() -> Self { + Self::Native(NativeType::Boolean) + } + + pub fn array(item: FieldType) -> Self { + Self::Array(Box::new(item)) + } +} + +impl Printable for FieldType { + fn print(&self) -> proc_macro2::TokenStream { + match self { + FieldType::Native(native) => native.print(), + FieldType::Custom(name) => { + let name_ident = format_ident!("{}", name); + quote! { #name_ident } + } + FieldType::Array(inner_type) => { + let inner_type_stream = inner_type.print(); + quote! { Vec<#inner_type_stream> } + } + FieldType::Internal(name) => path_to_stream(name.clone()), + } + } +} + +/// TODO: use https://docs.rs/itertools/0.9.0/itertools/trait.Itertools.html#method.fold1 +/// https://t.me/rust_beginners_ru/57578 +/// https://t.me/rust_beginners_ru/57579 +fn path_to_stream(path: String) -> proc_macro2::TokenStream { + if path.contains("::") { + let mut parts = path.split("::"); + + let first = parts + .next() + .expect("Path split to parts requires first element"); + let first_ident = format_ident!("{}", first); + + let rest = parts.map(|p| format_ident!("{}", p)); + + quote! { + #first_ident #(::#rest)* + } + } else { + // Can panic if identifier is incorrect. + // TODO: add regexp check for input + let ident = format_ident!("{}", path); + quote! { #ident } + } +} + +pub enum NativeType { + // Add minimum and maximum ranges + // https://swagger.io/docs/specification/data-models/data-types/#numbers + Integer { format: FormatInteger }, + Float { format: FormatFloat }, + String { format: FormatString }, + Boolean, +} + +impl Printable for NativeType { + fn print(&self) -> proc_macro2::TokenStream { + match self { + NativeType::Integer { format } => format.print(), + NativeType::Float { format } => format.print(), + NativeType::String { format } => format.print(), + NativeType::Boolean => quote! { bool }, + } + } +} + +pub enum FormatString { + None, + Binary, + Byte, + Date, + DateTime, + Email, + Hostname, + Ipv4, + Ipv6, + Password, + Url, + Uuid, + Pattern(regex::Regex), +} + +impl Default for FormatString { + fn default() -> FormatString { + FormatString::None + } +} + +impl Printable for FormatString { + fn print(&self) -> proc_macro2::TokenStream { + // Any string format now compiles to String + quote! { String } + } +} + +pub enum FormatInteger { + Int32, + Int64, +} + +impl Default for FormatInteger { + fn default() -> FormatInteger { + FormatInteger::Int32 + } +} + +impl Printable for FormatInteger { + fn print(&self) -> proc_macro2::TokenStream { + match self { + FormatInteger::Int32 => quote! { i32 }, + FormatInteger::Int64 => quote! { i64 }, + } + } +} + +pub enum FormatFloat { + Float, + Double, +} + +impl Default for FormatFloat { + fn default() -> FormatFloat { + FormatFloat::Float + } +} + +impl Printable for FormatFloat { + fn print(&self) -> proc_macro2::TokenStream { + match self { + FormatFloat::Float => quote! { f32 }, + FormatFloat::Double => quote! { f64 }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::shot; + use insta::assert_snapshot; + + struct TestType(T); + + impl crate::printer::Printable for TestType { + fn print(&self) -> proc_macro2::TokenStream { + let t = self.0.print(); + quote::quote! { + type Test = #t; + } + } + } + + #[test] + fn format_float() { + assert_snapshot!(shot(TestType(FormatFloat::default())), @r"type Test = f32;"); + assert_snapshot!(shot(TestType(FormatFloat::Float)), @r"type Test = f32;"); + assert_snapshot!(shot(TestType(FormatFloat::Double)), @r"type Test = f64;"); + } + + #[test] + fn format_integer() { + assert_snapshot!(shot(TestType(FormatInteger::default())), @r###"type Test = i32;"###); + assert_snapshot!(shot(TestType(FormatInteger::Int32)), @r###"type Test = i32;"###); + assert_snapshot!(shot(TestType(FormatInteger::Int64)), @r###"type Test = i64;"###); + } + + #[test] + fn format_string() { + assert_snapshot!(shot(TestType(FormatString::default())), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::None)), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::Binary)), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::Byte)), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::Date)), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::DateTime)), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::Email)), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::Hostname)), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::Ipv4)), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::Ipv6)), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::Password)), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::Url)), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::Uuid)), @"type Test = String; +"); + assert_snapshot!(shot(TestType(FormatString::Pattern(regex::Regex::new(".*").unwrap()))), @"type Test = String; +"); + } + + #[test] + fn native_type_string() { + assert_snapshot!(shot(TestType(NativeType::String { format: Default::default() })), @"type Test = String; +"); + } + + #[test] + fn native_type_float() { + assert_snapshot!(shot(TestType(NativeType::Float { format: Default::default() })), @"type Test = f32; +"); + } + + #[test] + fn native_type_integer() { + assert_snapshot!(shot(TestType(NativeType::Integer { format: Default::default() })), @"type Test = i32; +"); + } + + #[test] + fn native_type_boolean() { + assert_snapshot!(shot(TestType(NativeType::Boolean)), @"type Test = bool; +"); + } + + #[test] + fn convert_path_to_stream() { + assert_snapshot!(path_to_stream("crate::app::Type".to_owned()).to_string(), @"crate :: app :: Type"); + assert_snapshot!(path_to_stream("Type".to_owned()).to_string(), @"Type"); + assert_snapshot!(path_to_stream("super::super::app::Type".to_owned()).to_string(), @"super :: super :: app :: Type"); + } + + #[test] + fn field_type_native() { + assert_snapshot!(shot(TestType(FieldType::Native(NativeType::Boolean))), @r"type Test = bool;"); + assert_snapshot!(shot(TestType(FieldType::Native(NativeType::Float { format: FormatFloat::Double }))), @r"type Test = f64;"); + assert_snapshot!(shot(TestType(FieldType::Native(NativeType::String { format: FormatString::Email }))), @r"type Test = String;"); + } + + #[test] + fn field_type_custom() { + assert_snapshot!(shot(TestType(FieldType::Custom("MySuperType".to_owned()))), @r"type Test = MySuperType;"); + assert_snapshot!(shot(TestType(FieldType::Custom("i32".to_owned()))), @r"type Test = i32;"); + } + + #[test] + #[should_panic] + fn field_type_custom_disallow_path() { + assert_snapshot!(shot(TestType(FieldType::Custom("some::Example".to_owned()))), @r""); + } + + #[test] + fn field_type_internal_allow_path() { + assert_snapshot!(shot(TestType(FieldType::Internal("some::Example".to_owned()))), @r"type Test = some::Example;"); + } + + #[test] + fn field_type_array() { + assert_snapshot!(shot(TestType(FieldType::Array(Box::new(FieldType::Native(NativeType::Boolean))))), @r"type Test = Vec;"); + assert_snapshot!(shot(TestType(FieldType::Array(Box::new(FieldType::Array(Box::new(FieldType::Native(NativeType::Boolean))))))), @r"type Test = Vec>;"); + assert_snapshot!(shot(TestType(FieldType::Array(Box::new(FieldType::Array(Box::new(FieldType::Custom("Super".to_owned()))))))), @r"type Test = Vec>;"); + assert_snapshot!(shot(TestType(FieldType::Array(Box::new(FieldType::Array(Box::new(FieldType::Internal("crate::Super".to_owned()))))))), @r"type Test = Vec>;"); + } + + #[test] + fn component_type() { + assert_snapshot!(shot(Component::Type { + name: "snake_case_name".to_owned(), + description: None, + type_value: FieldType::Internal("super::another::Type".to_owned()), + }), @"pub type SnakeCaseName = super::another::Type;"); + + assert_snapshot!(shot(Component::Type { + name: "UPPER_CASE_NAME".to_owned(), + description: Some("Example description for test type export".to_owned()), + type_value: FieldType::Internal("super::another::Type".to_owned()), + }), @r###" + #[doc = "Example description for test type export"] + pub type UpperCaseName = super::another::Type; + "###); + } + + #[test] + fn component_object() { + assert_snapshot!(shot(Component::Object { + name: "snake_case_name".to_owned(), + description: None, + fields: vec![], + }), @r###" + #[derive(Debug, Serialize, Deserialize)] + pub struct SnakeCaseName {} + "###); + + assert_snapshot!(shot(Component::Object { + name: "UPPER_CASE_NAME".to_owned(), + description: Some("My super long description.\nOr not".to_owned()), + fields: vec![], + }), @r###" + #[doc = "My super long description.\nOr not"] + #[derive(Debug, Serialize, Deserialize)] + pub struct UpperCaseName {} + "###); + + assert_snapshot!(shot(Component::Object { + name: "THIS-IS-FIELDS".to_owned(), + description: None, + fields: vec![Field { + name: "UPPER_CASE_FIELD".to_owned(), + description: Some("Description".to_owned()), + required: true, + field_type: FieldType::Native(NativeType::String { format: Default::default() }) + }, + Field { + name: "snake_case_field".to_owned(), + description: None, + required: true, + field_type: FieldType::Native(NativeType::Integer { format: FormatInteger::Int64 }) + }, + Field { + name: "superCase".to_owned(), + description: None, + required: false, + field_type: FieldType::Internal("super::super::app::Type".to_owned()), + }, + Field { + name: "JustAnother".to_owned(), + description: Some("".to_owned()), + required: false, + field_type: FieldType::Array(Box::new(FieldType::Internal("i128".to_owned()))) + }], + }), @r###" + #[derive(Debug, Serialize, Deserialize)] + pub struct ThisIsFields { + #[doc = "Description"] + #[serde(rename = "UPPER_CASE_FIELD")] + pub upper_case_field: String, + pub snake_case_field: i64, + #[serde(rename = "superCase")] + pub super_case: Option, + #[doc = ""] + #[serde(rename = "JustAnother")] + pub just_another: Option>, + } + "###); + } + + #[test] + fn component_enum() { + assert_snapshot!(shot(Component::Enum { + name: "snake_case_name".to_owned(), + description: None, + variants: vec![], + }), @r###" + #[derive(Debug, Serialize, Deserialize)] + pub enum SnakeCaseName {} + "###); + + assert_snapshot!(shot(Component::Enum { + name: "UPPER_CASE_NAME".to_owned(), + description: Some("My super long description.\nOr not".to_owned()), + variants: vec![], + }), @r###" + #[doc = "My super long description.\nOr not"] + #[derive(Debug, Serialize, Deserialize)] + pub enum UpperCaseName {} + "###); + + assert_snapshot!(shot(Component::Enum { + name: "THIS-IS-FIELDS".to_owned(), + description: None, + variants: vec![EnumVariant { + name: "UPPER_CASE_FIELD".to_owned(), + description: Some("Description".to_owned()), + }, + EnumVariant { + name: "snake_case_field".to_owned(), + description: None, + }, + EnumVariant { + name: "superCase".to_owned(), + description: None, + }, + EnumVariant { + name: "JustAnother".to_owned(), + description: Some("".to_owned()), + }], + }), @r###" + #[derive(Debug, Serialize, Deserialize)] + pub enum ThisIsFields { + #[doc = "Description"] + #[serde(rename = "UPPER_CASE_FIELD")] + UpperCaseField, + #[serde(rename = "snake_case_field")] + SnakeCaseField, + #[serde(rename = "superCase")] + SuperCase, + #[doc = ""] + JustAnother, + } + "###); + } +} diff --git a/openapi-actix/src/printer/components/mod.rs b/openapi-actix/src/printer/components/mod.rs new file mode 100644 index 0000000..0f14830 --- /dev/null +++ b/openapi-actix/src/printer/components/mod.rs @@ -0,0 +1,9 @@ +pub use component::*; +pub use module::*; + +pub mod component; +pub mod module; +pub mod parameters; +pub mod request_bodies; +pub mod responses; +pub mod schemas; diff --git a/openapi-actix/src/printer/components/module.rs b/openapi-actix/src/printer/components/module.rs new file mode 100644 index 0000000..86cb465 --- /dev/null +++ b/openapi-actix/src/printer/components/module.rs @@ -0,0 +1,59 @@ +use super::parameters::ParametersModule; +use super::request_bodies::RequestBodiesModule; +use super::responses::ResponsesModule; +use super::schemas::SchemasModule; +use crate::printer::Printable; +use quote::quote; + +#[derive(Default)] +pub struct ComponentsModule { + pub parameters: ParametersModule, + pub request_bodies: RequestBodiesModule, + pub responses: ResponsesModule, + pub schemas: SchemasModule, +} + +impl Printable for ComponentsModule { + fn print(&self) -> proc_macro2::TokenStream { + let parameters = self.parameters.print(); + let request_bodies = self.request_bodies.print(); + let responses = self.responses.print(); + let schemas = self.schemas.print(); + + quote! { + pub mod components { + #parameters + #request_bodies + #responses + #schemas + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::shot; + use insta::assert_snapshot; + + #[test] + fn components_module_default() { + assert_snapshot!(shot(ComponentsModule::default()), @r###" + pub mod components { + pub mod parameters { + use serde::{Deserialize, Serialize}; + } + pub mod request_bodies { + use serde::{Deserialize, Serialize}; + } + pub mod responses { + use serde::{Deserialize, Serialize}; + } + pub mod schemas { + use serde::{Deserialize, Serialize}; + } + } + "###); + } +} diff --git a/openapi-actix/src/printer/components/parameters.rs b/openapi-actix/src/printer/components/parameters.rs new file mode 100644 index 0000000..c5acf62 --- /dev/null +++ b/openapi-actix/src/printer/components/parameters.rs @@ -0,0 +1,52 @@ +pub use module::*; + +pub mod module { + use super::super::Component; + use crate::printer::Printable; + use quote::quote; + + #[derive(Default)] + pub struct ParametersModule { + pub list: Vec, + } + + impl Printable for ParametersModule { + fn print(&self) -> proc_macro2::TokenStream { + let components = self.list.print(); + + quote! { + pub mod parameters { + use serde::{Serialize, Deserialize}; + + #components + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::super::Component; + use super::*; + use crate::test::shot; + use insta::assert_snapshot; + + #[test] + fn parameters_with_some_components() { + assert_snapshot!(shot(ParametersModule { + list: vec![ + Component::Enum { name: "Example".to_owned(), description: None, variants: vec![] }, + Component::Object { name: "Test".to_owned(), description: None, fields: vec![] }, + ] + }), @r###" + pub mod parameters { + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize, Deserialize)] + pub enum Example {} + #[derive(Debug, Serialize, Deserialize)] + pub struct Test {} + } + "###); + } +} diff --git a/openapi-actix/src/printer/components/request_bodies.rs b/openapi-actix/src/printer/components/request_bodies.rs new file mode 100644 index 0000000..784a00e --- /dev/null +++ b/openapi-actix/src/printer/components/request_bodies.rs @@ -0,0 +1,52 @@ +pub use module::*; + +pub mod module { + use super::super::Component; + use crate::printer::Printable; + use quote::quote; + + #[derive(Default)] + pub struct RequestBodiesModule { + pub list: Vec, + } + + impl Printable for RequestBodiesModule { + fn print(&self) -> proc_macro2::TokenStream { + let components = self.list.print(); + + quote! { + pub mod request_bodies { + use serde::{Serialize, Deserialize}; + + #components + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::super::Component; + use super::*; + use crate::test::shot; + use insta::assert_snapshot; + + #[test] + fn request_bodies_with_some_components() { + assert_snapshot!(shot(RequestBodiesModule { + list: vec![ + Component::Enum { name: "Example".to_owned(), description: None, variants: vec![] }, + Component::Object { name: "Test".to_owned(), description: None, fields: vec![] }, + ] + }), @r###" + pub mod request_bodies { + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize, Deserialize)] + pub enum Example {} + #[derive(Debug, Serialize, Deserialize)] + pub struct Test {} + } + "###); + } +} diff --git a/openapi-actix/src/printer/components/responses.rs b/openapi-actix/src/printer/components/responses.rs new file mode 100644 index 0000000..3e37d98 --- /dev/null +++ b/openapi-actix/src/printer/components/responses.rs @@ -0,0 +1,52 @@ +pub use module::*; + +pub mod module { + use super::super::Component; + use crate::printer::Printable; + use quote::quote; + + #[derive(Default)] + pub struct ResponsesModule { + pub list: Vec, + } + + impl Printable for ResponsesModule { + fn print(&self) -> proc_macro2::TokenStream { + let components = self.list.print(); + + quote! { + pub mod responses { + use serde::{Serialize, Deserialize}; + + #components + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::super::Component; + use super::*; + use crate::test::shot; + use insta::assert_snapshot; + + #[test] + fn responses_with_some_components() { + assert_snapshot!(shot(ResponsesModule { + list: vec![ + Component::Enum { name: "Example".to_owned(), description: None, variants: vec![] }, + Component::Object { name: "Test".to_owned(), description: None, fields: vec![] }, + ] + }), @r###" + pub mod responses { + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize, Deserialize)] + pub enum Example {} + #[derive(Debug, Serialize, Deserialize)] + pub struct Test {} + } + "###); + } +} diff --git a/openapi-actix/src/printer/components/schemas.rs b/openapi-actix/src/printer/components/schemas.rs new file mode 100644 index 0000000..3e86ac4 --- /dev/null +++ b/openapi-actix/src/printer/components/schemas.rs @@ -0,0 +1,52 @@ +pub use module::*; + +pub mod module { + use super::super::Component; + use crate::printer::Printable; + use quote::quote; + + #[derive(Default)] + pub struct SchemasModule { + pub list: Vec, + } + + impl Printable for SchemasModule { + fn print(&self) -> proc_macro2::TokenStream { + let components = self.list.print(); + + quote! { + pub mod schemas { + use serde::{Serialize, Deserialize}; + + #components + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::super::Component; + use super::*; + use crate::test::shot; + use insta::assert_snapshot; + + #[test] + fn schemas_with_some_components() { + assert_snapshot!(shot(SchemasModule { + list: vec![ + Component::Enum { name: "Example".to_owned(), description: None, variants: vec![] }, + Component::Object { name: "Test".to_owned(), description: None, fields: vec![] }, + ] + }), @r###" + pub mod schemas { + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize, Deserialize)] + pub enum Example {} + #[derive(Debug, Serialize, Deserialize)] + pub struct Test {} + } + "###); + } +} diff --git a/openapi-actix/src/printer/mod.rs b/openapi-actix/src/printer/mod.rs new file mode 100644 index 0000000..92d18a2 --- /dev/null +++ b/openapi-actix/src/printer/mod.rs @@ -0,0 +1,619 @@ +pub mod api; +pub mod components; +pub mod paths; + +pub trait Printable { + fn print(&self) -> proc_macro2::TokenStream; +} + +impl Printable for Vec +where + T: Printable, +{ + fn print(&self) -> proc_macro2::TokenStream { + use quote::quote; + + let list = self.iter().map(|x| x.print()); + + quote! { + #(#list)* + } + } +} + +#[derive(Default)] +pub struct GeneratedModule { + pub api: api::module::ApiModule, + pub components: components::module::ComponentsModule, + pub paths: paths::module::PathsModule, +} + +impl GeneratedModule {} + +impl Printable for GeneratedModule { + fn print(&self) -> proc_macro2::TokenStream { + let api_module = self.api.print(); + let components_module = self.components.print(); + let paths_module = self.paths.print(); + + quote::quote! { + #![allow(dead_code, unused_imports)] + + #api_module + #components_module + #paths_module + } + } +} + +#[cfg(test)] +mod tests { + use super::{ + api::{ApiModule, ApiStruct, BindApiMethod, HttpMethod, ImplApi}, + components::{ + parameters::ParametersModule, request_bodies::RequestBodiesModule, + responses::ResponsesModule, schemas::SchemasModule, Component, ComponentsModule, + EnumVariant, Field, FieldType, FormatFloat, FormatInteger, FormatString, NativeType, + }, + paths::{ + ContentType, Path, PathsModule, QueryParam, ResponseEnum, ResponseStatus, StatusVariant, + }, + GeneratedModule, + }; + use crate::test::shot; + use insta::assert_snapshot; + + #[test] + fn huge_test_all_generated() { + let api: ApiStruct = ApiStruct { + api_name: "ExampleApiDef".to_owned(), + description: Some("Public API for frontend and OAuth applications [Review Github](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/)".to_owned()), + terms_of_service: None, + }; + + let m1 = BindApiMethod { + method: HttpMethod::Get, + name: "sessionGet".to_owned(), + path: "/session".to_owned(), + request_body: None, + }; + + let m2 = BindApiMethod { + method: HttpMethod::Post, + name: "sessionCreate".to_owned(), + path: "/session".to_owned(), + request_body: Some("SessionCreateBody".to_owned()), + }; + + let m3 = BindApiMethod { + method: HttpMethod::Post, + name: "registerConfirmation".to_owned(), + path: "/register/confirmation".to_owned(), + request_body: Some("RegisterConfirmation".to_owned()), + }; + + let methods = ImplApi { + api_name: api.api_name.clone(), + methods: vec![m1, m2, m3], + }; + + let api_module = ApiModule { + structure: api, + methods, + }; + + let components_module = ComponentsModule { + parameters: ParametersModule { + list: vec![ + Component::Enum { + name: "OAuthResponseType".to_owned(), + description: Some( + "response_type is set to code indicating that you want an authorization code as the response." + .to_owned(), + ), + variants: vec![EnumVariant { + name: "code".to_owned(), + description: None, + }], + }, + Component::Type { + name: "OAuthClientId".to_owned(), + description: Some("The client_id is the identifier for your app".to_owned()), + type_value: FieldType::Internal("uuid::Uuid".to_owned()), + }, + Component::Type { + name: "OAuthRedirectUri".to_owned(), + description: Some( + "redirect_uri may be optional depending on the API, but is highly recommended".to_owned(), + ), + type_value: FieldType::Native(NativeType::String { format: Default::default() }), + }, + ], + }, + responses: ResponsesModule { + list: vec![ + Component::Object { + name: "RegisterConfirmationFailed".to_owned(), + fields: vec![Field { + name: "error".to_owned(), + required: true, + description: None, + field_type: FieldType::Custom("RegisterConfirmationFailedError".to_owned()), + }], + description: Some("Answer for registration confirmation".to_owned()), + }, + Component::Enum { + name: "RegisterConfirmationFailedError".to_owned(), + variants: vec![ + EnumVariant { + name: "code_invalid_or_expired".to_owned(), + description: None, + }, + EnumVariant { + name: "email_already_activated".to_owned(), + description: None, + }, + EnumVariant { + name: "invalid_form".to_owned(), + description: None, + }, + ], + description: None, + }, + Component::Object { + name: "RegistrationRequestCreated".to_owned(), + description: Some( + "Registration link sent to email, now user can find out when the link expires".to_owned(), + ), + fields: vec![Field { + name: "expiresAt".to_owned(), + required: true, + description: Some("UTC Unix TimeStamp when the link expires".to_owned()), + field_type: FieldType::Native(NativeType::Integer { + format: FormatInteger::Int64, + }), + }], + }, + ], + }, + request_bodies: RequestBodiesModule { + list: vec![ + Component::Object { + name: "Register".to_owned(), + description: None, + fields: vec![ + Field { + name: "email".to_owned(), + required: true, + description: None, + field_type: FieldType::Native(NativeType::String { + format: FormatString::Email, + }), + }, + Field { + name: "demo".to_owned(), + required: false, + description: None, + field_type: FieldType::Array(Box::new(FieldType::Array(Box::new(FieldType::Native( + NativeType::String { + format: FormatString::Email, + }, + ))))), + }, + ], + }, + Component::Object { + name: "RegisterConfirmation".to_owned(), + description: None, + fields: vec![ + Field { + name: "confirmationCode".to_owned(), + required: true, + description: None, + field_type: FieldType::Native(NativeType::String { + format: FormatString::default(), + }), + }, + Field { + name: "firstName".to_owned(), + required: true, + description: None, + field_type: FieldType::Native(NativeType::String { + format: FormatString::default(), + }), + }, + Field { + name: "lastName".to_owned(), + required: true, + description: None, + field_type: FieldType::Native(NativeType::String { + format: FormatString::default(), + }), + }, + Field { + name: "password".to_owned(), + required: true, + description: None, + field_type: FieldType::Native(NativeType::String { + format: FormatString::default(), + }), + }, + Field { + name: "demo".to_owned(), + required: false, + description: None, + field_type: FieldType::Native(NativeType::Float { + format: FormatFloat::default(), + }), + }, + Field { + name: "customizer".to_owned(), + required: false, + description: None, + field_type: FieldType::Internal("crate::app::MySuperType".to_owned()), + }, + ], + }, + ], + }, + schemas: SchemasModule { + list: vec![ + Component::Object { + name: "RegisterConfirmation".to_owned(), + description: None, + fields: vec![ + Field { + name: "confirmationCode".to_owned(), + required: true, + description: None, + field_type: FieldType::Native(NativeType::String { + format: FormatString::default(), + }), + }, + Field { + name: "firstName".to_owned(), + required: true, + description: None, + field_type: FieldType::Native(NativeType::String { + format: FormatString::default(), + }), + }, + Field { + name: "lastName".to_owned(), + required: true, + description: None, + field_type: FieldType::Native(NativeType::String { + format: FormatString::default(), + }), + }, + Field { + name: "password".to_owned(), + required: true, + description: None, + field_type: FieldType::Native(NativeType::String { + format: FormatString::default(), + }), + }, + Field { + name: "demo".to_owned(), + required: false, + description: None, + field_type: FieldType::Native(NativeType::Float { + format: FormatFloat::default(), + }), + }, + Field { + name: "customizer".to_owned(), + required: false, + description: None, + field_type: FieldType::Internal("crate::app::MySuperType".to_owned()), + }, + ], + }, + ], + }, + }; + + let p1 = Path { + name: "registerConfirmation".to_owned(), + query_params: vec![], + response: ResponseEnum { + responses: vec![ + StatusVariant { + status: ResponseStatus::Created, + response_type_name: None, + description: None, + content_type: None, + x_variant_name: None, + }, + StatusVariant { + status: ResponseStatus::BadRequest, + response_type_name: Some("RegisterConfirmationFailed".to_owned()), + description: None, + content_type: Some(ContentType::Json), + x_variant_name: None, + }, + StatusVariant { + status: ResponseStatus::InternalServerError, + response_type_name: None, + description: None, + content_type: Some(ContentType::Json), + x_variant_name: Some("Unexpected".to_owned()), + }, + ], + }, + }; + + let p2 = Path { + name: "sessionCreate".to_owned(), + query_params: vec![ + QueryParam { + name: "responseType".to_owned(), + type_ref: "OAuthResponseType".to_owned(), + description: Some( + "response_type is set to code indicating that you want an authorization code as the response." + .to_owned(), + ), + required: true, + }, + QueryParam { + name: "redirect_uri".to_owned(), + type_ref: "OAuthRedirectUri".to_owned(), + description: None, + required: false, + }, + QueryParam { + name: "GlobalNameOfTheUniverse".to_owned(), + type_ref: "OAuthClientId".to_owned(), + description: None, + required: false, + }, + ], + response: ResponseEnum { + responses: vec![ + StatusVariant { + status: ResponseStatus::Created, + response_type_name: None, + description: Some("User logined, cookies writed\nFoo".to_owned()), + content_type: None, + x_variant_name: None, + }, + StatusVariant { + status: ResponseStatus::BadRequest, + response_type_name: Some("sessionCreateFailed".to_owned()), + description: None, + content_type: Some(ContentType::Json), + x_variant_name: None, + }, + StatusVariant { + status: ResponseStatus::InternalServerError, + response_type_name: None, + description: None, + content_type: Some(ContentType::Json), + x_variant_name: Some("Unexpected".to_owned()), + }, + ], + }, + }; + + let paths_module = PathsModule { + paths: vec![p1, p2], + }; + + let generated_module = GeneratedModule { + api: api_module, + components: components_module, + paths: paths_module, + }; + + assert_snapshot!(shot(generated_module), @r###" + #![allow(dead_code, unused_imports)] + pub mod api { + #[doc = "Public API for frontend and OAuth applications [Review Github](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/)"] + pub struct ExampleApiDef { + api: actix_swagger::Api, + } + impl ExampleApiDef { + pub fn new() -> Self { + Self { + api: actix_swagger::Api::new(), + } + } + } + impl Default for ExampleApiDef { + fn default() -> Self { + let api = Self::new(); + api + } + } + impl actix_web::dev::HttpServiceFactory for ExampleApiDef { + fn register(self, config: &mut actix_web::dev::AppService) { + self.api.register(config); + } + } + use super::paths; + use actix_swagger::{Answer, Method}; + use actix_web::{dev::Factory, FromRequest}; + use std::future::Future; + impl ExampleApiDef { + pub fn bind_session_get(mut self, handler: F) -> Self + where + F: Factory>, + T: FromRequest + 'static, + R: Future> + 'static, + { + self.api = self.api.bind("/session".to_owned(), Method::GET, handler); + self + } + #[doc = "Request body - super::requst_bodies::SessionCreateBody"] + pub fn bind_session_create(mut self, handler: F) -> Self + where + F: Factory>, + T: FromRequest + 'static, + R: Future> + 'static, + { + self.api = self.api.bind("/session".to_owned(), Method::POST, handler); + self + } + #[doc = "Request body - super::requst_bodies::RegisterConfirmation"] + pub fn bind_register_confirmation(mut self, handler: F) -> Self + where + F: Factory>, + T: FromRequest + 'static, + R: Future> + 'static, + { + self.api = self + .api + .bind("/register/confirmation".to_owned(), Method::POST, handler); + self + } + } + } + pub mod components { + pub mod parameters { + use serde::{Deserialize, Serialize}; + #[doc = "response_type is set to code indicating that you want an authorization code as the response."] + #[derive(Debug, Serialize, Deserialize)] + pub enum OauthResponseType { + #[serde(rename = "code")] + Code, + } + #[doc = "The client_id is the identifier for your app"] + pub type OauthClientId = uuid::Uuid; + #[doc = "redirect_uri may be optional depending on the API, but is highly recommended"] + pub type OauthRedirectUri = String; + } + pub mod request_bodies { + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize, Deserialize)] + pub struct Register { + pub email: String, + pub demo: Option>>, + } + #[derive(Debug, Serialize, Deserialize)] + pub struct RegisterConfirmation { + #[serde(rename = "confirmationCode")] + pub confirmation_code: String, + #[serde(rename = "firstName")] + pub first_name: String, + #[serde(rename = "lastName")] + pub last_name: String, + pub password: String, + pub demo: Option, + pub customizer: Option, + } + } + pub mod responses { + use serde::{Deserialize, Serialize}; + #[doc = "Answer for registration confirmation"] + #[derive(Debug, Serialize, Deserialize)] + pub struct RegisterConfirmationFailed { + pub error: RegisterConfirmationFailedError, + } + #[derive(Debug, Serialize, Deserialize)] + pub enum RegisterConfirmationFailedError { + #[serde(rename = "code_invalid_or_expired")] + CodeInvalidOrExpired, + #[serde(rename = "email_already_activated")] + EmailAlreadyActivated, + #[serde(rename = "invalid_form")] + InvalidForm, + } + #[doc = "Registration link sent to email, now user can find out when the link expires"] + #[derive(Debug, Serialize, Deserialize)] + pub struct RegistrationRequestCreated { + #[doc = "UTC Unix TimeStamp when the link expires"] + #[serde(rename = "expiresAt")] + pub expires_at: i64, + } + } + pub mod schemas { + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize, Deserialize)] + pub struct RegisterConfirmation { + #[serde(rename = "confirmationCode")] + pub confirmation_code: String, + #[serde(rename = "firstName")] + pub first_name: String, + #[serde(rename = "lastName")] + pub last_name: String, + pub password: String, + pub demo: Option, + pub customizer: Option, + } + } + } + pub mod paths { + use super::components::{parameters, responses}; + pub mod register_confirmation { + use super::responses; + use actix_swagger::{Answer, ContentType, StatusCode}; + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize)] + #[serde(untagged)] + pub enum Response { + Created, + BadRequest(responses::RegisterConfirmationFailed), + Unexpected, + } + impl Response { + #[inline] + pub fn to_answer(self) -> Answer<'static, Self> { + let status = match self { + Self::Created => StatusCode::CREATED, + Self::BadRequest(_) => StatusCode::BAD_REQUEST, + Self::Unexpected => StatusCode::INTERNAL_SERVER_ERROR, + }; + let content_type = match self { + Self::Created => None, + Self::BadRequest(_) => Some(ContentType::Json), + Self::Unexpected => Some(ContentType::Json), + }; + Answer::new(self).status(status).content_type(content_type) + } + } + } + pub mod session_create { + use super::responses; + use actix_swagger::{Answer, ContentType, StatusCode}; + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize)] + #[serde(untagged)] + pub enum Response { + #[doc = "User logined, cookies writed\nFoo"] + Created, + BadRequest(responses::SessionCreateFailed), + Unexpected, + } + impl Response { + #[inline] + pub fn to_answer(self) -> Answer<'static, Self> { + let status = match self { + Self::Created => StatusCode::CREATED, + Self::BadRequest(_) => StatusCode::BAD_REQUEST, + Self::Unexpected => StatusCode::INTERNAL_SERVER_ERROR, + }; + let content_type = match self { + Self::Created => None, + Self::BadRequest(_) => Some(ContentType::Json), + Self::Unexpected => Some(ContentType::Json), + }; + Answer::new(self).status(status).content_type(content_type) + } + } + use super::parameters; + #[derive(Debug, Deserialize)] + pub struct QueryParams { + #[doc = "response_type is set to code indicating that you want an authorization code as the response."] + #[serde(rename = "responseType")] + pub response_type: parameters::OauthResponseType, + pub redirect_uri: Option, + #[serde(rename = "GlobalNameOfTheUniverse")] + pub global_name_of_the_universe: Option, + } + pub type Query = actix_web::http::Query; + } + } + "###); + } +} diff --git a/openapi-actix/src/printer/paths/mod.rs b/openapi-actix/src/printer/paths/mod.rs new file mode 100644 index 0000000..700a787 --- /dev/null +++ b/openapi-actix/src/printer/paths/mod.rs @@ -0,0 +1,7 @@ +pub use module::*; +pub use path::*; +pub use response_status::*; + +pub mod module; +pub mod path; +pub mod response_status; diff --git a/openapi-actix/src/printer/paths/module.rs b/openapi-actix/src/printer/paths/module.rs new file mode 100644 index 0000000..8ba4655 --- /dev/null +++ b/openapi-actix/src/printer/paths/module.rs @@ -0,0 +1,37 @@ +use super::Path; +use crate::printer::Printable; +use quote::quote; + +#[derive(Default)] +pub struct PathsModule { + pub paths: Vec, +} + +impl Printable for PathsModule { + fn print(&self) -> proc_macro2::TokenStream { + let paths = self.paths.print(); + + quote! { + pub mod paths { + use super::components::{parameters, responses}; + #paths + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::shot; + use insta::assert_snapshot; + + #[test] + fn components_module_default() { + assert_snapshot!(shot(PathsModule::default()), @r###" + pub mod paths { + use super::components::{parameters, responses}; + } + "###); + } +} diff --git a/openapi-actix/src/printer/paths/path.rs b/openapi-actix/src/printer/paths/path.rs new file mode 100644 index 0000000..da1a158 --- /dev/null +++ b/openapi-actix/src/printer/paths/path.rs @@ -0,0 +1,466 @@ +use super::ResponseStatus; +use crate::printer::Printable; +use inflections::Inflect; +use quote::{format_ident, quote}; + +pub struct Path { + pub name: String, + pub response: ResponseEnum, + pub query_params: Vec, +} + +impl Path { + fn print_enum_variants(&self) -> proc_macro2::TokenStream { + let variants = self + .response + .responses + .iter() + .map(|r| r.print_enum_variant()); + + quote! { #(#variants,)* } + } + + fn print_status_variants(&self) -> proc_macro2::TokenStream { + let variants = self + .response + .responses + .iter() + .map(|r| r.print_status_variant()); + let tokens = quote! { #(#variants,)* }; + + quote! { + match self { + #tokens + } + } + } + + fn print_content_type_variants(&self) -> proc_macro2::TokenStream { + let variants = self + .response + .responses + .iter() + .map(|r| r.print_content_type_variant()); + let tokens = quote! { #(#variants,)* }; + + quote! { + match self { + #tokens + } + } + } + + fn query_params_impl(&self) -> proc_macro2::TokenStream { + if self.query_params.is_empty() { + quote! {} + } else { + let query_params = self.query_params.print(); + + quote! { + use super::parameters; + + #[derive(Debug, Deserialize)] + pub struct QueryParams { + #query_params + } + + pub type Query = actix_web::http::Query; + } + } + } +} + +impl Printable for Path { + fn print(&self) -> proc_macro2::TokenStream { + let module_name = format_ident!("{}", self.name.to_snake_case()); + let enum_variants = self.print_enum_variants(); + let status_match = self.print_status_variants(); + let content_type_match = self.print_content_type_variants(); + let query_params = self.query_params_impl(); + + quote! { + pub mod #module_name { + use super::responses; + use actix_swagger::{Answer, ContentType, StatusCode}; + use serde::{Serialize, Deserialize}; + + #[derive(Debug, Serialize)] + #[serde(untagged)] + pub enum Response { + #enum_variants + } + + impl Response { + #[inline] + pub fn to_answer(self) -> Answer<'static, Self> { + let status = #status_match; + let content_type = #content_type_match; + + Answer::new(self).status(status).content_type(content_type) + } + } + + #query_params + } + } + + /* + impl<'a> Into> for Response { + #[inline] + fn into(self: Response) -> Answer<'a, Response> { + self.to_answer() + } + } + */ + } +} + +pub struct ResponseEnum { + pub responses: Vec, +} + +pub struct StatusVariant { + pub status: ResponseStatus, + + /// Should be in `#/components/responses/` + pub response_type_name: Option, + + /// Comment for response status + pub description: Option, + + /// Now supports only one content type per response + pub content_type: Option, + + /// Variant can be renamed with `x-variant-name` + pub x_variant_name: Option, +} + +impl StatusVariant { + pub fn name(&self) -> proc_macro2::Ident { + let name = self + .x_variant_name + .clone() + .unwrap_or(self.status.to_string()); + format_ident!("{}", name.to_pascal_case()) + } + + pub fn description(&self) -> proc_macro2::TokenStream { + match &self.description { + Some(text) => quote! { #[doc = #text] }, + None => quote! {}, + } + } + + pub fn content_type(&self) -> proc_macro2::TokenStream { + match self.content_type.clone() { + Some(t) => { + let content = t.print(); + quote! { Some(ContentType::#content) } + } + None => quote! { None }, + } + } + + pub fn print_enum_variant(&self) -> proc_macro2::TokenStream { + let description = self.description(); + let variant_name = self.name(); + + if let Some(response) = self.response_type_name.clone() { + let response_name = format_ident!("{}", response.to_pascal_case()); + + quote! { + #description + #variant_name(responses::#response_name) + } + } else { + quote! { + #description + #variant_name + } + } + } + + pub fn print_status_variant(&self) -> proc_macro2::TokenStream { + let variant_name = self.name(); + let status = format_ident!("{}", self.status.to_string().to_constant_case()); + + match self.response_type_name { + Some(_) => quote! { Self::#variant_name(_) => StatusCode::#status }, + None => quote! { Self::#variant_name => StatusCode::#status }, + } + } + + pub fn print_content_type_variant(&self) -> proc_macro2::TokenStream { + let variant_name = self.name(); + let content_type = self.content_type(); + + match self.response_type_name { + Some(_) => quote! { Self::#variant_name(_) => #content_type }, + None => quote! { Self::#variant_name => #content_type }, + } + } +} + +#[derive(Debug, Clone)] +pub enum ContentType { + Json, +} + +impl ToString for ContentType { + fn to_string(&self) -> String { + match self { + ContentType::Json => "Json", + } + .to_owned() + } +} + +impl Printable for ContentType { + fn print(&self) -> proc_macro2::TokenStream { + let ident = format_ident!("{}", self.to_string()); + + quote! { #ident } + } +} + +pub struct QueryParam { + /// Name of the parameter in the query, can be in any case, will be converted to snake_case + pub name: String, + + /// should be reference to type in `components::parameters` module + /// Will be converted to PascalCase + pub type_ref: String, + + pub description: Option, + + pub required: bool, +} + +impl Printable for QueryParam { + fn print(&self) -> proc_macro2::TokenStream { + let name_original = self.name.clone(); + let name_snake = name_original.to_snake_case(); + let name_ident = format_ident!("{}", name_snake); + let rename = match name_snake != name_original { + true => quote! { #[serde(rename = #name_original)] }, + false => quote! {}, + }; + + let type_name = format_ident!("{}", self.type_ref.to_pascal_case()); + let description = match &self.description { + Some(description) => quote! { #[doc = #description]}, + None => quote! {}, + }; + + let type_result = match self.required { + true => quote! { parameters::#type_name }, + false => quote! { Option }, + }; + + quote! { + #description + #rename + pub #name_ident: #type_result, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::shot; + use insta::assert_snapshot; + + #[test] + fn path_empty() { + assert_snapshot!(shot(Path { + name: "Example".to_owned(), + response: ResponseEnum { + responses: vec![] + }, + query_params: vec![] + }), @r###" + pub mod example { + use super::responses; + use actix_swagger::{Answer, ContentType, StatusCode}; + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize)] + #[serde(untagged)] + pub enum Response {} + impl Response { + #[inline] + pub fn to_answer(self) -> Answer<'static, Self> { + let status = match self {}; + let content_type = match self {}; + Answer::new(self).status(status).content_type(content_type) + } + } + } + "###); + } + + #[test] + fn path_with_responses() { + assert_snapshot!(shot(Path { + name: "Example".to_owned(), + response: ResponseEnum { + responses: vec![ + StatusVariant { + status: ResponseStatus::Ok, + content_type: None, + response_type_name: None, + description: None, + x_variant_name: None, + }, + StatusVariant { + status: ResponseStatus::Created, + content_type: Some(ContentType::Json), + response_type_name: None, + description: None, + x_variant_name: None, + }, + StatusVariant { + status: ResponseStatus::Accepted, + content_type: Some(ContentType::Json), + response_type_name: Some("unexpected_FRIEND_Name".to_owned()), + description: None, + x_variant_name: None, + }, + StatusVariant { + status: ResponseStatus::BadRequest, + content_type: Some(ContentType::Json), + response_type_name: Some("unexpected_FRIEND_Name".to_owned()), + description: Some("My super simple description.\nAnother back".to_owned()), + x_variant_name: None, + }, + StatusVariant { + status: ResponseStatus::InternalServerError, + content_type: Some(ContentType::Json), + response_type_name: Some("unexpected_FRIEND_Name".to_owned()), + description: Some("My super simple description.\nAnother back".to_owned()), + x_variant_name: Some("Unexpected".to_owned()), + }, + StatusVariant { + status: ResponseStatus::ExpectationFailed, + content_type: None, + response_type_name: Some("unexpected_FRIEND_Name".to_owned()), + description: Some("My super simple description.\nAnother back".to_owned()), + x_variant_name: Some("Expectation".to_owned()), + }, + StatusVariant { + status: ResponseStatus::NotFound, + content_type: None, + response_type_name: None, + description: Some("My super simple description.\nAnother back".to_owned()), + x_variant_name: Some("No".to_owned()), + }, + ] + }, + query_params: vec![] + }), @r###" + pub mod example { + use super::responses; + use actix_swagger::{Answer, ContentType, StatusCode}; + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize)] + #[serde(untagged)] + pub enum Response { + Ok, + Created, + Accepted(responses::UnexpectedFriendName), + #[doc = "My super simple description.\nAnother back"] + BadRequest(responses::UnexpectedFriendName), + #[doc = "My super simple description.\nAnother back"] + Unexpected(responses::UnexpectedFriendName), + #[doc = "My super simple description.\nAnother back"] + Expectation(responses::UnexpectedFriendName), + #[doc = "My super simple description.\nAnother back"] + No, + } + impl Response { + #[inline] + pub fn to_answer(self) -> Answer<'static, Self> { + let status = match self { + Self::Ok => StatusCode::OK, + Self::Created => StatusCode::CREATED, + Self::Accepted(_) => StatusCode::ACCEPTED, + Self::BadRequest(_) => StatusCode::BAD_REQUEST, + Self::Unexpected(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::Expectation(_) => StatusCode::EXPECTATION_FAILED, + Self::No => StatusCode::NOT_FOUND, + }; + let content_type = match self { + Self::Ok => None, + Self::Created => Some(ContentType::Json), + Self::Accepted(_) => Some(ContentType::Json), + Self::BadRequest(_) => Some(ContentType::Json), + Self::Unexpected(_) => Some(ContentType::Json), + Self::Expectation(_) => None, + Self::No => None, + }; + Answer::new(self).status(status).content_type(content_type) + } + } + } + "###); + } + + #[test] + fn path_with_query_params() { + assert_snapshot!(shot(Path { + name: "Example".to_owned(), + response: ResponseEnum { + responses: vec![] + }, + query_params: vec![ + QueryParam { + name: "simple_LONG_DescriptionFor-Me".to_owned(), + description: None, + required: false, + type_ref: "simple_LONG_DescriptionFor-Me".to_owned() + }, + QueryParam { + name: "ARE_YOU_SURE".to_owned(), + description: Some("This is the description".to_owned()), + required: false, + type_ref: "simple_LONG_DescriptionFor-Me".to_owned() + }, + QueryParam { + name: "just-required".to_owned(), + description: None, + required: true, + type_ref: "Another".to_owned() + }, + ] + }), @r###" + pub mod example { + use super::responses; + use actix_swagger::{Answer, ContentType, StatusCode}; + use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize)] + #[serde(untagged)] + pub enum Response {} + impl Response { + #[inline] + pub fn to_answer(self) -> Answer<'static, Self> { + let status = match self {}; + let content_type = match self {}; + Answer::new(self).status(status).content_type(content_type) + } + } + use super::parameters; + #[derive(Debug, Deserialize)] + pub struct QueryParams { + #[serde(rename = "simple_LONG_DescriptionFor-Me")] + pub simple_long_description_for_me: Option, + #[doc = "This is the description"] + #[serde(rename = "ARE_YOU_SURE")] + pub are_you_sure: Option, + #[serde(rename = "just-required")] + pub just_required: parameters::Another, + } + pub type Query = actix_web::http::Query; + } + "###); + } +} diff --git a/openapi-actix/src/printer/paths/response_status.rs b/openapi-actix/src/printer/paths/response_status.rs new file mode 100644 index 0000000..faadae3 --- /dev/null +++ b/openapi-actix/src/printer/paths/response_status.rs @@ -0,0 +1,261 @@ +pub enum ResponseStatus { + Continue, + SwitchingProtocols, + Processing, + Ok, + Created, + Accepted, + NonAuthoritativeInformation, + NoContent, + ResetContent, + PartialContent, + MultiStatus, + AlreadyReported, + ImUsed, + MultipleChoices, + MovedPermanently, + Found, + SeeOther, + NotModified, + UseProxy, + TemporaryRedirect, + PermanentRedirect, + BadRequest, + Unauthorized, + PaymentRequired, + Forbidden, + NotFound, + MethodNotAllowed, + NotAcceptable, + ProxyAuthenticationRequired, + RequestTimeout, + Conflict, + Gone, + LengthRequired, + PreconditionFailed, + PayloadTooLarge, + UriTooLong, + UnsupportedMediaType, + RangeNotSatisfiable, + ExpectationFailed, + ImATeapot, + MisdirectedRequest, + UnprocessableEntity, + Locked, + FailedDependency, + UpgradeRequired, + PreconditionRequired, + TooManyRequests, + RequestHeaderFieldsTooLarge, + UnavailableForLegalReasons, + InternalServerError, + NotImplemented, + BadGateway, + ServiceUnavailable, + GatewayTimeout, + HttpVersionNotSupported, + VariantAlsoNegotiates, + InsufficientStorage, + LoopDetected, + NotExtended, + NetworkAuthenticationRequired, +} + +impl ResponseStatus { + pub fn from_code(code: u16) -> ResponseStatus { + match code { + 100 => Self::Continue, + 101 => Self::SwitchingProtocols, + 102 => Self::Processing, + 200 => Self::Ok, + 201 => Self::Created, + 202 => Self::Accepted, + 203 => Self::NonAuthoritativeInformation, + 204 => Self::NoContent, + 205 => Self::ResetContent, + 206 => Self::PartialContent, + 207 => Self::MultiStatus, + 208 => Self::AlreadyReported, + 226 => Self::ImUsed, + 300 => Self::MultipleChoices, + 301 => Self::MovedPermanently, + 302 => Self::Found, + 303 => Self::SeeOther, + 304 => Self::NotModified, + 305 => Self::UseProxy, + 307 => Self::TemporaryRedirect, + 308 => Self::PermanentRedirect, + 400 => Self::BadRequest, + 401 => Self::Unauthorized, + 402 => Self::PaymentRequired, + 403 => Self::Forbidden, + 404 => Self::NotFound, + 405 => Self::MethodNotAllowed, + 406 => Self::NotAcceptable, + 407 => Self::ProxyAuthenticationRequired, + 408 => Self::RequestTimeout, + 409 => Self::Conflict, + 410 => Self::Gone, + 411 => Self::LengthRequired, + 412 => Self::PreconditionFailed, + 413 => Self::PayloadTooLarge, + 414 => Self::UriTooLong, + 415 => Self::UnsupportedMediaType, + 416 => Self::RangeNotSatisfiable, + 417 => Self::ExpectationFailed, + 418 => Self::ImATeapot, + 421 => Self::MisdirectedRequest, + 422 => Self::UnprocessableEntity, + 423 => Self::Locked, + 424 => Self::FailedDependency, + 426 => Self::UpgradeRequired, + 428 => Self::PreconditionRequired, + 429 => Self::TooManyRequests, + 431 => Self::RequestHeaderFieldsTooLarge, + 451 => Self::UnavailableForLegalReasons, + 500 => Self::InternalServerError, + 501 => Self::NotImplemented, + 502 => Self::BadGateway, + 503 => Self::ServiceUnavailable, + 504 => Self::GatewayTimeout, + 505 => Self::HttpVersionNotSupported, + 506 => Self::VariantAlsoNegotiates, + 507 => Self::InsufficientStorage, + 508 => Self::LoopDetected, + 510 => Self::NotExtended, + 511 => Self::NetworkAuthenticationRequired, + code => panic!("Unexected status code: {}", code), + } + } + + pub fn to_code(&self) -> u16 { + match self { + Self::Continue => 100, + Self::SwitchingProtocols => 101, + Self::Processing => 102, + Self::Ok => 200, + Self::Created => 201, + Self::Accepted => 202, + Self::NonAuthoritativeInformation => 203, + Self::NoContent => 204, + Self::ResetContent => 205, + Self::PartialContent => 206, + Self::MultiStatus => 207, + Self::AlreadyReported => 208, + Self::ImUsed => 226, + Self::MultipleChoices => 300, + Self::MovedPermanently => 301, + Self::Found => 302, + Self::SeeOther => 303, + Self::NotModified => 304, + Self::UseProxy => 305, + Self::TemporaryRedirect => 307, + Self::PermanentRedirect => 308, + Self::BadRequest => 400, + Self::Unauthorized => 401, + Self::PaymentRequired => 402, + Self::Forbidden => 403, + Self::NotFound => 404, + Self::MethodNotAllowed => 405, + Self::NotAcceptable => 406, + Self::ProxyAuthenticationRequired => 407, + Self::RequestTimeout => 408, + Self::Conflict => 409, + Self::Gone => 410, + Self::LengthRequired => 411, + Self::PreconditionFailed => 412, + Self::PayloadTooLarge => 413, + Self::UriTooLong => 414, + Self::UnsupportedMediaType => 415, + Self::RangeNotSatisfiable => 416, + Self::ExpectationFailed => 417, + Self::ImATeapot => 418, + Self::MisdirectedRequest => 421, + Self::UnprocessableEntity => 422, + Self::Locked => 423, + Self::FailedDependency => 424, + Self::UpgradeRequired => 426, + Self::PreconditionRequired => 428, + Self::TooManyRequests => 429, + Self::RequestHeaderFieldsTooLarge => 431, + Self::UnavailableForLegalReasons => 451, + Self::InternalServerError => 500, + Self::NotImplemented => 501, + Self::BadGateway => 502, + Self::ServiceUnavailable => 503, + Self::GatewayTimeout => 504, + Self::HttpVersionNotSupported => 505, + Self::VariantAlsoNegotiates => 506, + Self::InsufficientStorage => 507, + Self::LoopDetected => 508, + Self::NotExtended => 510, + Self::NetworkAuthenticationRequired => 511, + } + } + + pub fn to_string(&self) -> String { + match self { + Self::Continue => "Continue", + Self::SwitchingProtocols => "Switching Protocols", + Self::Processing => "Processing", + Self::Ok => "Ok", + Self::Created => "Created", + Self::Accepted => "Accepted", + Self::NonAuthoritativeInformation => "Non Authoritative Information", + Self::NoContent => "No Content", + Self::ResetContent => "Reset Content", + Self::PartialContent => "Partial Content", + Self::MultiStatus => "Multi Status", + Self::AlreadyReported => "Already Reported", + Self::ImUsed => "Im Used", + Self::MultipleChoices => "Multiple Choices", + Self::MovedPermanently => "Moved Permanently", + Self::Found => "Found", + Self::SeeOther => "See Other", + Self::NotModified => "Not Modified", + Self::UseProxy => "Use Proxy", + Self::TemporaryRedirect => "Temporary Redirect", + Self::PermanentRedirect => "Permanent Redirect", + Self::BadRequest => "Bad Request", + Self::Unauthorized => "Unauthorized", + Self::PaymentRequired => "Payment Required", + Self::Forbidden => "Forbidden", + Self::NotFound => "Not Found", + Self::MethodNotAllowed => "Method Not Allowed", + Self::NotAcceptable => "Not Acceptable", + Self::ProxyAuthenticationRequired => "Proxy Authentication Required", + Self::RequestTimeout => "Request Timeout", + Self::Conflict => "Conflict", + Self::Gone => "Gone", + Self::LengthRequired => "Length Required", + Self::PreconditionFailed => "Precondition Failed", + Self::PayloadTooLarge => "Payload Too Large", + Self::UriTooLong => "Uri Too Long", + Self::UnsupportedMediaType => "Unsupported Media Type", + Self::RangeNotSatisfiable => "Range Not Satisfiable", + Self::ExpectationFailed => "Expectation Failed", + Self::ImATeapot => "Im A Teapot", + Self::MisdirectedRequest => "Misdirected Request", + Self::UnprocessableEntity => "Unprocessable Entity", + Self::Locked => "Locked", + Self::FailedDependency => "Failed Dependency", + Self::UpgradeRequired => "Upgrade Required", + Self::PreconditionRequired => "Precondition Required", + Self::TooManyRequests => "Too Many Requests", + Self::RequestHeaderFieldsTooLarge => "Request Header Fields Too Large", + Self::UnavailableForLegalReasons => "Unavailable For Legal Reasons", + Self::InternalServerError => "Internal Server Error", + Self::NotImplemented => "Not Implemented", + Self::BadGateway => "Bad Gateway", + Self::ServiceUnavailable => "Service Unavailable", + Self::GatewayTimeout => "Gateway Timeout", + Self::HttpVersionNotSupported => "Http Version Not Supported", + Self::VariantAlsoNegotiates => "Variant Also Negotiates", + Self::InsufficientStorage => "Insufficient Storage", + Self::LoopDetected => "Loop Detected", + Self::NotExtended => "Not Extended", + Self::NetworkAuthenticationRequired => "Network Authentication Required", + } + .to_owned() + } +} diff --git a/openapi-actix/src/test.rs b/openapi-actix/src/test.rs new file mode 100644 index 0000000..49ded8d --- /dev/null +++ b/openapi-actix/src/test.rs @@ -0,0 +1,40 @@ +pub fn pretty(input: String) -> String { + use std::fs::File; + use std::io::{Read, Write}; + use std::process::Command; + + let dir = tempfile::tempdir().expect("Failed to create tempdir"); + let file_path = dir.path().join("source.rs"); + println!("{:?}", file_path); + let mut file = File::create(&file_path).expect("Failed to create tempfile"); + + file.write_all(input.as_bytes()) + .expect("Failed to write source"); + + drop(file); + + let mut child = Command::new("rustfmt") + .arg("--emit") + .arg("files") + .arg(file_path.as_os_str()) + .spawn() + .expect("Rustfmt failed to run"); + + let _ = child.wait().expect("Failed to wait for rustfmt"); + + let mut result = String::new(); + + let mut file = File::open(&file_path).expect("Failed to open tempfile"); + file.sync_all().expect("Faile to sync all"); + file.read_to_string(&mut result) + .expect("Failed to read from tempfile"); + + drop(file); + dir.close().expect("Failed to close directory"); + + result.trim().to_owned() +} + +pub fn shot(input: T) -> String { + pretty(input.print().to_string()) +} diff --git a/openapi-hooks/Cargo.toml b/openapi-hooks/Cargo.toml new file mode 100644 index 0000000..5f9a50c --- /dev/null +++ b/openapi-hooks/Cargo.toml @@ -0,0 +1,11 @@ +[package] +authors = ["Sergey Sova "] +edition = "2018" +name = "openapi-hooks" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +openapi-resolver = "0.1.0" +openapiv3 = "0.3.2" diff --git a/openapi-hooks/src/lib.rs b/openapi-hooks/src/lib.rs new file mode 100644 index 0000000..9dbe206 --- /dev/null +++ b/openapi-hooks/src/lib.rs @@ -0,0 +1,80 @@ +use openapi_resolver::RefResolve; +use openapiv3::{ + Header, OpenAPI, Operation, Parameter, ReferenceOr, RequestBody, Response, Schema, + SecurityScheme, +}; + +pub use openapiv3 as v3; + +#[derive(Debug)] +pub enum Method { + Get, + Put, + Post, + Delete, + Options, + Head, + Patch, + Trace, +} + +pub trait Internal<'a> { + fn create_file(&mut self, name: String, content: String); + + fn resolve(&'a self, source: &'a ReferenceOr) -> Option<&'a T> + where + &'a T: RefResolve<'a>; + + fn root(&'a self) -> &'a OpenAPI; +} + +pub trait Plugin<'a, I: Internal<'a>> { + fn on_operation( + &'a mut self, + _method: Method, + _path: &'a str, + _operation: &'a Operation, + _internal: &'a I, + ) { + } + + fn on_security_scheme( + &'a mut self, + _name: &'a str, + _security_scheme: &'a SecurityScheme, + _internal: &'a I, + ) { + } + + fn on_response(&'a mut self, _name: &'a str, _response: &'a Response, _internal: &'a I) {} + + fn on_parameter(&'a mut self, _name: &'a str, _parameter: &'a Parameter, _internal: &'a I) {} + + fn on_request_body( + &'a mut self, + _name: &'a str, + _request_body: &'a RequestBody, + _internal: &'a I, + ) { + } + + fn on_header(&'a mut self, _name: &'a str, _header: &'a Header, _internal: &'a I) {} + + fn on_schema(&'a mut self, _name: &'a str, _schema: &'a Schema, _internal: &'a I) {} + + /// Setup self with data from openapi before iterating over paths + fn pre_paths(&'a mut self, _internal: &'a I) {} + + /// Collect data after iterating over paths + fn post_paths(&'a mut self, _internal: &'a I) {} + + /// First setup before iterating over components + fn pre_components(&'a mut self, _internal: &'a I) {} + + /// Finish collecting components + fn post_components(&'a mut self, _internal: &'a I) {} + + fn proceed(&'a mut self, internal: &'a mut I) { + internal.create_file("lib.rs".to_owned(), "".to_owned()); + } +} diff --git a/openapi-resolver/Cargo.toml b/openapi-resolver/Cargo.toml new file mode 100644 index 0000000..32edb72 --- /dev/null +++ b/openapi-resolver/Cargo.toml @@ -0,0 +1,15 @@ +[package] +authors = ["Sergey Sova "] +description = "$ref resolver for OpenAPI schemas" +edition = "2018" +license = "MIT" +name = "openapi-resolver" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +openapiv3 = "0.3.2" + +[dev-dependencies] +insta = {version = "1.6", features = ["ron", "redactions"]} diff --git a/openapi-resolver/src/lib.rs b/openapi-resolver/src/lib.rs new file mode 100644 index 0000000..0684e00 --- /dev/null +++ b/openapi-resolver/src/lib.rs @@ -0,0 +1,134 @@ +use openapiv3::{ + Header, OpenAPI, Parameter, PathItem, ReferenceOr, RequestBody, Response, Schema, + SecurityScheme, +}; + +pub fn resolve_reference<'a, T>(source: &'a ReferenceOr, root: &'a OpenAPI) -> Option<&'a T> +where + &'a T: RefResolve<'a>, +{ + match source { + ReferenceOr::Item(item) => Some(item), + ReferenceOr::Reference { reference } => RefResolve::resolve(&root, reference), + } +} + +pub trait RefResolve<'d>: Sized { + fn resolve(api: &'d OpenAPI, link: &'d str) -> Option; +} + +impl<'a> RefResolve<'a> for &'a Response { + fn resolve(api: &'a OpenAPI, link: &'a str) -> Option { + if let Some(name) = link.strip_prefix("#/components/responses/") { + match &api.components { + Some(components) => match components.responses.get(name) { + Some(ReferenceOr::Reference { reference }) => Self::resolve(api, reference), + Some(ReferenceOr::Item(item)) => Some(item), + None => None, + }, + None => None, + } + } else { + None + } + } +} + +impl<'a> RefResolve<'a> for &'a Parameter { + fn resolve(api: &'a OpenAPI, link: &'a str) -> Option { + if let Some(name) = link.strip_prefix("#/components/parameters/") { + match &api.components { + Some(components) => match components.parameters.get(name) { + Some(ReferenceOr::Reference { reference }) => Self::resolve(api, reference), + Some(ReferenceOr::Item(item)) => Some(item), + None => None, + }, + None => None, + } + } else { + None + } + } +} + +impl<'a> RefResolve<'a> for &'a SecurityScheme { + fn resolve(api: &'a OpenAPI, link: &'a str) -> Option { + if let Some(name) = link.strip_prefix("#/components/securitySchemes/") { + match &api.components { + Some(components) => match components.security_schemes.get(name) { + Some(ReferenceOr::Reference { reference }) => Self::resolve(api, reference), + Some(ReferenceOr::Item(item)) => Some(item), + None => None, + }, + None => None, + } + } else { + None + } + } +} + +impl<'a> RefResolve<'a> for &'a RequestBody { + fn resolve(api: &'a OpenAPI, link: &'a str) -> Option { + if let Some(name) = link.strip_prefix("#/components/requestBodies/") { + match &api.components { + Some(components) => match components.request_bodies.get(name) { + Some(ReferenceOr::Reference { reference }) => Self::resolve(api, reference), + Some(ReferenceOr::Item(item)) => Some(item), + None => None, + }, + None => None, + } + } else { + None + } + } +} + +impl<'a> RefResolve<'a> for &'a Header { + fn resolve(api: &'a OpenAPI, link: &'a str) -> Option { + if let Some(name) = link.strip_prefix("#/components/headers/") { + match &api.components { + Some(components) => match components.headers.get(name) { + Some(ReferenceOr::Reference { reference }) => Self::resolve(api, reference), + Some(ReferenceOr::Item(item)) => Some(item), + None => None, + }, + None => None, + } + } else { + None + } + } +} + +impl<'a> RefResolve<'a> for &'a Schema { + fn resolve(api: &'a OpenAPI, link: &'a str) -> Option { + if let Some(name) = link.strip_prefix("#/components/schemas/") { + match &api.components { + Some(components) => match components.schemas.get(name) { + Some(ReferenceOr::Reference { reference }) => Self::resolve(api, reference), + Some(ReferenceOr::Item(item)) => Some(item), + None => None, + }, + None => None, + } + } else { + None + } + } +} + +impl<'a> RefResolve<'a> for &'a PathItem { + fn resolve(api: &'a OpenAPI, link: &'a str) -> Option { + if let Some(name) = link.strip_prefix("#/paths/") { + match api.paths.get(name) { + Some(ReferenceOr::Reference { reference }) => Self::resolve(api, reference), + Some(ReferenceOr::Item(item)) => Some(item), + None => None, + } + } else { + None + } + } +}