Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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,
Comment thread
DIDONAX marked this conversation as resolved.
Outdated
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();
Comment thread
DIDONAX marked this conversation as resolved.
Outdated
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('/');
Comment thread
DIDONAX marked this conversation as resolved.
Outdated
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?

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.

??

}

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";

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

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.

??

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"
Comment thread
DIDONAX marked this conversation as resolved.
Outdated
#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