diff --git a/Cargo.lock b/Cargo.lock index 7d3f160..a88039f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -70,6 +70,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + [[package]] name = "clap" version = "4.5.19" @@ -128,6 +134,49 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.13.0" @@ -146,6 +195,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "globset" version = "0.4.15" @@ -173,11 +231,10 @@ dependencies = [ "insta", "itertools", "minijinja", - "openapiv3", + "oas3", "regex-lite", "serde", "serde_json", - "serde_yaml", "thiserror", ] @@ -187,6 +244,118 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.6.0" @@ -195,7 +364,6 @@ checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", - "serde", ] [[package]] @@ -246,12 +414,24 @@ version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +[[package]] +name = "libyaml-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e126dda6f34391ab7b444f9922055facc83c07a910da3eb16f1e4d9c45dc777" + [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + [[package]] name = "log" version = "0.4.22" @@ -274,14 +454,42 @@ dependencies = [ ] [[package]] -name = "openapiv3" -version = "2.0.0" +name = "oas3" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc02deea53ffe807708244e5914f6b099ad7015a207ee24317c22112e17d9c5c" +checksum = "05ed0821ab10d7703415a06df039c2493f3a7667999d8b4e104731de0c53796f" dependencies = [ - "indexmap", + "derive_more", + "http", + "log", + "once_cell", + "regex", + "semver", "serde", "serde_json", + "url", + "yaml_serde", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", ] [[package]] @@ -302,11 +510,23 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -325,6 +545,15 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.18" @@ -340,6 +569,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.210" @@ -372,25 +607,24 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "similar" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "strsim" version = "0.11.1" @@ -408,6 +642,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thiserror" version = "1.0.64" @@ -428,6 +673,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "unicode-ident" version = "1.0.13" @@ -435,10 +690,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] -name = "unsafe-libyaml" -version = "0.2.11" +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" @@ -546,3 +826,99 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yaml_serde" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c7c1b1a6a7c8a6b2741a6c21a4f8918e51899b111cfa08d1288202656e3975" +dependencies = [ + "indexmap", + "itoa", + "libyaml-rs", + "ryu", + "serde", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 53eae43..db1a961 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,10 @@ repository = "https://github.com/PatOConnor43/heave" clap = { version = "4.4.18", features = ["derive"] } itertools = "0.12.1" minijinja = "1.0.12" -openapiv3 = "2.0.0" +oas3 = { version = "0.21", features = ["yaml-spec"] } regex-lite = "0.1.6" serde = { version = "1.0.196", features = ["derive"] } serde_json = "1.0.113" -serde_yaml = "0.9.31" thiserror = "1.0.57" [profile.dev.package] diff --git a/src/main.rs b/src/main.rs index 13e7b78..08da5d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ use clap::{Args, Parser, Subcommand}; use itertools::Itertools; use minijinja::{context, Environment}; -use openapiv3::{MediaType, OpenAPI, ReferenceOr}; +use oas3::spec::{ + MediaType, ObjectOrReference, ObjectSchema, Parameter, ParameterIn, RequestBody, Response, + SchemaType, SchemaTypeSet, Spec, +}; use std::{ error::Error, path::{Path, PathBuf}, @@ -483,23 +486,23 @@ fn main() -> Result<(), Box> { let input_extension = match &input_path.extension() { Some(ext) => match ext.to_str() { Some("json") => Ok(InputSpecExtension::Json), - Some("yaml") => Ok(InputSpecExtension::Yaml), + Some("yaml") | Some("yml") => Ok(InputSpecExtension::Yaml), _ => Err("Input spec must be json or yaml file"), }, None => Err("Input spec must be json or yaml file"), }?; let content = std::fs::read_to_string(input_path)?; - let openapi: OpenAPI = match input_extension { + let spec: Spec = match input_extension { InputSpecExtension::Json => { - serde_json::from_str(&content).expect("Could not deserialize input as json") + oas3::from_json(&content).expect("Could not deserialize input as json") } InputSpecExtension::Yaml => { - serde_yaml::from_str(&content).expect("Could not deserialize input as yaml") + oas3::from_yaml(&content).expect("Could not deserialize input as yaml") } }; - let result = generate(openapi); + let result = generate(spec); let mut final_outputs = result.outputs; if args.include_paths.is_some() { let include_paths = args.include_paths.unwrap(); @@ -633,10 +636,10 @@ fn write_outputs( Ok(()) } -fn generate(openapi: openapiv3::OpenAPI) -> GenerateResult { +fn generate(spec: Spec) -> GenerateResult { let mut outputs: Vec = vec![]; let mut diagnostics: Vec = vec![]; - for (path, method, operation) in openapi.operations() { + for (path, method, operation) in spec.operations() { let name = operation .operation_id .clone() @@ -650,18 +653,18 @@ fn generate(openapi: openapiv3::OpenAPI) -> GenerateResult { }; for parameter in operation.parameters.iter() { match parameter { - openapiv3::ReferenceOr::Reference { reference } => { - let parameter_name = reference.split("#/components/parameters/").nth(1); + ObjectOrReference::Ref { ref_path, .. } => { + let parameter_name = ref_path.split("#/components/parameters/").nth(1); if parameter_name.is_none() { diagnostics.push(HeaveError::MalformedParameterReference { operation: name.to_string(), path: path.to_string(), - reference: reference.to_string(), + reference: ref_path.to_string(), }); continue; } let parameter_name = parameter_name.unwrap(); - let components = &openapi.components; + let components = &spec.components; if components.is_none() { diagnostics.push(HeaveError::MissingComponents); continue; @@ -671,41 +674,30 @@ fn generate(openapi: openapiv3::OpenAPI) -> GenerateResult { if found_parameter.is_none() { diagnostics.push(HeaveError::MissingParameterReference { context: context.clone(), - reference: reference.to_string(), + reference: ref_path.to_string(), }); continue; } let found_parameter = found_parameter.unwrap(); - // TODO add support for reference parameters - if found_parameter.as_item().is_none() { - continue; - } - let found_parameter = found_parameter.as_item().unwrap(); match found_parameter { - openapiv3::Parameter::Query { parameter_data, .. } => { - query_parameters.push(parameter_data.name.to_string()); + ObjectOrReference::Object(param) => { + classify_parameter(param, &mut query_parameters, &mut header_parameters); } - openapiv3::Parameter::Header { parameter_data, .. } => { - header_parameters.push(parameter_data.name.to_string()); + // TODO add support for nested reference parameters + ObjectOrReference::Ref { .. } => { + continue; } - _ => {} } } - openapiv3::ReferenceOr::Item(item) => match item { - openapiv3::Parameter::Query { parameter_data, .. } => { - query_parameters.push(parameter_data.name.to_string()); - } - openapiv3::Parameter::Header { parameter_data, .. } => { - header_parameters.push(parameter_data.name.to_string()); - } - _ => {} - }, + ObjectOrReference::Object(param) => { + classify_parameter(param, &mut query_parameters, &mut header_parameters); + } } } while let Some(request_body) = &operation.request_body { let (request_body, mut inner_diagnostics) = - resolve_request_body(&openapi, request_body, &context); + resolve_request_body(&spec, request_body, &context); diagnostics.append(&mut inner_diagnostics); if request_body.is_none() { break; @@ -733,14 +725,14 @@ fn generate(openapi: openapiv3::OpenAPI) -> GenerateResult { break; } let schema = schema.as_ref().unwrap(); - let (schema, mut inner_diagnostics) = resolve_schema(&openapi, schema, &context); + let (schema, mut inner_diagnostics) = resolve_schema(&spec, schema, &context); diagnostics.append(&mut inner_diagnostics); if schema.is_none() { break; } let schema = schema.unwrap(); let request_body_parameter_tuple = - generate_request_body_from_schema(&openapi, schema, None, &context, "$"); + generate_request_body_from_schema(&spec, schema, None, &context, "$"); request_body_parameter = request_body_parameter_tuple.0; let mut inner_diagnostics = request_body_parameter_tuple.1; diagnostics.append(&mut inner_diagnostics); @@ -754,75 +746,36 @@ fn generate(openapi: openapiv3::OpenAPI) -> GenerateResult { break; } - for (status_code, response) in operation.responses.responses.iter() { - let mut asserts: Vec = vec![]; - match status_code { - openapiv3::StatusCode::Range(_) => { + if let Some(responses) = &operation.responses { + for (status_code_str, response) in responses.iter() { + let mut asserts: Vec = vec![]; + // Check if this is a range like "2XX" + let parsed_code: Option = status_code_str.parse().ok(); + if parsed_code.is_none() { diagnostics.push(HeaveError::UnsupportedStatusCodeRange { context: context.clone(), - }) + }); + continue; } - openapiv3::StatusCode::Code(code) => { - let name = format!("{}_{}.hurl", name, code); - let (response, mut inner_diagnostics) = - resolve_response(&openapi, response, &context); - diagnostics.append(&mut inner_diagnostics); - if response.is_none() { - continue; - } - let response = response.unwrap(); - let mut media_type: Option<&MediaType> = None; - for (media_type_key, media_type_val) in response.content.iter() { - if media_type_key.starts_with("application/json") { - media_type = Some(media_type_val); - break; - } - } - if media_type.is_none() { - let output = Output { - expected_status_code: *code, - name, - hurl_path: path.to_string().replace("{", "{{").replace("}", "}}"), - oas_path: path.to_string(), - oas_operation_id: operation.operation_id.clone(), - method: method.to_string().to_uppercase(), - header_parameters: header_parameters.clone(), - query_parameters: query_parameters.clone(), - asserts: vec![], - request_body_parameter: request_body_parameter - .clone() - .unwrap_or("".to_string()), - }; - outputs.push(output); - continue; - } - let schema = media_type.unwrap().schema.as_ref(); - if schema.is_none() { - diagnostics.push(HeaveError::MissingSchemaDefinitionForMediaType { - context: context.clone(), - }); - continue; - } - let schema = schema.unwrap(); - let (schema, mut inner_diagnostics) = - resolve_schema(&openapi, schema, &context); - diagnostics.append(&mut inner_diagnostics); - if schema.is_none() { - continue; + let code = parsed_code.unwrap(); + let name = format!("{}_{}.hurl", name, code); + let (response, mut inner_diagnostics) = + resolve_response(&spec, response, &context); + diagnostics.append(&mut inner_diagnostics); + if response.is_none() { + continue; + } + let response = response.unwrap(); + let mut media_type: Option<&MediaType> = None; + for (media_type_key, media_type_val) in response.content.iter() { + if media_type_key.starts_with("application/json") { + media_type = Some(media_type_val); + break; } - let schema = schema.unwrap(); - let is_required = true; - let (mut new_asserts, mut new_diagnostics) = - generate_assert_from_schema(&openapi, schema, "$", is_required, &context); - asserts.append(&mut new_asserts); - diagnostics.append(&mut new_diagnostics); - - // It's possible for identical asserts to be generated when dealing with - // polymorphic attributes (like allOf). This cleans that up. - let asserts: Vec<_> = asserts.into_iter().unique().collect(); - + } + if media_type.is_none() { let output = Output { - expected_status_code: *code, + expected_status_code: code, name, hurl_path: path.to_string().replace("{", "{{").replace("}", "}}"), oas_path: path.to_string(), @@ -830,14 +783,55 @@ fn generate(openapi: openapiv3::OpenAPI) -> GenerateResult { method: method.to_string().to_uppercase(), header_parameters: header_parameters.clone(), query_parameters: query_parameters.clone(), - asserts: asserts.clone(), + asserts: vec![], request_body_parameter: request_body_parameter .clone() .unwrap_or("".to_string()), }; - outputs.push(output) + outputs.push(output); + continue; } - }; + let schema = media_type.unwrap().schema.as_ref(); + if schema.is_none() { + diagnostics.push(HeaveError::MissingSchemaDefinitionForMediaType { + context: context.clone(), + }); + continue; + } + let schema = schema.unwrap(); + let (schema, mut inner_diagnostics) = + resolve_schema(&spec, schema, &context); + diagnostics.append(&mut inner_diagnostics); + if schema.is_none() { + continue; + } + let schema = schema.unwrap(); + let is_required = true; + let (mut new_asserts, mut new_diagnostics) = + generate_assert_from_schema(&spec, schema, "$", is_required, &context); + asserts.append(&mut new_asserts); + diagnostics.append(&mut new_diagnostics); + + // It's possible for identical asserts to be generated when dealing with + // polymorphic attributes (like allOf). This cleans that up. + let asserts: Vec<_> = asserts.into_iter().unique().collect(); + + let output = Output { + expected_status_code: code, + name, + hurl_path: path.to_string().replace("{", "{{").replace("}", "}}"), + oas_path: path.to_string(), + oas_operation_id: operation.operation_id.clone(), + method: method.to_string().to_uppercase(), + header_parameters: header_parameters.clone(), + query_parameters: query_parameters.clone(), + asserts: asserts.clone(), + request_body_parameter: request_body_parameter + .clone() + .unwrap_or("".to_string()), + }; + outputs.push(output) + } } } @@ -847,15 +841,31 @@ fn generate(openapi: openapiv3::OpenAPI) -> GenerateResult { } } +fn classify_parameter( + param: &Parameter, + query_parameters: &mut Vec, + header_parameters: &mut Vec, +) { + match param.location { + ParameterIn::Query => { + query_parameters.push(param.name.to_string()); + } + ParameterIn::Header => { + header_parameters.push(param.name.to_string()); + } + _ => {} + } +} + fn generate_assert_from_schema( - openapi: &openapiv3::OpenAPI, - schema: &openapiv3::Schema, + spec: &Spec, + schema: &ObjectSchema, jsonpath: &str, is_required: bool, diagnostic_context: &DiagnosticContext, ) -> (Vec, Vec) { // We don't need to generate an assert for a field that is write only - if schema.schema_data.write_only { + if schema.write_only.unwrap_or(false) { return (vec![], vec![]); } @@ -901,80 +911,113 @@ fn generate_assert_from_schema( default ) }; - match &schema.schema_kind { - openapiv3::SchemaKind::OneOf { .. } => { + + // Check composition keywords first + let composition_count = [ + !schema.one_of.is_empty(), + !schema.all_of.is_empty(), + !schema.any_of.is_empty(), + ] + .iter() + .filter(|&&x| x) + .count(); + if composition_count > 1 { + diagnostics.push(HeaveError::UnsupportedSchemaKind { + context: diagnostic_context.clone(), + kind: "Any".to_string(), + jsonpath: jsonpath.to_string(), + }); + return (asserts, diagnostics); + } + if !schema.one_of.is_empty() { + diagnostics.push(HeaveError::UnsupportedSchemaKind { + context: diagnostic_context.clone(), + kind: "OneOf".to_string(), + jsonpath: jsonpath.to_string(), + }); + return (asserts, diagnostics); + } + if !schema.all_of.is_empty() { + for all_of_schema_or_ref in &schema.all_of { + let (all_of_schema, mut inner_diagnostics) = + resolve_schema(spec, all_of_schema_or_ref, diagnostic_context); + diagnostics.append(&mut inner_diagnostics); + + if let Some(s) = all_of_schema { + let (mut child_asserts, mut child_diagnostics) = generate_assert_from_schema( + spec, + s, + jsonpath, + is_required, + diagnostic_context, + ); + asserts.append(&mut child_asserts); + diagnostics.append(&mut child_diagnostics); + } + } + return (asserts, diagnostics); + } + if !schema.any_of.is_empty() { + diagnostics.push(HeaveError::UnsupportedSchemaKind { + context: diagnostic_context.clone(), + kind: "AnyOf".to_string(), + jsonpath: jsonpath.to_string(), + }); + return (asserts, diagnostics); + } + + // Determine the primary type from schema_type + let type_set = match &schema.schema_type { + Some(type_set) => type_set, + None => { diagnostics.push(HeaveError::UnsupportedSchemaKind { context: diagnostic_context.clone(), - kind: "OneOf".to_string(), + kind: "Any".to_string(), jsonpath: jsonpath.to_string(), - }) + }); + return (asserts, diagnostics); } - openapiv3::SchemaKind::AllOf { all_of } => { - for all_of_schema_or_ref in all_of { - let (all_of_schema, mut inner_diagnostics) = - resolve_schema(openapi, all_of_schema_or_ref, diagnostic_context); - diagnostics.append(&mut inner_diagnostics); + }; - if let Some(s) = all_of_schema { - let (mut child_asserts, mut child_diagnostics) = generate_assert_from_schema( - openapi, - s, - jsonpath, - is_required, - diagnostic_context, - ); - asserts.append(&mut child_asserts); - diagnostics.append(&mut child_diagnostics); - } + let non_null_types = get_non_null_types(type_set); + if non_null_types.len() > 1 { + for t in &non_null_types { + if let Some(predicate) = schema_type_to_hurl_predicate(*t) { + asserts.push(format!( + "#jsonpath \"{}\" {}", + jsonpath, predicate + )); } } - openapiv3::SchemaKind::AnyOf { .. } => { - diagnostics.push(HeaveError::UnsupportedSchemaKind { - context: diagnostic_context.clone(), - kind: "AnyOf".to_string(), - jsonpath: jsonpath.to_string(), - }) + return (asserts, diagnostics); + } + + let primary_type = get_primary_type(type_set); + + match primary_type { + Some(SchemaType::Boolean) => { + asserts.push(is_required_formatter(jsonpath, "isBoolean", is_required)) } - openapiv3::SchemaKind::Not { .. } => diagnostics.push(HeaveError::UnsupportedSchemaKind { - context: diagnostic_context.clone(), - kind: "Not".to_string(), - jsonpath: jsonpath.to_string(), - }), - openapiv3::SchemaKind::Any(_) => diagnostics.push(HeaveError::UnsupportedSchemaKind { - context: diagnostic_context.clone(), - kind: "Any".to_string(), - jsonpath: jsonpath.to_string(), - }), - openapiv3::SchemaKind::Type(schema_type) => { - match schema_type { - openapiv3::Type::Boolean(_) => { - asserts.push(is_required_formatter(jsonpath, "isBoolean", is_required)) - } - openapiv3::Type::String(_) => { - asserts.push(is_required_formatter(jsonpath, "isString", is_required)) - } - openapiv3::Type::Number(_) => { - asserts.push(is_required_formatter(jsonpath, "isNumber", is_required)) - } - openapiv3::Type::Integer(_) => { - asserts.push(is_required_formatter(jsonpath, "isInteger", is_required)) - } - openapiv3::Type::Array(a) => { - asserts.push(is_required_formatter(jsonpath, "isCollection", is_required)); - let items = &a.items; - if items.is_none() { - return (asserts, diagnostics); - } - let items = items.as_ref().unwrap(); - let unboxed = items.clone().unbox(); - let (inner, mut inner_diagnostics) = - resolve_schema(openapi, &unboxed, diagnostic_context); + Some(SchemaType::String) => { + asserts.push(is_required_formatter(jsonpath, "isString", is_required)) + } + Some(SchemaType::Number) => { + asserts.push(is_required_formatter(jsonpath, "isNumber", is_required)) + } + Some(SchemaType::Integer) => { + asserts.push(is_required_formatter(jsonpath, "isInteger", is_required)) + } + Some(SchemaType::Array) => { + asserts.push(is_required_formatter(jsonpath, "isCollection", is_required)); + let items = &schema.items; + if items.is_none() { + return (asserts, diagnostics); + } + let items = items.as_ref().unwrap(); + let inner = resolve_schema_from_schema(spec, items, diagnostic_context); + match inner { + (Some(inner), mut inner_diagnostics) => { diagnostics.append(&mut inner_diagnostics); - if inner.is_none() { - return (asserts, diagnostics); - } - let inner = inner.unwrap(); - // Take the existing path and index the first element in the list. let inner_jsonpath = format!("{}[0]", jsonpath); @@ -982,7 +1025,7 @@ fn generate_assert_from_schema( let is_required = false; let (mut child_asserts, mut child_diagnostics) = generate_assert_from_schema( - openapi, + spec, inner, inner_jsonpath.as_ref(), is_required, @@ -991,64 +1034,119 @@ fn generate_assert_from_schema( asserts.append(&mut child_asserts); diagnostics.append(&mut child_diagnostics); } - openapiv3::Type::Object(ob) => { - asserts.push(is_required_formatter(jsonpath, "isCollection", is_required)); - let properties = &ob.properties; - for (name, prop) in properties.iter() { - let unboxed = prop.clone().unbox(); - let (inner, mut inner_diagnostics) = - resolve_schema(openapi, &unboxed, diagnostic_context); - diagnostics.append(&mut inner_diagnostics); - if inner.is_none() { - break; - } - let inner = inner.unwrap(); - - // There are characters that aren't allowed in jsonpath so we change the format - // if they're present. - let inner_jsonpath = if name.chars().any(|c| c == '@' || c == '$') { - format!("{}['{}']", jsonpath, name) - } else { - format!("{}.{}", jsonpath, name) - }; - let child_is_required = is_required && ob.required.contains(name); - let (mut child_asserts, mut child_diagnostics) = - generate_assert_from_schema( - openapi, - inner, - inner_jsonpath.as_ref(), - child_is_required, - diagnostic_context, - ); - asserts.append(&mut child_asserts); - diagnostics.append(&mut child_diagnostics); - } + (None, mut inner_diagnostics) => { + diagnostics.append(&mut inner_diagnostics); + return (asserts, diagnostics); } } } + Some(SchemaType::Object) => { + asserts.push(is_required_formatter(jsonpath, "isCollection", is_required)); + let properties = &schema.properties; + for (name, prop) in properties.iter() { + let (inner, mut inner_diagnostics) = + resolve_schema(spec, prop, diagnostic_context); + diagnostics.append(&mut inner_diagnostics); + if inner.is_none() { + break; + } + let inner = inner.unwrap(); + + // There are characters that aren't allowed in jsonpath so we change the format + // if they're present. + let inner_jsonpath = if name.chars().any(|c| c == '@' || c == '$') { + format!("{}['{}']", jsonpath, name) + } else { + format!("{}.{}", jsonpath, name) + }; + let child_is_required = is_required && schema.required.contains(name); + let (mut child_asserts, mut child_diagnostics) = + generate_assert_from_schema( + spec, + inner, + inner_jsonpath.as_ref(), + child_is_required, + diagnostic_context, + ); + asserts.append(&mut child_asserts); + diagnostics.append(&mut child_diagnostics); + } + } + _ => { + diagnostics.push(HeaveError::UnsupportedSchemaKind { + context: diagnostic_context.clone(), + kind: "Any".to_string(), + jsonpath: jsonpath.to_string(), + }); + } } (asserts, diagnostics) } +/// Extract the primary (non-null) type from a SchemaTypeSet. +fn get_primary_type(type_set: &SchemaTypeSet) -> Option { + match type_set { + SchemaTypeSet::Single(t) => Some(*t), + SchemaTypeSet::Multiple(types) => { + types.iter().find(|t| **t != SchemaType::Null).copied() + } + } +} + +fn get_non_null_types(type_set: &SchemaTypeSet) -> Vec { + match type_set { + SchemaTypeSet::Single(t) => vec![*t], + SchemaTypeSet::Multiple(types) => { + types.iter().filter(|t| **t != SchemaType::Null).copied().collect() + } + } +} + +fn schema_type_to_hurl_predicate(t: SchemaType) -> Option<&'static str> { + match t { + SchemaType::Boolean => Some("isBoolean"), + SchemaType::String => Some("isString"), + SchemaType::Number => Some("isNumber"), + SchemaType::Integer => Some("isInteger"), + SchemaType::Array | SchemaType::Object => Some("isCollection"), + _ => None, + } +} + +/// Resolve a Schema enum (which may be Boolean or Object) into an ObjectSchema reference. +/// When the schema contains a `$ref`, resolves it against the spec's components. +fn resolve_schema_from_schema<'a>( + spec: &'a Spec, + schema: &'a oas3::spec::Schema, + diagnostic_context: &DiagnosticContext, +) -> (Option<&'a ObjectSchema>, Vec) { + match schema { + oas3::spec::Schema::Boolean(_) => (None, vec![]), + oas3::spec::Schema::Object(obj_or_ref) => { + resolve_schema(spec, obj_or_ref, diagnostic_context) + } + } +} + fn resolve_schema<'a>( - openapi: &'a openapiv3::OpenAPI, - schema: &'a openapiv3::ReferenceOr, + spec: &'a Spec, + schema: &'a ObjectOrReference, diagnostic_context: &DiagnosticContext, -) -> (Option<&'a openapiv3::Schema>, Vec) { +) -> (Option<&'a ObjectSchema>, Vec) { let mut diagnostics: Vec = vec![]; match schema { - ReferenceOr::Item(item) => (Some(item), diagnostics), - ReferenceOr::Reference { reference } => { - let schema_name = reference.split("#/components/schemas/").nth(1); + ObjectOrReference::Object(item) => (Some(item), diagnostics), + ObjectOrReference::Ref { ref_path, .. } => { + let schema_name = ref_path.split("#/components/schemas/").nth(1); if schema_name.is_none() { diagnostics.push(HeaveError::MalformedSchemaReference { context: diagnostic_context.clone(), - reference: reference.to_string(), + reference: ref_path.to_string(), }); return (None, diagnostics); } let schema_name = schema_name.unwrap(); - let components = &openapi.components; + let components = &spec.components; if components.is_none() { diagnostics.push(HeaveError::MissingComponents); return (None, diagnostics); @@ -1057,43 +1155,44 @@ fn resolve_schema<'a>( if found_schema.is_none() { diagnostics.push(HeaveError::MissingSchemaReference { context: diagnostic_context.clone(), - reference: reference.to_string(), + reference: ref_path.to_string(), }); return (None, diagnostics); } let found_schema = found_schema.unwrap(); - if found_schema.as_item().is_none() { - diagnostics.push(HeaveError::FailedSchemaDereference { - context: diagnostic_context.clone(), - reference: reference.to_string(), - }); - return (None, diagnostics); + match found_schema { + ObjectOrReference::Object(schema) => (Some(schema), diagnostics), + ObjectOrReference::Ref { .. } => { + diagnostics.push(HeaveError::FailedSchemaDereference { + context: diagnostic_context.clone(), + reference: ref_path.to_string(), + }); + (None, diagnostics) + } } - let schema = found_schema.as_item().unwrap(); - (Some(schema), diagnostics) } } } fn resolve_request_body<'a>( - openapi: &'a openapiv3::OpenAPI, - request_body: &'a openapiv3::ReferenceOr, + spec: &'a Spec, + request_body: &'a ObjectOrReference, diagnostic_context: &DiagnosticContext, -) -> (Option<&'a openapiv3::RequestBody>, Vec) { +) -> (Option<&'a RequestBody>, Vec) { let mut diagnostics: Vec = vec![]; match request_body { - ReferenceOr::Item(item) => (Some(item), diagnostics), - ReferenceOr::Reference { reference } => { - let request_body_name = reference.split("#/components/requestBodies/").nth(1); + ObjectOrReference::Object(item) => (Some(item), diagnostics), + ObjectOrReference::Ref { ref_path, .. } => { + let request_body_name = ref_path.split("#/components/requestBodies/").nth(1); if request_body_name.is_none() { diagnostics.push(HeaveError::MalformedRequestBodyReference { context: diagnostic_context.clone(), - reference: reference.to_string(), + reference: ref_path.to_string(), }); return (None, diagnostics); } let request_body_name = request_body_name.unwrap(); - let components = &openapi.components; + let components = &spec.components; if components.is_none() { diagnostics.push(HeaveError::MissingComponents); return (None, diagnostics); @@ -1106,33 +1205,34 @@ fn resolve_request_body<'a>( if found_request_body.is_none() { diagnostics.push(HeaveError::MissingRequestBodyReference { context: diagnostic_context.clone(), - reference: reference.to_string(), + reference: ref_path.to_string(), }); return (None, diagnostics); } let found_request_body = found_request_body.unwrap(); - if found_request_body.as_item().is_none() { - diagnostics.push(HeaveError::FailedRequestBodyDereference { - context: diagnostic_context.clone(), - reference: reference.to_string(), - }); - return (None, diagnostics); + match found_request_body { + ObjectOrReference::Object(rb) => (Some(rb), diagnostics), + ObjectOrReference::Ref { .. } => { + diagnostics.push(HeaveError::FailedRequestBodyDereference { + context: diagnostic_context.clone(), + reference: ref_path.to_string(), + }); + (None, diagnostics) + } } - let request_body = found_request_body.as_item().unwrap(); - (Some(request_body), diagnostics) } } } fn generate_request_body_from_schema( - openapi: &openapiv3::OpenAPI, - schema: &openapiv3::Schema, + spec: &Spec, + schema: &ObjectSchema, name: Option, diagnostic_context: &DiagnosticContext, jsonpath: &str, ) -> (Option, Vec) { // We don't need to include this in the request body if it's read only - if schema.schema_data.read_only { + if schema.read_only.unwrap_or(false) { return (None, vec![]); } // Cycle Detection @@ -1168,161 +1268,178 @@ fn generate_request_body_from_schema( } let mut diagnostics = vec![]; - match &schema.schema_kind { - openapiv3::SchemaKind::OneOf { .. } => { + + // Check composition keywords first + let composition_count = [ + !schema.one_of.is_empty(), + !schema.all_of.is_empty(), + !schema.any_of.is_empty(), + ] + .iter() + .filter(|&&x| x) + .count(); + if composition_count > 1 { + diagnostics.push(HeaveError::UnsupportedSchemaKind { + context: diagnostic_context.clone(), + kind: "Any".to_string(), + jsonpath: name.unwrap_or("".to_string()), + }); + return (None, diagnostics); + } + if !schema.one_of.is_empty() { + diagnostics.push(HeaveError::UnsupportedSchemaKind { + context: diagnostic_context.clone(), + kind: "OneOf".to_string(), + jsonpath: name.unwrap_or("".to_string()), + }); + return (None, diagnostics); + } + if !schema.all_of.is_empty() { + let mut child_request_bodies = vec![]; + let mut flattened_object_fields = serde_json::Value::Object(serde_json::Map::new()); + for all_of_schema_or_ref in &schema.all_of { + let (all_of_schema, mut inner_diagnostics) = + resolve_schema(spec, all_of_schema_or_ref, diagnostic_context); + diagnostics.append(&mut inner_diagnostics); + if let Some(s) = all_of_schema { + let (request_body, mut inner_diagnostics) = generate_request_body_from_schema( + spec, + s, + None, + diagnostic_context, + jsonpath, + ); + diagnostics.append(&mut inner_diagnostics); + + if let Some(body) = &request_body { + // In the case of `allOf`, objects need special handling. We create an + // empty JSON value and then flatten all the fields on to that single + // value. Any primitive fields can just be added directly to + // `child_request_bodies`. + let mut j = serde_json::from_str::(body).unwrap(); + if j.is_object() { + let inner_map = flattened_object_fields.as_object_mut().unwrap(); + inner_map.append(j.as_object_mut().unwrap()); + } else { + child_request_bodies.push(request_body); + } + } + } + } + // Only include `flattened_object_fields` if we actually added anything to it. + if !flattened_object_fields.as_object().unwrap().is_empty() { + child_request_bodies.push(Some(flattened_object_fields.to_string())); + } + + // If child_request_bodies is empty we need to communicate that we couldn't build + // anything. + if child_request_bodies.is_empty() { + return (None, diagnostics); + } + + let stringified_body = child_request_bodies + .into_iter() + .flatten() + .collect::>() + .join(",\n"); + + return match name { + Some(name) => ( + Some(format!("\"{}\": {}", name, stringified_body)), + diagnostics, + ), + None => (Some(stringified_body), diagnostics), + }; + } + if !schema.any_of.is_empty() { + diagnostics.push(HeaveError::UnsupportedSchemaKind { + context: diagnostic_context.clone(), + kind: "AnyOf".to_string(), + jsonpath: name.unwrap_or("".to_string()), + }); + return (None, diagnostics); + } + + // A small helper that takes properties that may or may not have names and formats them + // accordingly. + let single_property_formatter = |name: Option, default: &str| -> String { + match name { + Some(name) => format!("\"{}\": {}", name, default), + None => default.to_string(), + } + }; + + // Determine the primary type from schema_type + let primary_type = match &schema.schema_type { + Some(type_set) => get_primary_type(type_set), + None => { diagnostics.push(HeaveError::UnsupportedSchemaKind { context: diagnostic_context.clone(), - kind: "OneOf".to_string(), + kind: "Any".to_string(), jsonpath: name.unwrap_or("".to_string()), - }) + }); + return (None, diagnostics); + } + }; + + match primary_type { + Some(SchemaType::Boolean) => { + return (Some(single_property_formatter(name, "false")), diagnostics); } - openapiv3::SchemaKind::AllOf { all_of } => { - let mut child_request_bodies = vec![]; - let mut flattened_object_fields = serde_json::Value::Object(serde_json::Map::new()); - for all_of_schema_or_ref in all_of { - let (all_of_schema, mut inner_diagnostics) = - resolve_schema(openapi, all_of_schema_or_ref, diagnostic_context); + Some(SchemaType::String) => { + return (Some(single_property_formatter(name, "\"\"")), diagnostics); + } + Some(SchemaType::Number) | Some(SchemaType::Integer) => { + return (Some(single_property_formatter(name, "0")), diagnostics); + } + Some(SchemaType::Object) => { + let properties = &schema.properties; + let mut child_request_bodies: Vec> = vec![]; + for (prop_name, prop) in properties.iter() { + let (inner, mut inner_diagnostics) = + resolve_schema(spec, prop, diagnostic_context); diagnostics.append(&mut inner_diagnostics); - if let Some(s) = all_of_schema { - let (request_body, mut inner_diagnostics) = generate_request_body_from_schema( - openapi, - s, - None, + if inner.is_none() { + return (None, diagnostics); + } + let inner = inner.unwrap(); + let (request_body, mut inner_diagnostics) = + generate_request_body_from_schema( + spec, + inner, + Some(prop_name.to_string()), diagnostic_context, - jsonpath, + format!("{}.{}", jsonpath, prop_name).as_ref(), ); - diagnostics.append(&mut inner_diagnostics); - - if let Some(body) = &request_body { - // In the case of `allOf`, objects need special handling. We create an - // empty JSON value and then flatten all the fields on to that single - // value. Any primitive fields can just be added directly to - // `child_request_bodies`. - let mut j = serde_json::from_str::(body).unwrap(); - if j.is_object() { - let inner_map = flattened_object_fields.as_object_mut().unwrap(); - inner_map.append(j.as_object_mut().unwrap()); - } else { - child_request_bodies.push(request_body); - } - } - } - } - // Only include `flattened_object_fields` if we actually added anything to it. - if !flattened_object_fields.as_object().unwrap().is_empty() { - child_request_bodies.push(Some(flattened_object_fields.to_string())); - } - - // If child_request_bodies is empty we need to communicate that we couldn't build - // anything. - if child_request_bodies.is_empty() { - return (None, diagnostics); + child_request_bodies.push(request_body); + diagnostics.append(&mut inner_diagnostics); } - let stringified_body = child_request_bodies .into_iter() .flatten() .collect::>() .join(",\n"); - return match name { Some(name) => ( - Some(format!("\"{}\": {}", name, stringified_body)), + Some(format!("\"{}\": {{{}}}", name, stringified_body)), diagnostics, ), - None => (Some(stringified_body), diagnostics), + None => (Some(format!("{{\n{}\n}}", stringified_body,)), diagnostics), }; } - openapiv3::SchemaKind::AnyOf { .. } => { - diagnostics.push(HeaveError::UnsupportedSchemaKind { - context: diagnostic_context.clone(), - kind: "AnyOf".to_string(), - jsonpath: name.unwrap_or("".to_string()), - }) - } - openapiv3::SchemaKind::Not { .. } => diagnostics.push(HeaveError::UnsupportedSchemaKind { - context: diagnostic_context.clone(), - kind: "Not".to_string(), - jsonpath: name.unwrap_or("".to_string()), - }), - openapiv3::SchemaKind::Any(_) => diagnostics.push(HeaveError::UnsupportedSchemaKind { - context: diagnostic_context.clone(), - kind: "Any".to_string(), - jsonpath: name.unwrap_or("".to_string()), - }), - openapiv3::SchemaKind::Type(schema_type) => { - // A small helper that takes properties that may or may not have names and formats them - // accordingly. If they have a name, start by indenting them, print the named property, - // then give it a default value. If there is no name, just print the default value. - let single_property_formatter = |name: Option, default: &str| -> String { - match name { - Some(name) => format!("\"{}\": {}", name, default), - None => default.to_string(), - } - }; - return match schema_type { - openapiv3::Type::Boolean(_) => { - (Some(single_property_formatter(name, "false")), diagnostics) - } - openapiv3::Type::String(_) => { - (Some(single_property_formatter(name, "\"\"")), diagnostics) - } - openapiv3::Type::Number(_) | openapiv3::Type::Integer(_) => { - (Some(single_property_formatter(name, "0")), diagnostics) - } - openapiv3::Type::Object(ob) => { - let properties = &ob.properties; - let mut child_request_bodies: Vec> = vec![]; - for (name, prop) in properties.iter() { - let unboxed = prop.clone().unbox(); - let (inner, mut inner_diagnostics) = - resolve_schema(openapi, &unboxed, diagnostic_context); - diagnostics.append(&mut inner_diagnostics); - if inner.is_none() { - return (None, diagnostics); - } - let inner = inner.unwrap(); - let (request_body, mut inner_diagnostics) = - generate_request_body_from_schema( - openapi, - inner, - Some(name.to_string()), - diagnostic_context, - format!("{}.{}", jsonpath, name).as_ref(), - ); - child_request_bodies.push(request_body); - diagnostics.append(&mut inner_diagnostics); - } - let stringified_body = child_request_bodies - .into_iter() - .flatten() - .collect::>() - .join(",\n"); - return match name { - Some(name) => ( - Some(format!("\"{}\": {{{}}}", name, stringified_body)), - diagnostics, - ), - None => (Some(format!("{{\n{}\n}}", stringified_body,)), diagnostics), - }; - } - openapiv3::Type::Array(array) => { - let items = &array.items; - if items.is_none() { - return (None, diagnostics); - } - let items = items.as_ref().unwrap(); - let unboxed = items.clone().unbox(); - let (inner, mut inner_diagnostics) = - resolve_schema(openapi, &unboxed, diagnostic_context); + Some(SchemaType::Array) => { + let items = &schema.items; + if items.is_none() { + return (None, diagnostics); + } + let items = items.as_ref().unwrap(); + let inner = resolve_schema_from_schema(spec, items, diagnostic_context); + match inner { + (Some(inner), mut inner_diagnostics) => { diagnostics.append(&mut inner_diagnostics); - if inner.is_none() { - return (None, diagnostics); - } - let inner = inner.unwrap(); let (child_request_body, mut child_diagnostics) = generate_request_body_from_schema( - openapi, + spec, inner, None, diagnostic_context, @@ -1341,31 +1458,42 @@ fn generate_request_body_from_schema( None => (Some(format!("[{}]", child_request_body)), diagnostics), } } - }; + (None, mut inner_diagnostics) => { + diagnostics.append(&mut inner_diagnostics); + (None, diagnostics) + } + } + } + _ => { + diagnostics.push(HeaveError::UnsupportedSchemaKind { + context: diagnostic_context.clone(), + kind: "Any".to_string(), + jsonpath: name.unwrap_or("".to_string()), + }); + (None, diagnostics) } } - (None, diagnostics) } fn resolve_response<'a>( - openapi: &'a openapiv3::OpenAPI, - response: &'a openapiv3::ReferenceOr, + spec: &'a Spec, + response: &'a ObjectOrReference, diagnostic_context: &DiagnosticContext, -) -> (Option<&'a openapiv3::Response>, Vec) { +) -> (Option<&'a Response>, Vec) { let mut diagnostics = vec![]; match response { - ReferenceOr::Item(item) => (Some(item), diagnostics), - ReferenceOr::Reference { reference } => { - let response_name = reference.split("#/components/responses/").nth(1); + ObjectOrReference::Object(item) => (Some(item), diagnostics), + ObjectOrReference::Ref { ref_path, .. } => { + let response_name = ref_path.split("#/components/responses/").nth(1); if response_name.is_none() { diagnostics.push(HeaveError::MalformedResponseBodyReference { context: diagnostic_context.clone(), - reference: reference.to_string(), + reference: ref_path.to_string(), }); return (None, diagnostics); } let response_name = response_name.unwrap(); - let components = &openapi.components; + let components = &spec.components; if components.is_none() { diagnostics.push(HeaveError::MissingComponents); return (None, diagnostics); @@ -1374,20 +1502,21 @@ fn resolve_response<'a>( if found_response.is_none() { diagnostics.push(HeaveError::MissingResponseBodyReference { context: diagnostic_context.clone(), - reference: reference.to_string(), + reference: ref_path.to_string(), }); return (None, diagnostics); } let found_response = found_response.unwrap(); - if found_response.as_item().is_none() { - diagnostics.push(HeaveError::FailedResponseBodyDereference { - context: diagnostic_context.clone(), - reference: reference.to_string(), - }); - return (None, diagnostics); + match found_response { + ObjectOrReference::Object(resp) => (Some(resp), diagnostics), + ObjectOrReference::Ref { .. } => { + diagnostics.push(HeaveError::FailedResponseBodyDereference { + context: diagnostic_context.clone(), + reference: ref_path.to_string(), + }); + (None, diagnostics) + } } - let schema = found_response.as_item().unwrap(); - (Some(schema), diagnostics) } } } @@ -1397,14 +1526,14 @@ mod tests { use std::{error::Error, path::PathBuf, str::FromStr}; use insta::{assert_debug_snapshot, assert_snapshot, glob}; - use openapiv3::OpenAPI; + use oas3::spec::Spec; use crate::{generate, write_outputs, Output, DEFAULT_HURL_TEMPLATE}; - // Creates an OpenAPI from a file path - macro_rules! openapi_from_yaml { + // Creates a Spec from a file path + macro_rules! spec_from_yaml { ($fname:expr) => { - serde_yaml::from_str(&std::fs::read_to_string($fname).unwrap()).unwrap() + oas3::from_yaml(&std::fs::read_to_string($fname).unwrap()).unwrap() }; } @@ -1412,9 +1541,9 @@ mod tests { fn petstore() -> Result<(), Box> { // Testing json and yaml in this same test so I make sure the output snapshots are the same let content = std::fs::read_to_string("src/snapshots/petstore/petstore.yaml")?; - let openapi: OpenAPI = serde_yaml::from_str(&content).expect("Could not deserialize input"); + let spec: Spec = oas3::from_yaml(&content).expect("Could not deserialize input"); let output_directory = PathBuf::from_str("src/snapshots/petstore")?; - let result = generate(openapi); + let result = generate(spec); write_outputs(&result.outputs, DEFAULT_HURL_TEMPLATE, &output_directory)?; let mut settings = insta::Settings::clone_current(); settings.set_omit_expression(true); @@ -1428,9 +1557,9 @@ mod tests { }); let content = std::fs::read_to_string("src/snapshots/petstore/petstore.json")?; - let openapi: OpenAPI = serde_json::from_str(&content).expect("Could not deserialize input"); + let spec: Spec = oas3::from_json(&content).expect("Could not deserialize input"); let output_directory = PathBuf::from_str("src/snapshots/petstore")?; - let result = generate(openapi); + let result = generate(spec); write_outputs(&result.outputs, DEFAULT_HURL_TEMPLATE, &output_directory)?; let mut settings = insta::Settings::clone_current(); settings.set_omit_expression(true); @@ -1452,7 +1581,7 @@ mod tests { settings.set_omit_expression(true); settings.bind(|| { glob!("snapshots/diagnostics/*.yaml", |path| { - let input: OpenAPI = openapi_from_yaml!(&path); + let input: Spec = spec_from_yaml!(&path); let result = generate(input); assert_debug_snapshot!(result); }); @@ -1466,7 +1595,7 @@ mod tests { settings.set_omit_expression(true); settings.bind(|| { glob!("snapshots/cycle_detection/*.yaml", |path| { - let input: OpenAPI = openapi_from_yaml!(&path); + let input: Spec = spec_from_yaml!(&path); let result = generate(input); assert_debug_snapshot!(result); }); @@ -1480,7 +1609,7 @@ mod tests { settings.set_omit_expression(true); settings.bind(|| { glob!("snapshots/read_only/*.yaml", |path| { - let input: OpenAPI = openapi_from_yaml!(&path); + let input: Spec = spec_from_yaml!(&path); let result = generate(input); assert_debug_snapshot!(result); }); @@ -1494,7 +1623,7 @@ mod tests { settings.set_omit_expression(true); settings.bind(|| { glob!("snapshots/write_only/*.yaml", |path| { - let input: OpenAPI = openapi_from_yaml!(&path); + let input: Spec = spec_from_yaml!(&path); let result = generate(input); assert_debug_snapshot!(result); }); @@ -1504,9 +1633,9 @@ mod tests { #[test] fn allof_inputs() -> Result<(), Box> { - let openapi: OpenAPI = openapi_from_yaml!("src/snapshots/allof/petstore.yaml"); + let spec: Spec = spec_from_yaml!("src/snapshots/allof/petstore.yaml"); let output_directory = PathBuf::from_str("src/snapshots/allof")?; - let result = generate(openapi); + let result = generate(spec); write_outputs(&result.outputs, DEFAULT_HURL_TEMPLATE, &output_directory)?; let mut settings = insta::Settings::clone_current(); settings.set_omit_expression(true); @@ -1519,6 +1648,37 @@ mod tests { Ok(()) } + #[test] + fn oas31_petstore() -> Result<(), Box> { + let spec: Spec = spec_from_yaml!("src/snapshots/oas31/petstore.yaml"); + let output_directory = PathBuf::from_str("src/snapshots/oas31")?; + let result = generate(spec); + write_outputs(&result.outputs, DEFAULT_HURL_TEMPLATE, &output_directory)?; + let mut settings = insta::Settings::clone_current(); + settings.set_omit_expression(true); + settings.bind(|| { + glob!("snapshots/oas31/*.hurl", |path| { + let input = std::fs::read_to_string(path).unwrap(); + assert_snapshot!(input); + }); + }); + Ok(()) + } + + #[test] + fn oas31_diagnostic_inputs() -> Result<(), Box> { + let mut settings = insta::Settings::clone_current(); + settings.set_omit_expression(true); + settings.bind(|| { + glob!("snapshots/oas31_diagnostics/*.yaml", |path| { + let input: Spec = spec_from_yaml!(&path); + let result = generate(input); + assert_debug_snapshot!(result); + }); + }); + Ok(()) + } + #[test] fn filter_only_new_outputs() { let existing_files = vec![ diff --git a/src/snapshots/allof/addPet_200.hurl b/src/snapshots/allof/addPet_200.hurl index cddfbaf..7e646eb 100644 --- a/src/snapshots/allof/addPet_200.hurl +++ b/src/snapshots/allof/addPet_200.hurl @@ -23,15 +23,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -jsonpath "$.name" isString #jsonpath "$.category" isCollection #jsonpath "$.category.id" isInteger #jsonpath "$.category.name" isString +#jsonpath "$.id" isInteger +jsonpath "$.name" isString jsonpath "$.photoUrls" isCollection #jsonpath "$.photoUrls[0]" isString +#jsonpath "$.status" isString #jsonpath "$.tags" isCollection #jsonpath "$.tags[0]" isCollection #jsonpath "$.tags[0].id" isInteger #jsonpath "$.tags[0].name" isString -#jsonpath "$.status" isString diff --git a/src/snapshots/allof/getPetById_200.hurl b/src/snapshots/allof/getPetById_200.hurl index 86c96b2..aec5235 100644 --- a/src/snapshots/allof/getPetById_200.hurl +++ b/src/snapshots/allof/getPetById_200.hurl @@ -6,15 +6,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -jsonpath "$.name" isString #jsonpath "$.category" isCollection #jsonpath "$.category.id" isInteger #jsonpath "$.category.name" isString +#jsonpath "$.id" isInteger +jsonpath "$.name" isString jsonpath "$.photoUrls" isCollection #jsonpath "$.photoUrls[0]" isString +#jsonpath "$.status" isString #jsonpath "$.tags" isCollection #jsonpath "$.tags[0]" isCollection #jsonpath "$.tags[0].id" isInteger #jsonpath "$.tags[0].name" isString -#jsonpath "$.status" isString diff --git a/src/snapshots/allof/updatePet_200.hurl b/src/snapshots/allof/updatePet_200.hurl index bd87c92..bf80168 100644 --- a/src/snapshots/allof/updatePet_200.hurl +++ b/src/snapshots/allof/updatePet_200.hurl @@ -23,15 +23,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -jsonpath "$.name" isString #jsonpath "$.category" isCollection #jsonpath "$.category.id" isInteger #jsonpath "$.category.name" isString +#jsonpath "$.id" isInteger +jsonpath "$.name" isString jsonpath "$.photoUrls" isCollection #jsonpath "$.photoUrls[0]" isString +#jsonpath "$.status" isString #jsonpath "$.tags" isCollection #jsonpath "$.tags[0]" isCollection #jsonpath "$.tags[0].id" isInteger #jsonpath "$.tags[0].name" isString -#jsonpath "$.status" isString diff --git a/src/snapshots/heave__tests__allof_inputs@addPet_200.hurl.snap b/src/snapshots/heave__tests__allof_inputs@addPet_200.hurl.snap index a907b09..39e20cd 100644 --- a/src/snapshots/heave__tests__allof_inputs@addPet_200.hurl.snap +++ b/src/snapshots/heave__tests__allof_inputs@addPet_200.hurl.snap @@ -27,16 +27,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -jsonpath "$.name" isString #jsonpath "$.category" isCollection #jsonpath "$.category.id" isInteger #jsonpath "$.category.name" isString +#jsonpath "$.id" isInteger +jsonpath "$.name" isString jsonpath "$.photoUrls" isCollection #jsonpath "$.photoUrls[0]" isString +#jsonpath "$.status" isString #jsonpath "$.tags" isCollection #jsonpath "$.tags[0]" isCollection #jsonpath "$.tags[0].id" isInteger #jsonpath "$.tags[0].name" isString -#jsonpath "$.status" isString - diff --git a/src/snapshots/heave__tests__allof_inputs@getPetById_200.hurl.snap b/src/snapshots/heave__tests__allof_inputs@getPetById_200.hurl.snap index bcf53e6..7e98946 100644 --- a/src/snapshots/heave__tests__allof_inputs@getPetById_200.hurl.snap +++ b/src/snapshots/heave__tests__allof_inputs@getPetById_200.hurl.snap @@ -10,16 +10,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -jsonpath "$.name" isString #jsonpath "$.category" isCollection #jsonpath "$.category.id" isInteger #jsonpath "$.category.name" isString +#jsonpath "$.id" isInteger +jsonpath "$.name" isString jsonpath "$.photoUrls" isCollection #jsonpath "$.photoUrls[0]" isString +#jsonpath "$.status" isString #jsonpath "$.tags" isCollection #jsonpath "$.tags[0]" isCollection #jsonpath "$.tags[0].id" isInteger #jsonpath "$.tags[0].name" isString -#jsonpath "$.status" isString - diff --git a/src/snapshots/heave__tests__allof_inputs@updatePet_200.hurl.snap b/src/snapshots/heave__tests__allof_inputs@updatePet_200.hurl.snap index d68f77c..bc928cf 100644 --- a/src/snapshots/heave__tests__allof_inputs@updatePet_200.hurl.snap +++ b/src/snapshots/heave__tests__allof_inputs@updatePet_200.hurl.snap @@ -27,16 +27,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -jsonpath "$.name" isString #jsonpath "$.category" isCollection #jsonpath "$.category.id" isInteger #jsonpath "$.category.name" isString +#jsonpath "$.id" isInteger +jsonpath "$.name" isString jsonpath "$.photoUrls" isCollection #jsonpath "$.photoUrls[0]" isString +#jsonpath "$.status" isString #jsonpath "$.tags" isCollection #jsonpath "$.tags[0]" isCollection #jsonpath "$.tags[0].id" isInteger #jsonpath "$.tags[0].name" isString -#jsonpath "$.status" isString - diff --git a/src/snapshots/heave__tests__cycle_detection@ResponseBodySeparatedCycle.yaml.snap b/src/snapshots/heave__tests__cycle_detection@ResponseBodySeparatedCycle.yaml.snap index 59fadf8..5e400f7 100644 --- a/src/snapshots/heave__tests__cycle_detection@ResponseBodySeparatedCycle.yaml.snap +++ b/src/snapshots/heave__tests__cycle_detection@ResponseBodySeparatedCycle.yaml.snap @@ -17,11 +17,11 @@ GenerateResult { query_parameters: [], asserts: [ "jsonpath \"$\" isCollection", - "#jsonpath \"$.name\" isString", "#jsonpath \"$.B\" isCollection", - "#jsonpath \"$.B.name\" isString", "#jsonpath \"$.B.A\" isCollection", "#jsonpath \"$.B.A.name\" isString", + "#jsonpath \"$.B.name\" isString", + "#jsonpath \"$.name\" isString", ], request_body_parameter: "{\n \"name\": \"\"\n}", }, diff --git a/src/snapshots/heave__tests__diagnostic_inputs@UnsupportedSchemaKind_Not_RequestBody.yaml.snap b/src/snapshots/heave__tests__diagnostic_inputs@UnsupportedSchemaKind_Not_RequestBody.yaml.snap index dd102f6..366d589 100644 --- a/src/snapshots/heave__tests__diagnostic_inputs@UnsupportedSchemaKind_Not_RequestBody.yaml.snap +++ b/src/snapshots/heave__tests__diagnostic_inputs@UnsupportedSchemaKind_Not_RequestBody.yaml.snap @@ -27,7 +27,7 @@ GenerateResult { operation: "addPet", path: "/pets", }, - kind: "Not", + kind: "Any", jsonpath: "", }, ], diff --git a/src/snapshots/heave__tests__diagnostic_inputs@UnsupportedSchemaKind_Not_ResponseBody.yaml.snap b/src/snapshots/heave__tests__diagnostic_inputs@UnsupportedSchemaKind_Not_ResponseBody.yaml.snap index 3670883..eaeb1bb 100644 --- a/src/snapshots/heave__tests__diagnostic_inputs@UnsupportedSchemaKind_Not_ResponseBody.yaml.snap +++ b/src/snapshots/heave__tests__diagnostic_inputs@UnsupportedSchemaKind_Not_ResponseBody.yaml.snap @@ -25,7 +25,7 @@ GenerateResult { operation: "addPet", path: "/pets", }, - kind: "Not", + kind: "Any", jsonpath: "$", }, ], diff --git a/src/snapshots/heave__tests__oas31_diagnostic_inputs@MultipleTypes_RequestBody.yaml.snap b/src/snapshots/heave__tests__oas31_diagnostic_inputs@MultipleTypes_RequestBody.yaml.snap new file mode 100644 index 0000000..b05397b --- /dev/null +++ b/src/snapshots/heave__tests__oas31_diagnostic_inputs@MultipleTypes_RequestBody.yaml.snap @@ -0,0 +1,25 @@ +--- +source: src/main.rs +input_file: src/snapshots/oas31_diagnostics/MultipleTypes_RequestBody.yaml +--- +GenerateResult { + outputs: [ + Output { + expected_status_code: 201, + name: "createItem_201.hurl", + hurl_path: "/items", + oas_path: "/items", + oas_operation_id: Some( + "createItem", + ), + method: "POST", + header_parameters: [], + query_parameters: [], + asserts: [ + "jsonpath \"$\" isCollection", + ], + request_body_parameter: "{\n \"label\": \"\",\n \"value\": 0\n}", + }, + ], + diagnostics: [], +} diff --git a/src/snapshots/heave__tests__oas31_diagnostic_inputs@MultipleTypes_ResponseBody.yaml.snap b/src/snapshots/heave__tests__oas31_diagnostic_inputs@MultipleTypes_ResponseBody.yaml.snap new file mode 100644 index 0000000..c0b3fe3 --- /dev/null +++ b/src/snapshots/heave__tests__oas31_diagnostic_inputs@MultipleTypes_ResponseBody.yaml.snap @@ -0,0 +1,29 @@ +--- +source: src/main.rs +input_file: src/snapshots/oas31_diagnostics/MultipleTypes_ResponseBody.yaml +--- +GenerateResult { + outputs: [ + Output { + expected_status_code: 200, + name: "getItem_200.hurl", + hurl_path: "/items", + oas_path: "/items", + oas_operation_id: Some( + "getItem", + ), + method: "GET", + header_parameters: [], + query_parameters: [], + asserts: [ + "jsonpath \"$\" isCollection", + "jsonpath \"$.id\" isInteger", + "#jsonpath \"$.label\" isString", + "#jsonpath \"$.value\" isInteger", + "#jsonpath \"$.value\" isString", + ], + request_body_parameter: "", + }, + ], + diagnostics: [], +} diff --git a/src/snapshots/heave__tests__oas31_petstore@createPet_201.hurl.snap b/src/snapshots/heave__tests__oas31_petstore@createPet_201.hurl.snap new file mode 100644 index 0000000..5531c5a --- /dev/null +++ b/src/snapshots/heave__tests__oas31_petstore@createPet_201.hurl.snap @@ -0,0 +1,28 @@ +--- +source: src/main.rs +input_file: src/snapshots/oas31/createPet_201.hurl +--- +POST {{ baseurl }}/pets +Authorization: Bearer {{ authorization }} +Prefer: code=201 +{ + "age": 0, + "code": 0, + "name": "", + "tag": "" +} +HTTP 201 + +[Asserts] +jsonpath "$" isCollection +#jsonpath "$.age" isInteger +#jsonpath "$.aliases" isCollection +#jsonpath "$.aliases[0]" isString +#jsonpath "$.code" isInteger +#jsonpath "$.code" isString +jsonpath "$.id" isInteger +#jsonpath "$.metadata" isCollection +#jsonpath "$.metadata.source" isString +jsonpath "$.name" isString +#jsonpath "$.tag" isString +#jsonpath "$.weight" isNumber diff --git a/src/snapshots/heave__tests__oas31_petstore@getPet_200.hurl.snap b/src/snapshots/heave__tests__oas31_petstore@getPet_200.hurl.snap new file mode 100644 index 0000000..12519e3 --- /dev/null +++ b/src/snapshots/heave__tests__oas31_petstore@getPet_200.hurl.snap @@ -0,0 +1,23 @@ +--- +source: src/main.rs +input_file: src/snapshots/oas31/getPet_200.hurl +--- +GET {{ baseurl }}/pets/{{petId}} +Authorization: Bearer {{ authorization }} +Prefer: code=200 + +HTTP 200 + +[Asserts] +jsonpath "$" isCollection +#jsonpath "$.age" isInteger +#jsonpath "$.aliases" isCollection +#jsonpath "$.aliases[0]" isString +#jsonpath "$.code" isInteger +#jsonpath "$.code" isString +jsonpath "$.id" isInteger +#jsonpath "$.metadata" isCollection +#jsonpath "$.metadata.source" isString +jsonpath "$.name" isString +#jsonpath "$.tag" isString +#jsonpath "$.weight" isNumber diff --git a/src/snapshots/heave__tests__oas31_petstore@listPets_200.hurl.snap b/src/snapshots/heave__tests__oas31_petstore@listPets_200.hurl.snap new file mode 100644 index 0000000..95d7d79 --- /dev/null +++ b/src/snapshots/heave__tests__oas31_petstore@listPets_200.hurl.snap @@ -0,0 +1,24 @@ +--- +source: src/main.rs +input_file: src/snapshots/oas31/listPets_200.hurl +--- +GET {{ baseurl }}/pets +Authorization: Bearer {{ authorization }} +Prefer: code=200 + +HTTP 200 + +[Asserts] +jsonpath "$" isCollection +#jsonpath "$[0]" isCollection +#jsonpath "$[0].age" isInteger +#jsonpath "$[0].aliases" isCollection +#jsonpath "$[0].aliases[0]" isString +#jsonpath "$[0].code" isInteger +#jsonpath "$[0].code" isString +#jsonpath "$[0].id" isInteger +#jsonpath "$[0].metadata" isCollection +#jsonpath "$[0].metadata.source" isString +#jsonpath "$[0].name" isString +#jsonpath "$[0].tag" isString +#jsonpath "$[0].weight" isNumber diff --git a/src/snapshots/heave__tests__petstore@addPet_200.hurl.snap b/src/snapshots/heave__tests__petstore@addPet_200.hurl.snap index dcce89b..9e8b660 100644 --- a/src/snapshots/heave__tests__petstore@addPet_200.hurl.snap +++ b/src/snapshots/heave__tests__petstore@addPet_200.hurl.snap @@ -27,17 +27,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -jsonpath "$.name" isString #jsonpath "$.category" isCollection #jsonpath "$.category.id" isInteger #jsonpath "$.category.name" isString +#jsonpath "$.id" isInteger +jsonpath "$.name" isString jsonpath "$.photoUrls" isCollection #jsonpath "$.photoUrls[0]" isString +#jsonpath "$.status" isString #jsonpath "$.tags" isCollection #jsonpath "$.tags[0]" isCollection #jsonpath "$.tags[0].id" isInteger #jsonpath "$.tags[0].name" isString -#jsonpath "$.status" isString - - diff --git a/src/snapshots/heave__tests__petstore@createUsersWithListInput_200.hurl.snap b/src/snapshots/heave__tests__petstore@createUsersWithListInput_200.hurl.snap index 03cb2dd..871495e 100644 --- a/src/snapshots/heave__tests__petstore@createUsersWithListInput_200.hurl.snap +++ b/src/snapshots/heave__tests__petstore@createUsersWithListInput_200.hurl.snap @@ -21,12 +21,11 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -#jsonpath "$.username" isString +#jsonpath "$.email" isString #jsonpath "$.firstName" isString +#jsonpath "$.id" isInteger #jsonpath "$.lastName" isString -#jsonpath "$.email" isString #jsonpath "$.password" isString #jsonpath "$.phone" isString #jsonpath "$.userStatus" isInteger - +#jsonpath "$.username" isString diff --git a/src/snapshots/heave__tests__petstore@findPetsByStatus_200.hurl.snap b/src/snapshots/heave__tests__petstore@findPetsByStatus_200.hurl.snap index 4517537..3e86c1d 100644 --- a/src/snapshots/heave__tests__petstore@findPetsByStatus_200.hurl.snap +++ b/src/snapshots/heave__tests__petstore@findPetsByStatus_200.hurl.snap @@ -15,16 +15,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection #jsonpath "$[0]" isCollection -#jsonpath "$[0].id" isInteger -#jsonpath "$[0].name" isString #jsonpath "$[0].category" isCollection #jsonpath "$[0].category.id" isInteger #jsonpath "$[0].category.name" isString +#jsonpath "$[0].id" isInteger +#jsonpath "$[0].name" isString #jsonpath "$[0].photoUrls" isCollection #jsonpath "$[0].photoUrls[0]" isString +#jsonpath "$[0].status" isString #jsonpath "$[0].tags" isCollection #jsonpath "$[0].tags[0]" isCollection #jsonpath "$[0].tags[0].id" isInteger #jsonpath "$[0].tags[0].name" isString -#jsonpath "$[0].status" isString - diff --git a/src/snapshots/heave__tests__petstore@findPetsByTags_200.hurl.snap b/src/snapshots/heave__tests__petstore@findPetsByTags_200.hurl.snap index a5bbf84..bfd6331 100644 --- a/src/snapshots/heave__tests__petstore@findPetsByTags_200.hurl.snap +++ b/src/snapshots/heave__tests__petstore@findPetsByTags_200.hurl.snap @@ -15,16 +15,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection #jsonpath "$[0]" isCollection -#jsonpath "$[0].id" isInteger -#jsonpath "$[0].name" isString #jsonpath "$[0].category" isCollection #jsonpath "$[0].category.id" isInteger #jsonpath "$[0].category.name" isString +#jsonpath "$[0].id" isInteger +#jsonpath "$[0].name" isString #jsonpath "$[0].photoUrls" isCollection #jsonpath "$[0].photoUrls[0]" isString +#jsonpath "$[0].status" isString #jsonpath "$[0].tags" isCollection #jsonpath "$[0].tags[0]" isCollection #jsonpath "$[0].tags[0].id" isInteger #jsonpath "$[0].tags[0].name" isString -#jsonpath "$[0].status" isString - diff --git a/src/snapshots/heave__tests__petstore@getOrderById_200.hurl.snap b/src/snapshots/heave__tests__petstore@getOrderById_200.hurl.snap index 050629c..e3258ac 100644 --- a/src/snapshots/heave__tests__petstore@getOrderById_200.hurl.snap +++ b/src/snapshots/heave__tests__petstore@getOrderById_200.hurl.snap @@ -10,10 +10,9 @@ HTTP 200 [Asserts] jsonpath "$" isCollection +#jsonpath "$.complete" isBoolean #jsonpath "$.id" isInteger #jsonpath "$.petId" isInteger #jsonpath "$.quantity" isInteger #jsonpath "$.shipDate" isString #jsonpath "$.status" isString -#jsonpath "$.complete" isBoolean - diff --git a/src/snapshots/heave__tests__petstore@getPetById_200.hurl.snap b/src/snapshots/heave__tests__petstore@getPetById_200.hurl.snap index eb0168d..acd37da 100644 --- a/src/snapshots/heave__tests__petstore@getPetById_200.hurl.snap +++ b/src/snapshots/heave__tests__petstore@getPetById_200.hurl.snap @@ -10,17 +10,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -jsonpath "$.name" isString #jsonpath "$.category" isCollection #jsonpath "$.category.id" isInteger #jsonpath "$.category.name" isString +#jsonpath "$.id" isInteger +jsonpath "$.name" isString jsonpath "$.photoUrls" isCollection #jsonpath "$.photoUrls[0]" isString +#jsonpath "$.status" isString #jsonpath "$.tags" isCollection #jsonpath "$.tags[0]" isCollection #jsonpath "$.tags[0].id" isInteger #jsonpath "$.tags[0].name" isString -#jsonpath "$.status" isString - - diff --git a/src/snapshots/heave__tests__petstore@getUserByName_200.hurl.snap b/src/snapshots/heave__tests__petstore@getUserByName_200.hurl.snap index 5b488b5..3c7a33a 100644 --- a/src/snapshots/heave__tests__petstore@getUserByName_200.hurl.snap +++ b/src/snapshots/heave__tests__petstore@getUserByName_200.hurl.snap @@ -10,12 +10,11 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -#jsonpath "$.username" isString +#jsonpath "$.email" isString #jsonpath "$.firstName" isString +#jsonpath "$.id" isInteger #jsonpath "$.lastName" isString -#jsonpath "$.email" isString #jsonpath "$.password" isString #jsonpath "$.phone" isString #jsonpath "$.userStatus" isInteger - +#jsonpath "$.username" isString diff --git a/src/snapshots/heave__tests__petstore@placeOrder_200.hurl.snap b/src/snapshots/heave__tests__petstore@placeOrder_200.hurl.snap index cb6092e..5bd566a 100644 --- a/src/snapshots/heave__tests__petstore@placeOrder_200.hurl.snap +++ b/src/snapshots/heave__tests__petstore@placeOrder_200.hurl.snap @@ -17,10 +17,9 @@ HTTP 200 [Asserts] jsonpath "$" isCollection +#jsonpath "$.complete" isBoolean #jsonpath "$.id" isInteger #jsonpath "$.petId" isInteger #jsonpath "$.quantity" isInteger #jsonpath "$.shipDate" isString #jsonpath "$.status" isString -#jsonpath "$.complete" isBoolean - diff --git a/src/snapshots/heave__tests__petstore@updatePet_200.hurl.snap b/src/snapshots/heave__tests__petstore@updatePet_200.hurl.snap index 81bd9fd..7102b14 100644 --- a/src/snapshots/heave__tests__petstore@updatePet_200.hurl.snap +++ b/src/snapshots/heave__tests__petstore@updatePet_200.hurl.snap @@ -27,17 +27,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -jsonpath "$.name" isString #jsonpath "$.category" isCollection #jsonpath "$.category.id" isInteger #jsonpath "$.category.name" isString +#jsonpath "$.id" isInteger +jsonpath "$.name" isString jsonpath "$.photoUrls" isCollection #jsonpath "$.photoUrls[0]" isString +#jsonpath "$.status" isString #jsonpath "$.tags" isCollection #jsonpath "$.tags[0]" isCollection #jsonpath "$.tags[0].id" isInteger #jsonpath "$.tags[0].name" isString -#jsonpath "$.status" isString - - diff --git a/src/snapshots/heave__tests__petstore@uploadFile_200.hurl.snap b/src/snapshots/heave__tests__petstore@uploadFile_200.hurl.snap index a641a9f..81dbce5 100644 --- a/src/snapshots/heave__tests__petstore@uploadFile_200.hurl.snap +++ b/src/snapshots/heave__tests__petstore@uploadFile_200.hurl.snap @@ -15,6 +15,5 @@ HTTP 200 [Asserts] jsonpath "$" isCollection #jsonpath "$.code" isInteger -#jsonpath "$.type" isString #jsonpath "$.message" isString - +#jsonpath "$.type" isString diff --git a/src/snapshots/heave__tests__read_only@all_inner_properties_read_only.yaml.snap b/src/snapshots/heave__tests__read_only@all_inner_properties_read_only.yaml.snap index 1c337dc..09a0e30 100644 --- a/src/snapshots/heave__tests__read_only@all_inner_properties_read_only.yaml.snap +++ b/src/snapshots/heave__tests__read_only@all_inner_properties_read_only.yaml.snap @@ -17,9 +17,9 @@ GenerateResult { query_parameters: [], asserts: [ "jsonpath \"$\" isCollection", - "#jsonpath \"$.name\" isString", "#jsonpath \"$.B\" isCollection", "#jsonpath \"$.B.name\" isString", + "#jsonpath \"$.name\" isString", ], request_body_parameter: "{\n \"B\": {}\n}", }, diff --git a/src/snapshots/heave__tests__read_only@all_read_only.yaml.snap b/src/snapshots/heave__tests__read_only@all_read_only.yaml.snap index 22e734e..aef26d1 100644 --- a/src/snapshots/heave__tests__read_only@all_read_only.yaml.snap +++ b/src/snapshots/heave__tests__read_only@all_read_only.yaml.snap @@ -17,9 +17,9 @@ GenerateResult { query_parameters: [], asserts: [ "jsonpath \"$\" isCollection", - "#jsonpath \"$.name\" isString", "#jsonpath \"$.B\" isCollection", "#jsonpath \"$.B.name\" isString", + "#jsonpath \"$.name\" isString", ], request_body_parameter: "", }, diff --git a/src/snapshots/oas31/createPet_201.hurl b/src/snapshots/oas31/createPet_201.hurl new file mode 100644 index 0000000..fe1a5cc --- /dev/null +++ b/src/snapshots/oas31/createPet_201.hurl @@ -0,0 +1,24 @@ +POST {{ baseurl }}/pets +Authorization: Bearer {{ authorization }} +Prefer: code=201 +{ + "age": 0, + "code": 0, + "name": "", + "tag": "" +} +HTTP 201 + +[Asserts] +jsonpath "$" isCollection +#jsonpath "$.age" isInteger +#jsonpath "$.aliases" isCollection +#jsonpath "$.aliases[0]" isString +#jsonpath "$.code" isInteger +#jsonpath "$.code" isString +jsonpath "$.id" isInteger +#jsonpath "$.metadata" isCollection +#jsonpath "$.metadata.source" isString +jsonpath "$.name" isString +#jsonpath "$.tag" isString +#jsonpath "$.weight" isNumber diff --git a/src/snapshots/oas31/getPet_200.hurl b/src/snapshots/oas31/getPet_200.hurl new file mode 100644 index 0000000..1f897fb --- /dev/null +++ b/src/snapshots/oas31/getPet_200.hurl @@ -0,0 +1,19 @@ +GET {{ baseurl }}/pets/{{petId}} +Authorization: Bearer {{ authorization }} +Prefer: code=200 + +HTTP 200 + +[Asserts] +jsonpath "$" isCollection +#jsonpath "$.age" isInteger +#jsonpath "$.aliases" isCollection +#jsonpath "$.aliases[0]" isString +#jsonpath "$.code" isInteger +#jsonpath "$.code" isString +jsonpath "$.id" isInteger +#jsonpath "$.metadata" isCollection +#jsonpath "$.metadata.source" isString +jsonpath "$.name" isString +#jsonpath "$.tag" isString +#jsonpath "$.weight" isNumber diff --git a/src/snapshots/oas31/listPets_200.hurl b/src/snapshots/oas31/listPets_200.hurl new file mode 100644 index 0000000..d77720a --- /dev/null +++ b/src/snapshots/oas31/listPets_200.hurl @@ -0,0 +1,20 @@ +GET {{ baseurl }}/pets +Authorization: Bearer {{ authorization }} +Prefer: code=200 + +HTTP 200 + +[Asserts] +jsonpath "$" isCollection +#jsonpath "$[0]" isCollection +#jsonpath "$[0].age" isInteger +#jsonpath "$[0].aliases" isCollection +#jsonpath "$[0].aliases[0]" isString +#jsonpath "$[0].code" isInteger +#jsonpath "$[0].code" isString +#jsonpath "$[0].id" isInteger +#jsonpath "$[0].metadata" isCollection +#jsonpath "$[0].metadata.source" isString +#jsonpath "$[0].name" isString +#jsonpath "$[0].tag" isString +#jsonpath "$[0].weight" isNumber diff --git a/src/snapshots/oas31/petstore.yaml b/src/snapshots/oas31/petstore.yaml new file mode 100644 index 0000000..e167985 --- /dev/null +++ b/src/snapshots/oas31/petstore.yaml @@ -0,0 +1,108 @@ +openapi: 3.1.0 +info: + version: 1.0.0 + title: Petstore - OpenAPI 3.1 +paths: + /pets: + get: + operationId: listPets + responses: + '200': + description: A list of pets + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + post: + operationId: createPet + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PetInput' + responses: + '201': + description: Pet created + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + /pets/{petId}: + get: + operationId: getPet + parameters: + - name: petId + in: path + required: true + schema: + type: integer + responses: + '200': + description: A pet + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + name: + type: string + tag: + type: + - string + - "null" + age: + type: + - integer + - "null" + weight: + type: + - number + - "null" + aliases: + type: + - array + - "null" + items: + type: string + metadata: + type: + - object + - "null" + properties: + source: + type: string + code: + type: + - integer + - string + PetInput: + type: object + required: + - name + properties: + name: + type: string + tag: + type: + - string + - "null" + age: + type: + - integer + - "null" + code: + type: + - integer + - string diff --git a/src/snapshots/oas31_diagnostics/MultipleTypes_RequestBody.yaml b/src/snapshots/oas31_diagnostics/MultipleTypes_RequestBody.yaml new file mode 100644 index 0000000..9565fe2 --- /dev/null +++ b/src/snapshots/oas31_diagnostics/MultipleTypes_RequestBody.yaml @@ -0,0 +1,30 @@ +openapi: 3.1.0 +info: + version: 1.0.0 + title: Multi-type Test +paths: + /items: + post: + operationId: createItem + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + value: + type: + - integer + - string + label: + type: + - string + - "null" + responses: + '201': + description: Created + content: + application/json: + schema: + type: object diff --git a/src/snapshots/oas31_diagnostics/MultipleTypes_ResponseBody.yaml b/src/snapshots/oas31_diagnostics/MultipleTypes_ResponseBody.yaml new file mode 100644 index 0000000..5a2e16d --- /dev/null +++ b/src/snapshots/oas31_diagnostics/MultipleTypes_ResponseBody.yaml @@ -0,0 +1,29 @@ +openapi: 3.1.0 +info: + version: 1.0.0 + title: Multi-type Test +paths: + /items: + get: + operationId: getItem + responses: + '200': + description: An item + content: + application/json: + schema: + type: object + required: + - id + - value + properties: + id: + type: integer + value: + type: + - integer + - string + label: + type: + - string + - "null" diff --git a/src/snapshots/petstore/addPet_200.hurl b/src/snapshots/petstore/addPet_200.hurl index cddfbaf..7e646eb 100644 --- a/src/snapshots/petstore/addPet_200.hurl +++ b/src/snapshots/petstore/addPet_200.hurl @@ -23,15 +23,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -jsonpath "$.name" isString #jsonpath "$.category" isCollection #jsonpath "$.category.id" isInteger #jsonpath "$.category.name" isString +#jsonpath "$.id" isInteger +jsonpath "$.name" isString jsonpath "$.photoUrls" isCollection #jsonpath "$.photoUrls[0]" isString +#jsonpath "$.status" isString #jsonpath "$.tags" isCollection #jsonpath "$.tags[0]" isCollection #jsonpath "$.tags[0].id" isInteger #jsonpath "$.tags[0].name" isString -#jsonpath "$.status" isString diff --git a/src/snapshots/petstore/createUsersWithListInput_200.hurl b/src/snapshots/petstore/createUsersWithListInput_200.hurl index d4f0fe4..43bc89d 100644 --- a/src/snapshots/petstore/createUsersWithListInput_200.hurl +++ b/src/snapshots/petstore/createUsersWithListInput_200.hurl @@ -17,11 +17,11 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -#jsonpath "$.username" isString +#jsonpath "$.email" isString #jsonpath "$.firstName" isString +#jsonpath "$.id" isInteger #jsonpath "$.lastName" isString -#jsonpath "$.email" isString #jsonpath "$.password" isString #jsonpath "$.phone" isString #jsonpath "$.userStatus" isInteger +#jsonpath "$.username" isString diff --git a/src/snapshots/petstore/findPetsByStatus_200.hurl b/src/snapshots/petstore/findPetsByStatus_200.hurl index bcfb682..d74305f 100644 --- a/src/snapshots/petstore/findPetsByStatus_200.hurl +++ b/src/snapshots/petstore/findPetsByStatus_200.hurl @@ -11,15 +11,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection #jsonpath "$[0]" isCollection -#jsonpath "$[0].id" isInteger -#jsonpath "$[0].name" isString #jsonpath "$[0].category" isCollection #jsonpath "$[0].category.id" isInteger #jsonpath "$[0].category.name" isString +#jsonpath "$[0].id" isInteger +#jsonpath "$[0].name" isString #jsonpath "$[0].photoUrls" isCollection #jsonpath "$[0].photoUrls[0]" isString +#jsonpath "$[0].status" isString #jsonpath "$[0].tags" isCollection #jsonpath "$[0].tags[0]" isCollection #jsonpath "$[0].tags[0].id" isInteger #jsonpath "$[0].tags[0].name" isString -#jsonpath "$[0].status" isString diff --git a/src/snapshots/petstore/findPetsByTags_200.hurl b/src/snapshots/petstore/findPetsByTags_200.hurl index c5f3b3a..c1e5180 100644 --- a/src/snapshots/petstore/findPetsByTags_200.hurl +++ b/src/snapshots/petstore/findPetsByTags_200.hurl @@ -11,15 +11,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection #jsonpath "$[0]" isCollection -#jsonpath "$[0].id" isInteger -#jsonpath "$[0].name" isString #jsonpath "$[0].category" isCollection #jsonpath "$[0].category.id" isInteger #jsonpath "$[0].category.name" isString +#jsonpath "$[0].id" isInteger +#jsonpath "$[0].name" isString #jsonpath "$[0].photoUrls" isCollection #jsonpath "$[0].photoUrls[0]" isString +#jsonpath "$[0].status" isString #jsonpath "$[0].tags" isCollection #jsonpath "$[0].tags[0]" isCollection #jsonpath "$[0].tags[0].id" isInteger #jsonpath "$[0].tags[0].name" isString -#jsonpath "$[0].status" isString diff --git a/src/snapshots/petstore/getOrderById_200.hurl b/src/snapshots/petstore/getOrderById_200.hurl index 0f8a9b1..718de11 100644 --- a/src/snapshots/petstore/getOrderById_200.hurl +++ b/src/snapshots/petstore/getOrderById_200.hurl @@ -6,9 +6,9 @@ HTTP 200 [Asserts] jsonpath "$" isCollection +#jsonpath "$.complete" isBoolean #jsonpath "$.id" isInteger #jsonpath "$.petId" isInteger #jsonpath "$.quantity" isInteger #jsonpath "$.shipDate" isString #jsonpath "$.status" isString -#jsonpath "$.complete" isBoolean diff --git a/src/snapshots/petstore/getPetById_200.hurl b/src/snapshots/petstore/getPetById_200.hurl index 86c96b2..aec5235 100644 --- a/src/snapshots/petstore/getPetById_200.hurl +++ b/src/snapshots/petstore/getPetById_200.hurl @@ -6,15 +6,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -jsonpath "$.name" isString #jsonpath "$.category" isCollection #jsonpath "$.category.id" isInteger #jsonpath "$.category.name" isString +#jsonpath "$.id" isInteger +jsonpath "$.name" isString jsonpath "$.photoUrls" isCollection #jsonpath "$.photoUrls[0]" isString +#jsonpath "$.status" isString #jsonpath "$.tags" isCollection #jsonpath "$.tags[0]" isCollection #jsonpath "$.tags[0].id" isInteger #jsonpath "$.tags[0].name" isString -#jsonpath "$.status" isString diff --git a/src/snapshots/petstore/getUserByName_200.hurl b/src/snapshots/petstore/getUserByName_200.hurl index e1edd76..a9ec168 100644 --- a/src/snapshots/petstore/getUserByName_200.hurl +++ b/src/snapshots/petstore/getUserByName_200.hurl @@ -6,11 +6,11 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -#jsonpath "$.username" isString +#jsonpath "$.email" isString #jsonpath "$.firstName" isString +#jsonpath "$.id" isInteger #jsonpath "$.lastName" isString -#jsonpath "$.email" isString #jsonpath "$.password" isString #jsonpath "$.phone" isString #jsonpath "$.userStatus" isInteger +#jsonpath "$.username" isString diff --git a/src/snapshots/petstore/petstore.json b/src/snapshots/petstore/petstore.json index 2bb5097..9fa9e37 100644 --- a/src/snapshots/petstore/petstore.json +++ b/src/snapshots/petstore/petstore.json @@ -1,5 +1,5 @@ { - "openapi" : "3.0.2", + "openapi" : "3.1.0", "info" : { "contact" : { "email" : "apiteam@swagger.io" diff --git a/src/snapshots/petstore/placeOrder_200.hurl b/src/snapshots/petstore/placeOrder_200.hurl index ead3f54..6060b5f 100644 --- a/src/snapshots/petstore/placeOrder_200.hurl +++ b/src/snapshots/petstore/placeOrder_200.hurl @@ -13,9 +13,9 @@ HTTP 200 [Asserts] jsonpath "$" isCollection +#jsonpath "$.complete" isBoolean #jsonpath "$.id" isInteger #jsonpath "$.petId" isInteger #jsonpath "$.quantity" isInteger #jsonpath "$.shipDate" isString #jsonpath "$.status" isString -#jsonpath "$.complete" isBoolean diff --git a/src/snapshots/petstore/updatePet_200.hurl b/src/snapshots/petstore/updatePet_200.hurl index bd87c92..bf80168 100644 --- a/src/snapshots/petstore/updatePet_200.hurl +++ b/src/snapshots/petstore/updatePet_200.hurl @@ -23,15 +23,15 @@ HTTP 200 [Asserts] jsonpath "$" isCollection -#jsonpath "$.id" isInteger -jsonpath "$.name" isString #jsonpath "$.category" isCollection #jsonpath "$.category.id" isInteger #jsonpath "$.category.name" isString +#jsonpath "$.id" isInteger +jsonpath "$.name" isString jsonpath "$.photoUrls" isCollection #jsonpath "$.photoUrls[0]" isString +#jsonpath "$.status" isString #jsonpath "$.tags" isCollection #jsonpath "$.tags[0]" isCollection #jsonpath "$.tags[0].id" isInteger #jsonpath "$.tags[0].name" isString -#jsonpath "$.status" isString diff --git a/src/snapshots/petstore/uploadFile_200.hurl b/src/snapshots/petstore/uploadFile_200.hurl index 94535d3..da3238b 100644 --- a/src/snapshots/petstore/uploadFile_200.hurl +++ b/src/snapshots/petstore/uploadFile_200.hurl @@ -11,5 +11,5 @@ HTTP 200 [Asserts] jsonpath "$" isCollection #jsonpath "$.code" isInteger -#jsonpath "$.type" isString #jsonpath "$.message" isString +#jsonpath "$.type" isString