29 static inline nlohmann::json& get_object(
30 nlohmann::json& j,
const std::string_view& k)
32 const auto ib = j.emplace(k, nlohmann::json::object());
33 return ib.first.value();
36 static inline nlohmann::json& get_array(
37 nlohmann::json& j,
const std::string_view& k)
39 const auto ib = j.emplace(k, nlohmann::json::array());
40 return ib.first.value();
44 static inline std::string sanitise_components_key(
const std::string_view& s)
51 std::regex re(
"[^a-zA-Z0-9\\.\\-_]");
53 std::back_inserter(result), s.begin(), s.end(), re,
"_");
57 static inline nlohmann::json create_document(
58 const std::string_view& title,
59 const std::string_view& description,
60 const std::string_view& document_version)
62 return nlohmann::json{
66 {
"description", description},
67 {
"version", document_version}}},
68 {
"servers", nlohmann::json::array()},
69 {
"paths", nlohmann::json::object()}};
72 static inline nlohmann::json& server(
73 nlohmann::json& document,
const std::string_view& url)
75 auto& servers = access::get_object(document,
"servers");
76 servers.push_back({{
"url", url}});
77 return servers.back();
80 static inline nlohmann::json& path(
81 nlohmann::json& document,
const std::string_view& path)
86 p = fmt::format(
"/{}", p);
89 auto& paths = access::get_object(document,
"paths");
90 return access::get_object(paths, p);
93 static inline nlohmann::json& path_operation(
94 nlohmann::json& path, llhttp_method verb,
bool default_responses =
true)
97 std::string s = llhttp_method_name(verb);
98 ccf::nonstd::to_lower(s);
99 auto& po = access::get_object(path, s);
101 if (default_responses)
105 access::get_object(po,
"responses");
111 static inline nlohmann::json& parameters(nlohmann::json& path_operation)
113 return access::get_array(path_operation,
"parameters");
116 static inline nlohmann::json& responses(nlohmann::json& path_operation)
118 return access::get_object(path_operation,
"responses");
121 static inline nlohmann::json& response(
122 nlohmann::json& path_operation,
124 const std::string_view& description =
"Default response description")
126 auto& all_responses = responses(path_operation);
130 const auto s = std::to_string(status);
131 auto& response = access::get_object(all_responses, s);
132 response[
"description"] = description;
136 static inline nlohmann::json& error_response_default(
137 nlohmann::json& path_operation)
139 auto& all_responses = responses(path_operation);
140 auto& response = access::get_object(all_responses,
"default");
141 response[
"$ref"] =
"#/components/responses/default";
145 static inline nlohmann::json& request_body(nlohmann::json& path_operation)
147 auto& request_body = access::get_object(path_operation,
"requestBody");
148 access::get_object(request_body,
"content");
152 static inline nlohmann::json& media_type(
153 nlohmann::json& j,
const std::string_view& mt)
155 auto& content = access::get_object(j,
"content");
156 return access::get_object(content, mt);
159 static inline nlohmann::json& schema(nlohmann::json& media_type_object)
161 return access::get_object(media_type_object,
"schema");
164 static inline nlohmann::json& extension(
165 nlohmann::json&
object,
const std::string_view& extension_name)
167 if (!extension_name.starts_with(
"x-"))
169 throw std::logic_error(fmt::format(
170 "Adding extension with name '{}'. Extension fields must begin with "
175 return access::get_object(
object, extension_name);
182 static inline nlohmann::json components_ref_object(
183 const std::string_view& element_name)
185 auto schema_ref_object = nlohmann::json::object();
186 schema_ref_object[
"$ref"] =
187 fmt::format(
"#/components/schemas/{}", element_name);
188 return schema_ref_object;
192 static inline nlohmann::json add_schema_to_components(
193 nlohmann::json& document,
194 const std::string_view& element_name,
195 const nlohmann::json& schema_)
197 const auto name = sanitise_components_key(element_name);
199 auto& components = access::get_object(document,
"components");
200 auto& schemas = access::get_object(components,
"schemas");
202 const auto schema_it = schemas.find(name);
203 if (schema_it != schemas.end())
207 const auto& existing_schema = schema_it.value();
208 if (schema_ != existing_schema)
210 throw std::logic_error(fmt::format(
211 "Adding schema with name '{}'. Does not match previous schema "
212 "registered with this name: {} vs {}",
215 existing_schema.dump()));
220 schemas.emplace(name, schema_);
223 return components_ref_object(name);
226 static inline void add_security_scheme_to_components(
227 nlohmann::json& document,
228 const std::string_view& scheme_name,
229 const nlohmann::json& security_scheme)
231 const auto name = sanitise_components_key(scheme_name);
233 auto& components = access::get_object(document,
"components");
234 auto& schemes = access::get_object(components,
"securitySchemes");
236 const auto schema_it = schemes.find(name);
237 if (schema_it != schemes.end())
241 const auto& existing_scheme = schema_it.value();
242 if (security_scheme != existing_scheme)
244 throw std::logic_error(fmt::format(
245 "Adding security scheme with name '{}'. Does not match previous "
246 "scheme registered with this name: {} vs {}",
248 security_scheme.dump(),
249 existing_scheme.dump()));
254 schemes.emplace(name, security_scheme);
265 template <
typename Doc,
typename T>
275 template <
typename T>
278 nlohmann::json schema;
281 return add_schema_component<typename T::value_type>();
288 if constexpr (std::is_same<T, std::vector<uint8_t>>::value)
291 schema[
"type"] =
"string";
292 schema[
"format"] =
"base64";
296 schema[
"type"] =
"array";
297 schema[
"items"] = add_schema_component<typename T::value_type>();
300 return add_schema_to_components(
301 document, ccf::ds::json::schema_name<T>(), schema);
307 if constexpr (nlohmann::detail::
308 is_compatible_object_type<nlohmann::json, T>::value)
310 schema[
"type"] =
"object";
311 schema[
"additionalProperties"] =
312 add_schema_component<typename T::mapped_type>();
316 schema[
"type"] =
"array";
317 auto items = nlohmann::json::object();
319 items[
"type"] =
"array";
321 auto sub_items = nlohmann::json::array();
322 sub_items.push_back(add_schema_component<typename T::key_type>());
324 add_schema_component<typename T::mapped_type>());
326 items[
"items"][
"oneOf"] = sub_items;
327 items[
"minItems"] = 2;
328 items[
"maxItems"] = 2;
330 schema[
"items"] = items;
332 return add_schema_to_components(
333 document, ccf::ds::json::schema_name<T>(), schema);
337 schema[
"type"] =
"array";
338 auto items = nlohmann::json::array();
339 items.push_back(add_schema_component<typename T::first_type>());
340 items.push_back(add_schema_component<typename T::second_type>());
341 schema[
"items"] = items;
342 return add_schema_to_components(
343 document, ccf::ds::json::schema_name<T>(), schema);
346 std::is_same<T, std::string>::value || std::is_arithmetic_v<T> ||
347 std::is_same<T, nlohmann::json>::value ||
348 std::is_same<T, ccf::ds::json::JsonSchema>::value)
350 ccf::ds::json::fill_schema<T>(schema);
351 return add_schema_to_components(
352 document, ccf::ds::json::schema_name<T>(), schema);
357 sanitise_components_key(ccf::ds::json::schema_name<T>());
359 auto& components = access::get_object(
document,
"components");
360 auto& schemas = access::get_object(components,
"schemas");
362 const auto ib = schemas.emplace(name, nlohmann::json::object());
365 auto& j = ib.first.value();
367#pragma clang diagnostic push
368#if defined(__clang__) && __clang_major__ >= 11
369# pragma clang diagnostic ignored "-Wuninitialized-const-reference"
373 if constexpr (std::is_enum<T>::value)
375 fill_enum_schema(j, t);
381#pragma clang diagnostic pop
384 return components_ref_object(name);
389 template <
typename T>
390 static inline char const* auto_content_type()
392 if constexpr (std::is_same_v<T, std::string>)
394 return http::headervalues::contenttype::TEXT;
398 return http::headervalues::contenttype::JSON;
402 static inline void add_request_body_schema(
403 nlohmann::json& document,
404 const std::string_view& uri,
406 const std::string_view& content_type,
408 const nlohmann::json& schema_)
410 auto&
rb = request_body(path_operation(path(document, uri), verb));
411 rb[
"description"] =
"Auto-generated request body schema";
413 schema(media_type(
rb, content_type)) =
414 add_schema_to_components(document,
schema_name, schema_);
417 template <
typename T>
418 static inline void add_request_body_schema(
419 nlohmann::json& document,
const std::string_view& uri, llhttp_method verb)
421 auto&
rb = request_body(path_operation(path(document, uri), verb));
422 rb[
"description"] =
"Auto-generated request body schema";
424 SchemaHelper sh{document};
425 const auto schema_comp = sh.add_schema_component<T>();
426 if (schema_comp !=
nullptr)
428 schema(media_type(
rb, auto_content_type<T>())) =
429 sh.add_schema_component<T>();
433 static inline void add_path_parameter_schema(
434 nlohmann::json& document,
435 const std::string_view& uri,
436 const nlohmann::json& param)
438 auto& params = parameters(path(document, uri));
439 for (
auto& p : params)
441 if (p[
"name"] == param[
"name"])
444 params.push_back(param);
447 static inline void add_request_parameter_schema(
448 nlohmann::json& document,
449 const std::string_view& uri,
451 const nlohmann::json& param)
453 auto& params = parameters(path_operation(path(document, uri), verb));
454 params.push_back(param);
457 static inline void add_response_schema(
458 nlohmann::json& document,
459 const std::string_view& uri,
462 const std::string_view& content_type,
464 const nlohmann::json& schema_)
466 auto& r = response(path_operation(path(document, uri), verb), status);
468 schema(media_type(r, content_type)) =
469 add_schema_to_components(document,
schema_name, schema_);
472 template <
typename T>
473 static inline void add_response_schema(
474 nlohmann::json& document,
475 const std::string_view& uri,
479 auto& r = response(path_operation(path(document, uri), verb), status);
481 SchemaHelper sh{document};
482 const auto schema_comp = sh.add_schema_component<T>();
483 if (schema_comp !=
nullptr)
485 schema(media_type(r, auto_content_type<T>())) =
486 sh.add_schema_component<T>();