Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion exe/generate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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]});
}
}
9 changes: 8 additions & 1 deletion include/openapi/gen_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not used outside its own source file -> remove definition from header

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);
Expand All @@ -61,4 +68,4 @@ void write_types(YAML::Node const&,
std::ostream& source,
std::optional<std::string_view> ns);

} // namespace openapi
} // namespace openapi
19 changes: 18 additions & 1 deletion include/openapi/parse.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "boost/url/params_view.hpp"
#include "boost/url/segments_view.hpp"

#include <sstream>
#include <string_view>
Expand Down Expand Up @@ -67,4 +68,20 @@ T parse_param(boost::urls::params_view const& params,
return default_value.has_value() ? T{*default_value} : T{};
}

} // namespace openapi
template <typename T>
T parse_segment(boost::urls::segments_view const& segs,
std::string_view name,
std::size_t idx) {
auto it = segs.begin();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it only needed in if block

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
54 changes: 47 additions & 7 deletions src/gen_types.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <optional>
#include <ostream>
#include <ranges>

#include "utl/enumerate.h"

Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -314,10 +315,37 @@ 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<std::string_view>();
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<std::string_view>();
auto segs = p | std::views::split('/');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be inlined (not used afterwards and name is not telling anything)

auto seg_idx = std::unordered_map<std::string_view, std::size_t>{};

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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 - 1 underflows?

}

for (auto const& p : n["parameters"]) {
auto const name = p["name"].as<std::string_view>();
auto const items = p["schema"]["items"];
Expand All @@ -333,20 +361,27 @@ 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) {
source << " :";
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<std::string_view>() == "path") {
auto const idx = seg_idx.at(p["name"].as<std::string_view>());
gen_member_init_from_seg(root, p, idx, source);
} else {
gen_member_init(root, p, is_required(p), source);
}
}
}
source << "\n {}\n\n";
Expand All @@ -357,6 +392,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<std::string_view>() == "path") {
continue;
}
auto const name = p["name"].as<std::string_view>();
auto const schema = p["schema"];
auto const has_default = schema["default"].IsDefined();
Expand Down Expand Up @@ -402,7 +440,9 @@ void write_params(YAML::Node const& root,

for (auto const& p : n["parameters"]) {
auto const name = p["name"].as<std::string_view>();
gen_member(root, name, is_required(p), p["schema"], header);
auto const in_path =
p["in"].IsDefined() && p["in"].as<std::string_view>() == "path";
Comment on lines +443 to +444
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't repeat yourself - write a utility function

gen_member(root, name, in_path || is_required(p), p["schema"], header);
}
header << "};\n\n";
}
Expand Down Expand Up @@ -560,7 +600,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<std::string>() + "_response",
Expand All @@ -573,4 +613,4 @@ void write_types(YAML::Node const& root,
write_postlude(header, source, ns);
}

} // namespace openapi
} // namespace openapi
20 changes: 19 additions & 1 deletion test/generate_types_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,22 @@ TEST(openapi, parse_expect_default_value) {
boost::urls::url_view{"/"}.params(), "mode",
std::vector<mode>{mode::TRANSIT, mode::WALK});
EXPECT_EQ((std::vector{mode::TRANSIT, mode::WALK}), v);
}
}

TEST(openapi, parse_segment_string) {
auto url = boost::urls::url_view{"/items/hello/42"};
auto v = std::string{};
parse_segment<std::string>(url.segments(), "param1", 1);
EXPECT_EQ("hello", parse_segment<std::string>(url.segments(), "param1", 1));
}

TEST(openapi, parse_segment_int) {
auto url = boost::urls::url_view{"/items/hello/42"};
EXPECT_EQ(42, parse_segment<std::int64_t>(url.segments(), "param2", 2));
}

TEST(openapi, parse_segment_out_of_bounds) {
auto url = boost::urls::url_view{"/items/hello"};
EXPECT_THROW(parse_segment<std::string>(url.segments(), "param1", 5),
bad_request_exception);
}
33 changes: 33 additions & 0 deletions test/parameter_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include "gtest/gtest.h"

#include "openapi/bad_request_exception.h"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

group headers

#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);
}
20 changes: 17 additions & 3 deletions test/pet.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -10,7 +25,6 @@ paths:
type: array
items:
$ref: '#/components/schemas/Item'

components:
schemas:
Status:
Expand Down Expand Up @@ -38,4 +52,4 @@ components:
y:
$ref: '#/components/schemas/Pets'
z:
type: integer
type: integer