From 3242516d8e2f6664f105c8505256e2eb439847b2 Mon Sep 17 00:00:00 2001 From: DIDONAX Date: Mon, 25 May 2026 02:16:39 +0200 Subject: [PATCH 1/5] handle in path params --- exe/generate.cc | 2 +- include/openapi/gen_types.h | 9 ++++++- include/openapi/parse.h | 19 +++++++++++++- src/gen_types.cc | 50 +++++++++++++++++++++++++++++++------ test/generate_types_test.cc | 20 ++++++++++++++- test/parameter_test.cc | 33 ++++++++++++++++++++++++ test/pet.yml | 20 ++++++++++++--- 7 files changed, 139 insertions(+), 14 deletions(-) create mode 100644 test/parameter_test.cc diff --git a/exe/generate.cc b/exe/generate.cc index ccd56a9..8902e6b 100644 --- a/exe/generate.cc +++ b/exe/generate.cc @@ -16,4 +16,4 @@ int main(int argc, char** argv) { auto source = std::ofstream{argv[3]}; openapi::write_types(root, argv[2], header, source, std::string_view{argv[4]}); -} \ No newline at end of file +} diff --git a/include/openapi/gen_types.h b/include/openapi/gen_types.h index 6705ba5..4114c34 100644 --- a/include/openapi/gen_types.h +++ b/include/openapi/gen_types.h @@ -50,7 +50,14 @@ void gen_member_init(YAML::Node const& root, std::ostream& header, std::ostream& source); +void gen_member_init_from_seg(YAML::Node const& root, + YAML::Node const& x, + std::size_t seg_idx, + std::ostream& header, + std::ostream& source); + void write_params(YAML::Node const& root, + YAML::Node const& path, YAML::Node const&, std::ostream& header, std::ostream& source); @@ -61,4 +68,4 @@ void write_types(YAML::Node const&, std::ostream& source, std::optional ns); -} // namespace openapi \ No newline at end of file +} // namespace openapi diff --git a/include/openapi/parse.h b/include/openapi/parse.h index 9f8fe23..2ffaf42 100644 --- a/include/openapi/parse.h +++ b/include/openapi/parse.h @@ -1,6 +1,7 @@ #pragma once #include "boost/url/params_view.hpp" +#include "boost/url/segments_view.hpp" #include #include @@ -67,4 +68,20 @@ T parse_param(boost::urls::params_view const& params, return default_value.has_value() ? T{*default_value} : T{}; } -} // namespace openapi \ No newline at end of file +template +T parse_segment(boost::urls::segments_view const& segs, + std::string_view name, + std::size_t idx) { + auto it = segs.begin(); + if (idx < segs.size()) { + std::advance(it, idx); + auto v = T{}; + parse(*it, v); + return v; + } else { + throw bad_request_exception{ + fmt::format("missing segment parameter: {}", name)}; + } +} + +} // namespace openapi diff --git a/src/gen_types.cc b/src/gen_types.cc index e390004..9278f69 100644 --- a/src/gen_types.cc +++ b/src/gen_types.cc @@ -2,6 +2,7 @@ #include #include +#include #include "utl/enumerate.h" @@ -108,7 +109,7 @@ std::string_view to_cpp(type const t) { struct indent { explicit indent(int indent, char separator = ',') - : indent_{indent}, separator_{separator} {} + : separator_{separator}, indent_{indent} {} void operator()(std::ostream& out) { if (!first_ && separator_ != '\0') { @@ -314,10 +315,33 @@ void gen_member_init(YAML::Node const& root, out << ", allow_missing)}"; } +void gen_member_init_from_seg(YAML::Node const& root, + YAML::Node const& x, + std::size_t idx, + std::ostream& out) { + auto const schema = x["schema"]; + auto const name = x["name"].as(); + auto const type = get_type(root, name, schema, true); + out << " " << name << "_{::openapi::parse_segment<" << type << ">(segs, \"" + << name << "\", " << idx << ")}"; +} + void write_params(YAML::Node const& root, + YAML::Node const& path, YAML::Node const& n, std::ostream& header, std::ostream& source) { + const auto p = path.as(); + auto segs = p | std::views::split('/'); + auto seg_idx = std::unordered_map{}; + + for (auto const& [i, seg] : segs | std::views::enumerate) { + auto sv = std::string_view{seg.begin(), seg.end()}; + if (sv.starts_with('{')) sv.remove_prefix(1); + if (sv.ends_with('}')) sv.remove_suffix(1); + seg_idx.emplace(sv, i - 1); + } + for (auto const& p : n["parameters"]) { auto const name = p["name"].as(); auto const items = p["schema"]["items"]; @@ -333,12 +357,14 @@ void write_params(YAML::Node const& root, header << "struct " << id << " {\n"; header << " explicit " << id << "();\n"; header << " explicit " << id - << "(boost::urls::params_view const&, bool allow_missing = false);\n"; + << "(boost::urls::params_view const&, boost::urls::segments_view " + "const& = {}, bool allow_missing = false);\n"; header << " boost::urls::url to_url(std::string_view path) const;\n"; source << id << "::" << id << "() = default;\n"; source << id << "::" << id - << "(boost::urls::params_view const& params, bool allow_missing)"; + << "(boost::urls::params_view const& params, " + "boost::urls::segments_view const& segs, bool allow_missing)"; auto const parameters = n["parameters"]; if (parameters.IsDefined() && parameters.size() != 0) { @@ -346,7 +372,12 @@ void write_params(YAML::Node const& root, auto ind = indent{2}; for (auto const& p : parameters) { ind(source); - gen_member_init(root, p, is_required(p), source); + if (p["in"].IsDefined() && p["in"].as() == "path") { + auto const idx = seg_idx.at(p["name"].as()); + gen_member_init_from_seg(root, p, idx, source); + } else { + gen_member_init(root, p, is_required(p), source); + } } } source << "\n {}\n\n"; @@ -357,6 +388,9 @@ void write_params(YAML::Node const& root, source << " auto u = boost::urls::url{path};\n"; source << " auto default_val = " << id << "{};\n"; for (auto const& p : parameters) { + if (p["in"].IsDefined() && p["in"].as() == "path") { + continue; + } auto const name = p["name"].as(); auto const schema = p["schema"]; auto const has_default = schema["default"].IsDefined(); @@ -402,7 +436,9 @@ void write_params(YAML::Node const& root, for (auto const& p : n["parameters"]) { auto const name = p["name"].as(); - gen_member(root, name, is_required(p), p["schema"], header); + auto const in_path = + p["in"].IsDefined() && p["in"].as() == "path"; + gen_member(root, name, in_path || is_required(p), p["schema"], header); } header << "};\n\n"; } @@ -560,7 +596,7 @@ void write_types(YAML::Node const& root, for (auto const& path : root["paths"]) { for (auto const& method : path.second) { - write_params(root, method.second, header, source); + write_params(root, path.first, method.second, header, source); for (auto const& response : method.second["responses"]) { gen_type(method.second["operationId"].as() + "_response", @@ -573,4 +609,4 @@ void write_types(YAML::Node const& root, write_postlude(header, source, ns); } -} // namespace openapi \ No newline at end of file +} // namespace openapi diff --git a/test/generate_types_test.cc b/test/generate_types_test.cc index d24dcfc..1deee99 100644 --- a/test/generate_types_test.cc +++ b/test/generate_types_test.cc @@ -65,4 +65,22 @@ TEST(openapi, parse_expect_default_value) { boost::urls::url_view{"/"}.params(), "mode", std::vector{mode::TRANSIT, mode::WALK}); EXPECT_EQ((std::vector{mode::TRANSIT, mode::WALK}), v); -} \ No newline at end of file +} + +TEST(openapi, parse_segment_string) { + auto url = boost::urls::url_view{"/items/hello/42"}; + auto v = std::string{}; + parse_segment(url.segments(), "param1", 1); + EXPECT_EQ("hello", parse_segment(url.segments(), "param1", 1)); +} + +TEST(openapi, parse_segment_int) { + auto url = boost::urls::url_view{"/items/hello/42"}; + EXPECT_EQ(42, parse_segment(url.segments(), "param2", 2)); +} + +TEST(openapi, parse_segment_out_of_bounds) { + auto url = boost::urls::url_view{"/items/hello"}; + EXPECT_THROW(parse_segment(url.segments(), "param1", 5), + bad_request_exception); +} diff --git a/test/parameter_test.cc b/test/parameter_test.cc new file mode 100644 index 0000000..a360e2c --- /dev/null +++ b/test/parameter_test.cc @@ -0,0 +1,33 @@ +#include "gtest/gtest.h" + +#include "openapi/bad_request_exception.h" +#include "yaml-cpp/yaml.h" + +#include "cista/hash.h" + +#include "boost/json.hpp" +#include "boost/url.hpp" + +#include "utl/verify.h" + +#include "openapi/gen_types.h" +#include "openapi/json.h" + +#include "pet-api/pet-api.h" + +using namespace openapi; +using namespace pet; + +TEST(openapi, valid_query) { + auto url = boost::urls::url_view{"/items/test/42?param3=10"}; + auto p = pet::getItems_params{url.params(), url.segments()}; + EXPECT_EQ("test", p.param1_); + EXPECT_EQ(42, p.param2_); + EXPECT_EQ(10, p.param3_); +} + +TEST(openapi, path_params_always_required) { + auto url = boost::urls::url_view{"/items?param3=10"}; + EXPECT_THROW(pet::getItems_params(url.params(), url.segments()), + openapi::bad_request_exception); +} diff --git a/test/pet.yml b/test/pet.yml index 16507f8..8c425d9 100644 --- a/test/pet.yml +++ b/test/pet.yml @@ -1,7 +1,22 @@ paths: - /items: + /items/{param1}/{param2}: get: operationId: getItems + parameters: + - name: param1 + required: true + in: path + schema: + type: string + - name: param2 + required: true + in: path + schema: + type: integer + - name: param3 + schema: + type: integer + responses: 200: content: @@ -10,7 +25,6 @@ paths: type: array items: $ref: '#/components/schemas/Item' - components: schemas: Status: @@ -38,4 +52,4 @@ components: y: $ref: '#/components/schemas/Pets' z: - type: integer \ No newline at end of file + type: integer From 103b1be868fae02fa04130f18db89580655e6b19 Mon Sep 17 00:00:00 2001 From: DIDONAX Date: Tue, 26 May 2026 19:02:12 +0200 Subject: [PATCH 2/5] fix brackets --- src/gen_types.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gen_types.cc b/src/gen_types.cc index 9278f69..a6d9bed 100644 --- a/src/gen_types.cc +++ b/src/gen_types.cc @@ -337,8 +337,12 @@ void write_params(YAML::Node const& root, for (auto const& [i, seg] : segs | std::views::enumerate) { auto sv = std::string_view{seg.begin(), seg.end()}; - if (sv.starts_with('{')) sv.remove_prefix(1); - if (sv.ends_with('}')) sv.remove_suffix(1); + if (sv.starts_with('{')) { + sv.remove_prefix(1); + } + if (sv.ends_with('}')) { + sv.remove_suffix(1); + } seg_idx.emplace(sv, i - 1); } From 5157813cbf95abdb52efd4c5ba9f6d53ecc924a0 Mon Sep 17 00:00:00 2001 From: DIDONAX Date: Fri, 5 Jun 2026 00:23:53 +0200 Subject: [PATCH 3/5] fix --- include/openapi/gen_types.h | 6 ------ include/openapi/parse.h | 8 ++++---- src/gen_types.cc | 18 ++++++++++-------- test/parameter_test.cc | 9 +-------- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/include/openapi/gen_types.h b/include/openapi/gen_types.h index 4114c34..6c679b6 100644 --- a/include/openapi/gen_types.h +++ b/include/openapi/gen_types.h @@ -50,12 +50,6 @@ void gen_member_init(YAML::Node const& root, std::ostream& header, std::ostream& source); -void gen_member_init_from_seg(YAML::Node const& root, - YAML::Node const& x, - std::size_t seg_idx, - std::ostream& header, - std::ostream& source); - void write_params(YAML::Node const& root, YAML::Node const& path, YAML::Node const&, diff --git a/include/openapi/parse.h b/include/openapi/parse.h index 2ffaf42..7089254 100644 --- a/include/openapi/parse.h +++ b/include/openapi/parse.h @@ -7,6 +7,7 @@ #include #include +#include "bad_request_exception.h" #include "utl/parser/arg_parser.h" #include "utl/verify.h" @@ -72,16 +73,15 @@ template T parse_segment(boost::urls::segments_view const& segs, std::string_view name, std::size_t idx) { - auto it = segs.begin(); if (idx < segs.size()) { + auto it = segs.begin(); std::advance(it, idx); auto v = T{}; parse(*it, v); return v; - } else { - throw bad_request_exception{ - fmt::format("missing segment parameter: {}", name)}; } + throw bad_request_exception{ + fmt::format("missing segment parameter: {}", name)}; } } // namespace openapi diff --git a/src/gen_types.cc b/src/gen_types.cc index a6d9bed..40dccf9 100644 --- a/src/gen_types.cc +++ b/src/gen_types.cc @@ -332,10 +332,10 @@ void write_params(YAML::Node const& root, std::ostream& header, std::ostream& source) { const auto p = path.as(); - auto segs = p | std::views::split('/'); auto seg_idx = std::unordered_map{}; - for (auto const& [i, seg] : segs | std::views::enumerate) { + for (auto const& [i, seg] : + p | std::views::split('/') | std::views::enumerate) { auto sv = std::string_view{seg.begin(), seg.end()}; if (sv.starts_with('{')) { sv.remove_prefix(1); @@ -343,7 +343,7 @@ void write_params(YAML::Node const& root, if (sv.ends_with('}')) { sv.remove_suffix(1); } - seg_idx.emplace(sv, i - 1); + seg_idx.emplace(sv, i == 0 ? i : i - 1); } for (auto const& p : n["parameters"]) { @@ -370,13 +370,17 @@ void write_params(YAML::Node const& root, << "(boost::urls::params_view const& params, " "boost::urls::segments_view const& segs, bool allow_missing)"; + auto in_path = [](const auto& p) { + return p["in"].IsDefined() && p["in"].as() == "path"; + }; + auto const parameters = n["parameters"]; if (parameters.IsDefined() && parameters.size() != 0) { source << " :"; auto ind = indent{2}; for (auto const& p : parameters) { ind(source); - if (p["in"].IsDefined() && p["in"].as() == "path") { + if (in_path(p)) { auto const idx = seg_idx.at(p["name"].as()); gen_member_init_from_seg(root, p, idx, source); } else { @@ -392,7 +396,7 @@ void write_params(YAML::Node const& root, source << " auto u = boost::urls::url{path};\n"; source << " auto default_val = " << id << "{};\n"; for (auto const& p : parameters) { - if (p["in"].IsDefined() && p["in"].as() == "path") { + if (in_path(p)) { continue; } auto const name = p["name"].as(); @@ -440,9 +444,7 @@ void write_params(YAML::Node const& root, for (auto const& p : n["parameters"]) { auto const name = p["name"].as(); - auto const in_path = - p["in"].IsDefined() && p["in"].as() == "path"; - gen_member(root, name, in_path || is_required(p), p["schema"], header); + gen_member(root, name, in_path(p) || is_required(p), p["schema"], header); } header << "};\n\n"; } diff --git a/test/parameter_test.cc b/test/parameter_test.cc index a360e2c..e7120ca 100644 --- a/test/parameter_test.cc +++ b/test/parameter_test.cc @@ -1,17 +1,10 @@ #include "gtest/gtest.h" -#include "openapi/bad_request_exception.h" -#include "yaml-cpp/yaml.h" - -#include "cista/hash.h" - #include "boost/json.hpp" #include "boost/url.hpp" -#include "utl/verify.h" - +#include "openapi/bad_request_exception.h" #include "openapi/gen_types.h" -#include "openapi/json.h" #include "pet-api/pet-api.h" From b2a77734106d224ee7215508be33c73e5fe11cec Mon Sep 17 00:00:00 2001 From: DIDONAX Date: Fri, 5 Jun 2026 00:28:32 +0200 Subject: [PATCH 4/5] fix --- include/openapi/gen_types.h | 3 +-- include/openapi/parse.h | 7 +++---- src/gen_types.cc | 7 +++---- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/include/openapi/gen_types.h b/include/openapi/gen_types.h index 6c679b6..aefb42d 100644 --- a/include/openapi/gen_types.h +++ b/include/openapi/gen_types.h @@ -4,14 +4,13 @@ #include #include +#include "utl/to_vec.h" #include "utl/verify.h" #include "yaml-cpp/yaml.h" #include "cista/hash.h" -#include "utl/to_vec.h" - namespace openapi { enum class type { diff --git a/include/openapi/parse.h b/include/openapi/parse.h index 7089254..a2b5a5b 100644 --- a/include/openapi/parse.h +++ b/include/openapi/parse.h @@ -1,13 +1,12 @@ #pragma once -#include "boost/url/params_view.hpp" -#include "boost/url/segments_view.hpp" - #include #include #include -#include "bad_request_exception.h" +#include "boost/url/params_view.hpp" +#include "boost/url/segments_view.hpp" + #include "utl/parser/arg_parser.h" #include "utl/verify.h" diff --git a/src/gen_types.cc b/src/gen_types.cc index 40dccf9..5d0015f 100644 --- a/src/gen_types.cc +++ b/src/gen_types.cc @@ -36,9 +36,7 @@ void write_prelude(std::string_view path_to_header, #include "openapi/json.h" #include "openapi/parse.h" -namespace std { - -template +namespace std { template std::ostream& operator<<(std::ostream& out, std::vector const& v) { auto first = true; for (auto const& x : v) { @@ -371,7 +369,8 @@ void write_params(YAML::Node const& root, "boost::urls::segments_view const& segs, bool allow_missing)"; auto in_path = [](const auto& p) { - return p["in"].IsDefined() && p["in"].as() == "path"; + return p["in"].IsDefined() && + p["in"].template as() == "path"; }; auto const parameters = n["parameters"]; From 2190d1c699dc5433868f862b04e9ed1049d2522d Mon Sep 17 00:00:00 2001 From: DIDONAX Date: Fri, 5 Jun 2026 00:32:36 +0200 Subject: [PATCH 5/5] indent --- src/gen_types.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gen_types.cc b/src/gen_types.cc index 5d0015f..89c3b79 100644 --- a/src/gen_types.cc +++ b/src/gen_types.cc @@ -36,7 +36,9 @@ void write_prelude(std::string_view path_to_header, #include "openapi/json.h" #include "openapi/parse.h" -namespace std { template +namespace std { + +template std::ostream& operator<<(std::ostream& out, std::vector const& v) { auto first = true; for (auto const& x : v) {