From 97857c347c8f5d6f907f9e43ce26a627e6a3fd13 Mon Sep 17 00:00:00 2001 From: Adam Leventhal Date: Sun, 17 Oct 2021 10:40:22 -0700 Subject: [PATCH] convert from executable -> executable + library with macro and builder options (#9) --- Cargo.lock | 833 +- Cargo.toml | 33 +- example-build/Cargo.toml | 17 + example-build/build.rs | 22 + example-build/src/main.rs | 12 + example-macro/Cargo.toml | 14 + example-macro/src/main.rs | 7 + progenitor-impl/Cargo.toml | 25 + progenitor-impl/src/lib.rs | 651 + {src => progenitor-impl/src}/template.rs | 40 +- {src => progenitor-impl/src}/to_schema.rs | 70 + progenitor-impl/tests/output/buildomat.out | 414 + progenitor-impl/tests/output/keeper.out | 188 + progenitor-impl/tests/test_output.rs | 39 + progenitor-macro/Cargo.toml | 17 + progenitor-macro/src/lib.rs | 50 + progenitor/Cargo.toml | 23 + progenitor/src/lib.rs | 7 + progenitor/src/main.rs | 249 + sample_openapi/api.github.com.json | 96322 +++++++++++++++++++ sample_openapi/buildomat.json | 801 + sample_openapi/keeper.json | 358 + src/main.rs | 827 - 23 files changed, 100148 insertions(+), 871 deletions(-) create mode 100644 example-build/Cargo.toml create mode 100644 example-build/build.rs create mode 100644 example-build/src/main.rs create mode 100644 example-macro/Cargo.toml create mode 100644 example-macro/src/main.rs create mode 100644 progenitor-impl/Cargo.toml create mode 100644 progenitor-impl/src/lib.rs rename {src => progenitor-impl/src}/template.rs (83%) rename {src => progenitor-impl/src}/to_schema.rs (86%) create mode 100644 progenitor-impl/tests/output/buildomat.out create mode 100644 progenitor-impl/tests/output/keeper.out create mode 100644 progenitor-impl/tests/test_output.rs create mode 100644 progenitor-macro/Cargo.toml create mode 100644 progenitor-macro/src/lib.rs create mode 100644 progenitor/Cargo.toml create mode 100644 progenitor/src/lib.rs create mode 100644 progenitor/src/main.rs create mode 100644 sample_openapi/api.github.com.json create mode 100644 sample_openapi/buildomat.json create mode 100644 sample_openapi/keeper.json delete mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock index ef1ff37..275668f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,24 +23,84 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bumpalo" +version = "3.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "serde", + "time", + "winapi", +] + [[package]] name = "convert_case" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "core-foundation" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[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.8" @@ -53,6 +113,123 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" +[[package]] +name = "encoding_rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "example-build" +version = "0.0.1" +dependencies = [ + "anyhow", + "chrono", + "percent-encoding", + "progenitor", + "reqwest", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "example-macro" +version = "0.0.1" +dependencies = [ + "anyhow", + "chrono", + "percent-encoding", + "progenitor", + "reqwest", + "serde", + "uuid", +] + +[[package]] +name = "expectorate" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804d601ea8a13ddbecf5ab4b6cf75b5d6d0539479c6fb2aea1596e352a5ee27e" +dependencies = [ + "difference", + "newline-converter", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" + +[[package]] +name = "futures-sink" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" + +[[package]] +name = "futures-task" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" + +[[package]] +name = "futures-util" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +dependencies = [ + "autocfg", + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "getopts" version = "0.2.21" @@ -73,6 +250,25 @@ dependencies = [ "wasi", ] +[[package]] +name = "h2" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c06815895acec637cd6ed6e9662c935b866d20a106f8361892893a7d9234964" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -88,6 +284,88 @@ dependencies = [ "winapi", ] +[[package]] +name = "http" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" + +[[package]] +name = "httpdate" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" + +[[package]] +name = "hyper" +version = "0.14.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.7.0" @@ -99,12 +377,27 @@ dependencies = [ "serde", ] +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + [[package]] name = "itoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -123,12 +416,113 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mio" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "native-tls" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "newline-converter" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f81c2b19eebbc4249b3ca6aff70ae05bf18d6a99b7cc63cf0248774e640565" + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + [[package]] name = "openapiv3" version = "1.0.0-beta.2" @@ -141,6 +535,45 @@ dependencies = [ "serde_yaml", ] +[[package]] +name = "openssl" +version = "0.10.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" + +[[package]] +name = "openssl-sys" +version = "0.9.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69df2d8dfc6ce3aaf44b40dec6f487d5a886516cf6879c49e98e0710f310a058" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + [[package]] name = "pest" version = "2.1.3" @@ -151,16 +584,34 @@ dependencies = [ ] [[package]] -name = "ppv-lite86" -version = "0.2.10" +name = "pin-project-lite" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb" + +[[package]] +name = "ppv-lite86" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" [[package]] name = "proc-macro2" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" dependencies = [ "unicode-xid", ] @@ -173,6 +624,23 @@ dependencies = [ "getopts", "indexmap", "openapiv3", + "progenitor-impl", + "progenitor-macro", + "regex", + "rustfmt-wrapper", + "serde", + "serde_json", +] + +[[package]] +name = "progenitor-impl" +version = "0.0.0" +dependencies = [ + "anyhow", + "expectorate", + "getopts", + "indexmap", + "openapiv3", "proc-macro2", "quote", "regex", @@ -180,9 +648,21 @@ dependencies = [ "schemars", "serde", "serde_json", + "thiserror", "typify", ] +[[package]] +name = "progenitor-macro" +version = "0.0.0" +dependencies = [ + "openapiv3", + "progenitor-impl", + "quote", + "serde_json", + "syn", +] + [[package]] name = "quote" version = "1.0.10" @@ -267,6 +747,41 @@ dependencies = [ "winapi", ] +[[package]] +name = "reqwest" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51c732d463dd300362ffb44b7b125f299c23d2990411a4253824630ebc7467fb" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rustfmt-wrapper" version = "0.1.0" @@ -293,6 +808,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi", +] + [[package]] name = "schemars" version = "0.8.6" @@ -317,6 +842,29 @@ dependencies = [ "syn", ] +[[package]] +name = "security-framework" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.11.0" @@ -377,6 +925,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.8.21" @@ -389,6 +949,22 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "socket2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "syn" version = "1.0.80" @@ -434,6 +1010,71 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "pin-project-lite", + "winapi", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + [[package]] name = "toolchain_find" version = "0.2.0" @@ -447,10 +1088,42 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "typify" version = "0.0.1" -source = "git+https://github.com/oxidecomputer/typify#8c134692500802b6174c7ef06cfbc801c8674a78" +source = "git+https://github.com/oxidecomputer/typify#3cf8c1bb87b29cec4615d124362fb8f0872ffebd" dependencies = [ "typify-impl", "typify-macro", @@ -459,7 +1132,7 @@ dependencies = [ [[package]] name = "typify-impl" version = "0.0.1" -source = "git+https://github.com/oxidecomputer/typify#8c134692500802b6174c7ef06cfbc801c8674a78" +source = "git+https://github.com/oxidecomputer/typify#3cf8c1bb87b29cec4615d124362fb8f0872ffebd" dependencies = [ "convert_case", "proc-macro2", @@ -473,7 +1146,7 @@ dependencies = [ [[package]] name = "typify-macro" version = "0.0.1" -source = "git+https://github.com/oxidecomputer/typify#8c134692500802b6174c7ef06cfbc801c8674a78" +source = "git+https://github.com/oxidecomputer/typify#3cf8c1bb87b29cec4615d124362fb8f0872ffebd" dependencies = [ "proc-macro2", "quote", @@ -489,6 +1162,21 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.9" @@ -501,6 +1189,34 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "walkdir" version = "2.3.2" @@ -513,10 +1229,96 @@ dependencies = [ ] [[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +name = "want" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "winapi" @@ -549,6 +1351,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index b526e05..5a483a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,14 @@ -[package] -name = "progenitor" -version = "0.0.0" -edition = "2018" -license = "MPL-2.0" -repository = "https://github.com/oxidecomputer/progenitor.git" -description = "An OpenAPI client generator" +[workspace] +members = [ + "progenitor", + "progenitor-macro", + "progenitor-impl", + "example-build", + "example-macro", +] -[dependencies] -anyhow = "1" -getopts = "0.2" -indexmap = "1.7.0" -openapiv3 = "1.0.0-beta.2" -proc-macro2 = "1.0.29" -quote = "1.0.9" -regex = "1.5.4" -rustfmt-wrapper = "0.1.0" -schemars = "0.8.5" -serde = { version = "1", features = [ "derive" ] } -serde_json = "1.0.68" -typify = { git = "https://github.com/oxidecomputer/typify" } +default-members = [ + "progenitor", + "progenitor-macro", + "progenitor-impl", +] diff --git a/example-build/Cargo.toml b/example-build/Cargo.toml new file mode 100644 index 0000000..caad60b --- /dev/null +++ b/example-build/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "example-build" +version = "0.0.1" +authors = ["Adam H. Leventhal "] +edition = "2018" + +[dependencies] +anyhow = "1.0.44" +percent-encoding = "2.1.0" +serde = { version = "1.0.130", features = ["derive"] } +reqwest = { version = "0.11.5", features = ["json", "stream"] } +uuid = { version = "0.8.2", features = ["serde", "v4"] } +chrono = { version = "0.4.19", features = ["serde"] } + +[build-dependencies] +progenitor = { path = "../progenitor" } +serde_json = "1.0.68" diff --git a/example-build/build.rs b/example-build/build.rs new file mode 100644 index 0000000..0777bf7 --- /dev/null +++ b/example-build/build.rs @@ -0,0 +1,22 @@ +// Copyright 2021 Oxide Computer Company + +use std::{ + env, + fs::{self, File}, + path::Path, +}; + +use progenitor::Generator; + +fn main() { + let file = File::open("../sample_openapi/keeper.json").unwrap(); + let spec = serde_json::from_reader(file).unwrap(); + let mut generator = Generator::new(); + + let content = generator.generate_text(&spec).unwrap(); + + let mut out_file = Path::new(&env::var("OUT_DIR").unwrap()).to_path_buf(); + out_file.push("codegen.rs"); + + fs::write(out_file, content).unwrap(); +} diff --git a/example-build/src/main.rs b/example-build/src/main.rs new file mode 100644 index 0000000..dd1f7c7 --- /dev/null +++ b/example-build/src/main.rs @@ -0,0 +1,12 @@ +// Copyright 2021 Oxide Computer Company + +// Include the generated code. +include!(concat!(env!("OUT_DIR"), "/codegen.rs")); + +fn main() { + let client = Client::new("https://foo/bar"); + let _ = client.enrol(&types::EnrolBody { + host: "".to_string(), + key: "".to_string(), + }); +} diff --git a/example-macro/Cargo.toml b/example-macro/Cargo.toml new file mode 100644 index 0000000..2a9634e --- /dev/null +++ b/example-macro/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "example-macro" +version = "0.0.1" +authors = ["Adam H. Leventhal "] +edition = "2018" + +[dependencies] +progenitor = { path = "../progenitor" } +anyhow = "1.0.44" +percent-encoding = "2.1.0" +serde = { version = "1.0.130", features = ["derive"] } +reqwest = { version = "0.11.5", features = ["json", "stream"] } +uuid = { version = "0.8.2", features = ["serde", "v4"] } +chrono = { version = "0.4.19", features = ["serde"] } \ No newline at end of file diff --git a/example-macro/src/main.rs b/example-macro/src/main.rs new file mode 100644 index 0000000..ec19fd5 --- /dev/null +++ b/example-macro/src/main.rs @@ -0,0 +1,7 @@ +// Copyright 2021 Oxide Computer Company + +use progenitor::generate_api; + +generate_api!("../sample_openapi/keeper.json"); + +fn main() {} diff --git a/progenitor-impl/Cargo.toml b/progenitor-impl/Cargo.toml new file mode 100644 index 0000000..412b73f --- /dev/null +++ b/progenitor-impl/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "progenitor-impl" +version = "0.0.0" +edition = "2018" +license = "MPL-2.0" +repository = "https://github.com/oxidecomputer/progenitor.git" +description = "An OpenAPI client generator - core implementation" + +[dependencies] +anyhow = "1" +getopts = "0.2" +indexmap = "1.7.0" +openapiv3 = "1.0.0-beta.2" +proc-macro2 = "1.0.29" +quote = "1.0.9" +regex = "1.5.4" +rustfmt-wrapper = "0.1.0" +schemars = "0.8.5" +serde = { version = "1", features = [ "derive" ] } +serde_json = "1.0.68" +typify = { git = "https://github.com/oxidecomputer/typify" } +thiserror = "1.0.30" + +[dev-dependencies] +expectorate = "1.0.4" \ No newline at end of file diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs new file mode 100644 index 0000000..0252941 --- /dev/null +++ b/progenitor-impl/src/lib.rs @@ -0,0 +1,651 @@ +// Copyright 2021 Oxide Computer Company + +use std::cmp::Ordering; + +use openapiv3::{OpenAPI, ReferenceOr}; +use proc_macro2::TokenStream; + +use quote::{format_ident, quote}; +use thiserror::Error; +use typify::TypeSpace; + +use crate::to_schema::ToSchema; + +mod template; +mod to_schema; + +#[derive(Error, Debug)] +pub enum Error { + #[error("unexpected value type")] + BadValue(String, serde_json::Value), + #[error("type error")] + TypeError(#[from] typify::Error), + #[error("XXX")] + BadConversion(String), + #[error("invalid operation path")] + InvalidPath(String), + //#[error("unknown")] + //Unknown, +} + +pub type Result = std::result::Result; + +#[derive(Default)] +pub struct Generator { + type_space: TypeSpace, +} + +impl Generator { + pub fn new() -> Self { + Self::default() + } + + pub fn generate_tokens(&mut self, spec: &OpenAPI) -> Result { + // Convert our components dictionary to schemars + let schemas = spec + .components + .iter() + .flat_map(|components| { + components.schemas.iter().map(|(name, ref_or_schema)| { + (name.clone(), ref_or_schema.to_schema()) + }) + }) + .collect::>(); + + self.type_space.set_type_mod("types"); + self.type_space.add_ref_types(schemas)?; + + enum ParamType { + Path, + Query, + Body, + } + + let methods = spec + .operations() + .map(|(path, method, operation)| { + let mut query: Vec<(String, bool)> = Vec::new(); + let mut raw_params = operation + .parameters + .iter() + .map(|parameter| { + match parameter.item()? { + openapiv3::Parameter::Path { + parameter_data, + style: openapiv3::PathStyle::Simple, + } => { + // Path parameters MUST be required. + assert!(parameter_data.required); + + let nam = parameter_data.name.clone(); + let schema = + parameter_data.schema()?.to_schema(); + let typ = self + .type_space + .add_type_details(&schema)? + .parameter; + + Ok((ParamType::Path, nam, typ)) + } + openapiv3::Parameter::Query { + parameter_data, + allow_reserved: _, + style: openapiv3::QueryStyle::Form, + allow_empty_value, + } => { + if let Some(aev) = allow_empty_value { + if *aev { + todo!("allow empty value is a no go"); + } + } + + let nam = parameter_data.name.clone(); + let schema = + parameter_data.schema()?.to_schema(); + let mut typ = self + .type_space + .add_type_details(&schema)? + .parameter; + if !parameter_data.required { + typ = quote! { Option<#typ> }; + } + query.push(( + nam.to_string(), + !parameter_data.required, + )); + Ok((ParamType::Query, nam, typ)) + } + x => todo!("unhandled parameter type: {:#?}", x), + } + }) + .collect::>>()?; + + let mut bounds = Vec::new(); + + let (body_param, body_func) = if let Some(b) = + &operation.request_body + { + let b = b.item()?; + if b.is_binary()? { + bounds.push(quote! {B: Into}); + (Some(quote! {B}), Some(quote! { .body(body) })) + } else { + let mt = b.content_json()?; + if !mt.encoding.is_empty() { + todo!("media type encoding not empty: {:#?}", mt); + } + + if let Some(s) = &mt.schema { + let schema = s.to_schema(); + let typ = self + .type_space + .add_type_details(&schema)? + .parameter; + (Some(typ), Some(quote! { .json(body) })) + } else { + todo!("media type encoding, no schema: {:#?}", mt); + } + } + } else { + (None, None) + }; + + if let Some(body) = body_param { + raw_params.push(( + ParamType::Body, + "body".to_string(), + body, + )); + } + + let tmp = template::parse(path)?; + let names = tmp.names(); + let url_path = tmp.compile(); + + // Put parameters in a deterministic order. + raw_params.sort_by(|a, b| match (&a.0, &b.0) { + // Path params are first and are in positional order. + (ParamType::Path, ParamType::Path) => { + let aa = names.iter().position(|x| x == &a.1).unwrap(); + let bb = names.iter().position(|x| x == &b.1).unwrap(); + aa.cmp(&bb) + } + (ParamType::Path, ParamType::Query) => Ordering::Less, + (ParamType::Path, ParamType::Body) => Ordering::Less, + + // Query params are in lexicographic order. + (ParamType::Query, ParamType::Body) => Ordering::Less, + (ParamType::Query, ParamType::Query) => a.1.cmp(&b.1), + (ParamType::Query, ParamType::Path) => Ordering::Greater, + + // Body params are last and should be unique + (ParamType::Body, ParamType::Path) => Ordering::Greater, + (ParamType::Body, ParamType::Query) => Ordering::Greater, + (ParamType::Body, ParamType::Body) => { + panic!("should only be one body") + } + }); + + let (response_type, decode_response) = if operation + .responses + .responses + .len() + == 1 + { + let only = operation.responses.responses.first().unwrap(); + if !matches!(only.0, openapiv3::StatusCode::Code(200..=299)) + { + todo!("code? {:#?}", only); + } + + let i = only.1.item()?; + if !i.headers.is_empty() { + todo!("no response headers for now"); + } + + if !i.links.is_empty() { + todo!("no response links for now"); + } + + // Look at the response content. For now, support a + // single JSON-formatted response. + let typ = match ( + i.content.len(), + i.content.get("application/json"), + ) { + (0, _) => quote! { () }, + (1, Some(mt)) => { + if !mt.encoding.is_empty() { + todo!( + "media type encoding not empty: {:#?}", + mt + ); + } + + if let Some(schema) = &mt.schema { + let schema = schema.to_schema(); + self.type_space.add_type_details(&schema)?.ident + } else { + todo!( + "media type encoding, no schema: {:#?}", + mt + ); + } + } + (1, None) => { + todo!( + "response content not JSON: {:#?}", + i.content + ); + } + (_, _) => { + todo!( + "too many response contents: {:#?}", + i.content + ); + } + }; + (typ, quote! { res.json().await? }) + } else if operation.responses.responses.is_empty() { + (quote! { reqwest::Response }, quote! { res }) + } else { + todo!("responses? {:#?}", operation.responses); + }; + + let operation_id = format_ident!( + "{}", + operation.operation_id.as_deref().unwrap() + ); + + let bounds = if bounds.is_empty() { + quote! {} + } else { + quote! { + < #(#bounds),* > + } + }; + + let params = raw_params.into_iter().map(|(_, name, typ)| { + let name = format_ident!("{}", name); + quote! { + #name: #typ + } + }); + + let (query_build, query_use) = if query.is_empty() { + (quote! {}, quote! {}) + } else { + let query_items = query.iter().map(|(qn, opt)| { + if *opt { + let qn_ident = format_ident!("{}", qn); + quote! { + if let Some(v) = & #qn_ident { + query.push((#qn, v.to_string())); + } + } + } else { + quote! { + query.push((#qn, #qn.to_string())); + } + } + }); + + let query_build = quote! { + let mut query = Vec::new(); + #(#query_items)* + }; + let query_use = quote! { + .query(&query) + }; + + (query_build, query_use) + }; + + let doc_comment = format!( + "{}: {} {}", + operation.operation_id.as_deref().unwrap(), + method.to_ascii_uppercase(), + path + ); + + let method_func = format_ident!("{}", method); + + let method = quote! { + #[doc = #doc_comment] + pub async fn #operation_id #bounds ( + &self, + #(#params),* + ) -> Result<#response_type> { + #url_path + #query_build + + let res = self.client + . #method_func (url) + #body_func + #query_use + .send() + .await? + .error_for_status()?; + + Ok(#decode_response) + } + }; + + Ok(method) + }) + .collect::>>()?; + + let mut types = self + .type_space + .iter_types() + .map(|type_entry| { + ( + type_entry.type_name(&self.type_space), + type_entry.output(&self.type_space), + ) + }) + .collect::>(); + types.sort_by(|a, b| a.0.cmp(&b.0)); + let types = types.into_iter().map(|(_, def)| def); + + let file = quote! { + use anyhow::Result; + + mod progenitor_support { + use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; + + #[allow(dead_code)] + const PATH_SET: &AsciiSet = &CONTROLS + .add(b' ') + .add(b'"') + .add(b'#') + .add(b'<') + .add(b'>') + .add(b'?') + .add(b'`') + .add(b'{') + .add(b'}'); + + #[allow(dead_code)] + pub(crate) fn encode_path(pc: &str) -> String { + utf8_percent_encode(pc, PATH_SET).to_string() + } + } + + pub mod types { + use serde::{Deserialize, Serialize}; + #(#types)* + } + + #[derive(Clone)] + pub struct Client { + baseurl: String, + client: reqwest::Client, + } + + impl Client { + pub fn new(baseurl: &str) -> Client { + let dur = std::time::Duration::from_secs(15); + let client = reqwest::ClientBuilder::new() + .connect_timeout(dur) + .timeout(dur) + .build() + .unwrap(); + + Client::new_with_client(baseurl, client) + } + + pub fn new_with_client( + baseurl: &str, + client: reqwest::Client, + ) -> Client { + Client { + baseurl: baseurl.to_string(), + client, + } + } + + #(#methods)* + } + }; + + Ok(file) + } + + pub fn generate_text(&mut self, spec: &OpenAPI) -> Result { + let output = self.generate_tokens(spec)?; + + // Format the file with rustfmt and some whitespace niceties. + let content = rustfmt_wrapper::rustfmt(output).unwrap(); + + Ok(if cfg!(not(windows)) { + let regex = regex::Regex::new(r#"(})(\n\s*[^} ])"#).unwrap(); + regex.replace_all(&content, "$1\n$2").to_string() + } else { + let regex = regex::Regex::new(r#"(})(\r\n\s*[^} ])"#).unwrap(); + regex.replace_all(&content, "$1\r\n$2").to_string() + }) + } + + pub fn dependencies(&self) -> Vec { + let mut deps = vec![ + "anyhow = \"1.0.44\"", + "percent-encoding = \"2.1.0\"", + "serde = { version = \"1.0.130\", features = [\"derive\"] }", + "reqwest = { version = \"0.11.5\", features = [\"json\", \"stream\"] }", + ]; + if self.type_space.uses_uuid() { + deps.push( + "uuid = { version = \"0.8.2\", features = [\"serde\", \"v4\"] }", + ) + } + if self.type_space.uses_chrono() { + deps.push( + "chrono = { version = \"0.4.19\", features = [\"serde\"] }", + ) + } + deps.sort_unstable(); + deps.iter().map(ToString::to_string).collect() + } + + pub fn get_type_space(&self) -> &TypeSpace { + &self.type_space + } +} + +trait ParameterDataExt { + fn schema(&self) -> Result<&openapiv3::ReferenceOr>; +} + +impl ParameterDataExt for openapiv3::ParameterData { + fn schema(&self) -> Result<&openapiv3::ReferenceOr> { + match &self.format { + openapiv3::ParameterSchemaOrContent::Schema(s) => Ok(s), + x => { + Err(Error::BadConversion(format!("XXX param format {:#?}", x))) + } + } + } +} + +trait ExtractJsonMediaType { + fn is_binary(&self) -> Result; + fn content_json(&self) -> Result; +} + +impl ExtractJsonMediaType for openapiv3::Response { + fn content_json(&self) -> Result { + if self.content.len() != 1 { + todo!("expected one content entry, found {}", self.content.len()); + } + + if let Some(mt) = self.content.get("application/json") { + Ok(mt.clone()) + } else { + todo!( + "could not find application/json, only found {}", + self.content.keys().next().unwrap() + ); + } + } + + fn is_binary(&self) -> Result { + if self.content.is_empty() { + /* + * XXX If there are no content types, I guess it is not binary? + */ + return Ok(false); + } + + if self.content.len() != 1 { + todo!("expected one content entry, found {}", self.content.len()); + } + + if let Some(mt) = self.content.get("application/octet-stream") { + if !mt.encoding.is_empty() { + todo!("XXX encoding"); + } + + if let Some(s) = &mt.schema { + use openapiv3::{ + SchemaKind, StringFormat, Type, + VariantOrUnknownOrEmpty::Item, + }; + + let s = s.item()?; + if s.schema_data.nullable { + todo!("XXX nullable binary?"); + } + if s.schema_data.default.is_some() { + todo!("XXX default binary?"); + } + if s.schema_data.discriminator.is_some() { + todo!("XXX binary discriminator?"); + } + match &s.schema_kind { + SchemaKind::Type(Type::String(st)) => { + if st.min_length.is_some() || st.max_length.is_some() { + todo!("binary min/max length"); + } + if !matches!(st.format, Item(StringFormat::Binary)) { + todo!( + "expected binary format string, got {:?}", + st.format + ); + } + if st.pattern.is_some() { + todo!("XXX pattern"); + } + if !st.enumeration.is_empty() { + todo!("XXX enumeration"); + } + return Ok(true); + } + x => { + todo!("XXX schemakind type {:?}", x); + } + } + } else { + todo!("binary thing had no schema?"); + } + } + + Ok(false) + } +} + +impl ExtractJsonMediaType for openapiv3::RequestBody { + fn content_json(&self) -> Result { + if self.content.len() != 1 { + todo!("expected one content entry, found {}", self.content.len()); + } + + if let Some(mt) = self.content.get("application/json") { + Ok(mt.clone()) + } else { + todo!( + "could not find application/json, only found {}", + self.content.keys().next().unwrap() + ); + } + } + + fn is_binary(&self) -> Result { + if self.content.is_empty() { + /* + * XXX If there are no content types, I guess it is not binary? + */ + return Ok(false); + } + + if self.content.len() != 1 { + todo!("expected one content entry, found {}", self.content.len()); + } + + if let Some(mt) = self.content.get("application/octet-stream") { + if !mt.encoding.is_empty() { + todo!("XXX encoding"); + } + + if let Some(s) = &mt.schema { + use openapiv3::{ + SchemaKind, StringFormat, Type, + VariantOrUnknownOrEmpty::Item, + }; + + let s = s.item()?; + if s.schema_data.nullable { + todo!("XXX nullable binary?"); + } + if s.schema_data.default.is_some() { + todo!("XXX default binary?"); + } + if s.schema_data.discriminator.is_some() { + todo!("XXX binary discriminator?"); + } + match &s.schema_kind { + SchemaKind::Type(Type::String(st)) => { + if st.min_length.is_some() || st.max_length.is_some() { + todo!("binary min/max length"); + } + if !matches!(st.format, Item(StringFormat::Binary)) { + todo!( + "expected binary format string, got {:?}", + st.format + ); + } + if st.pattern.is_some() { + todo!("XXX pattern"); + } + if !st.enumeration.is_empty() { + todo!("XXX enumeration"); + } + return Ok(true); + } + x => { + todo!("XXX schemakind type {:?}", x); + } + } + } else { + todo!("binary thing had no schema?"); + } + } + + Ok(false) + } +} + +trait ReferenceOrExt { + fn item(&self) -> Result<&T>; +} + +impl ReferenceOrExt for openapiv3::ReferenceOr { + fn item(&self) -> Result<&T> { + match self { + ReferenceOr::Reference { .. } => { + Err(Error::BadConversion("unexpected reference".to_string())) + } + ReferenceOr::Item(item) => Ok(item), + } + } +} diff --git a/src/template.rs b/progenitor-impl/src/template.rs similarity index 83% rename from src/template.rs rename to progenitor-impl/src/template.rs index 5c64e02..d55c561 100644 --- a/src/template.rs +++ b/progenitor-impl/src/template.rs @@ -1,7 +1,10 @@ -use anyhow::{anyhow, bail, Context, Result}; +// Copyright 2021 Oxide Computer Company + use proc_macro2::TokenStream; use quote::{format_ident, quote}; +use crate::{Error, Result}; + #[derive(Eq, PartialEq, Clone, Debug)] enum Component { Constant(String), @@ -53,11 +56,6 @@ impl Template { } pub fn parse(t: &str) -> Result