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]});
}
}
6 changes: 3 additions & 3 deletions include/openapi/gen_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
#include <ostream>
#include <string_view>

#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 {
Expand Down Expand Up @@ -51,6 +50,7 @@ void gen_member_init(YAML::Node const& root,
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 +61,4 @@ void write_types(YAML::Node const&,
std::ostream& source,
std::optional<std::string_view> ns);

} // namespace openapi
} // namespace openapi
22 changes: 19 additions & 3 deletions include/openapi/parse.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#pragma once

#include "boost/url/params_view.hpp"

#include <sstream>
#include <string_view>
#include <vector>

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

#include "utl/parser/arg_parser.h"
#include "utl/verify.h"

Expand Down Expand Up @@ -67,4 +68,19 @@ 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) {
if (idx < segs.size()) {
auto it = segs.begin();
std::advance(it, idx);
auto v = T{};
parse(*it, v);
return v;
}
throw bad_request_exception{
fmt::format("missing segment parameter: {}", name)};
}

} // namespace openapi
57 changes: 50 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 seg_idx = std::unordered_map<std::string_view, std::size_t>{};

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);
}
if (sv.ends_with('}')) {
sv.remove_suffix(1);
}
seg_idx.emplace(sv, i == 0 ? i : i - 1);
}

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,32 @@ 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 in_path = [](const auto& p) {
return p["in"].IsDefined() &&
p["in"].template as<std::string_view>() == "path";
};

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 (in_path(p)) {
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 +397,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 (in_path(p)) {
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 +445,7 @@ 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);
gen_member(root, name, in_path(p) || is_required(p), p["schema"], header);
}
header << "};\n\n";
}
Expand Down Expand Up @@ -560,7 +603,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 +616,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);
}
26 changes: 26 additions & 0 deletions test/parameter_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include "gtest/gtest.h"

#include "boost/json.hpp"
#include "boost/url.hpp"

#include "openapi/bad_request_exception.h"
#include "openapi/gen_types.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