mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2025-09-04 01:42:44 +00:00
Refractor ApiClient and add support for body params
ApiClient was refractored to use PIMPL. This is mainly done to reduce compile times whenever the implementation of ApiClient itself changes, since a lot of files include it. The loaders have gained support for body parameters, this was somehow omitted before.
This commit is contained in:
parent
1453cbbc63
commit
96ecd8e7d8
116 changed files with 4437 additions and 106 deletions
|
@ -36,6 +36,7 @@ import std.functional;
|
|||
import std.path : buildPath, dirSeparator;
|
||||
import std.parallelism : parallel;
|
||||
import std.range;
|
||||
import std.regex;
|
||||
import std.string;
|
||||
import std.stdio;
|
||||
import std.uni;
|
||||
|
@ -134,6 +135,8 @@ bool GENERATE_PROPERTIES = true;
|
|||
string outputDirectory = "generated";
|
||||
// END CODE GENERATION SETTINGS.
|
||||
|
||||
static immutable MimeType MIME_APPLICATION_JSON = MimeType("application", "json");
|
||||
|
||||
// Implementation
|
||||
|
||||
enum CasePolicy {
|
||||
|
@ -194,6 +197,7 @@ void realMain(string[] args) {
|
|||
foreach(string path, ref Node operations; root["paths"]) {
|
||||
foreach (string operation, ref Node endpoint; operations) {
|
||||
endpoint["path"] = path;
|
||||
endpoint["operation"] = operation;
|
||||
string tag;
|
||||
if ("tags" in endpoint && endpoint["tags"].length > 0) {
|
||||
tag = endpoint["tags"][0].as!string;
|
||||
|
@ -329,6 +333,7 @@ void writeRequestTypesFile(R)(File headerFile, File implementationFile, R endpoi
|
|||
RequestParameter[] requiredParameters = [];
|
||||
RequestParameter[] optionalParameters = [];
|
||||
RequestParameter[] parameters = [];
|
||||
RequestParameter[] bodyParameters = [];
|
||||
}
|
||||
|
||||
struct Controller {
|
||||
|
@ -347,8 +352,10 @@ void writeRequestTypesFile(R)(File headerFile, File implementationFile, R endpoi
|
|||
getParameters(endpoint.parameters, (e => e.required && e.location == ParameterLocation.QUERY));
|
||||
endpointController.optionalQueryParameters =
|
||||
getParameters(endpoint.parameters, (e => !e.required && e.location == ParameterLocation.QUERY));
|
||||
endpointController.bodyParameters =
|
||||
getParameters(endpoint.parameters, (e => e.location == ParameterLocation.BODY));
|
||||
with (endpointController) {
|
||||
parameters = requiredPathParameters ~ requiredQueryParameters ~ optionalQueryParameters;
|
||||
parameters = requiredPathParameters ~ requiredQueryParameters ~ optionalQueryParameters ~ bodyParameters;
|
||||
|
||||
requiredParameters = requiredPathParameters ~ requiredQueryParameters;
|
||||
optionalParameters = optionalQueryParameters;
|
||||
|
@ -384,6 +391,7 @@ void generateFileForEndpoints(ref const Node[] endpointNodes,
|
|||
string responseType = "void";
|
||||
string parameterType = "void";
|
||||
Endpoint endpoint;
|
||||
string operation;
|
||||
|
||||
string pathStringInterpolation() {
|
||||
string result = "QStringLiteral(\"" ~ endpoint.path ~ "\")";
|
||||
|
@ -414,6 +422,7 @@ void generateFileForEndpoints(ref const Node[] endpointNodes,
|
|||
endpoint.parameterType = name ~ "Params";
|
||||
endpoint.description = endpointNode.getOr!string("summary", "");
|
||||
endpoint.path = endpointNode["path"].as!string;
|
||||
endpoint.operation = endpointNode["operation"].as!string.toLower();
|
||||
|
||||
// Find the most likely result response.
|
||||
foreach(string code, const Node response; endpointNode["responses"]) {
|
||||
|
@ -451,6 +460,47 @@ void generateFileForEndpoints(ref const Node[] endpointNodes,
|
|||
}
|
||||
}
|
||||
|
||||
if ("requestBody" in endpointNode) {
|
||||
string description = "";
|
||||
if ("description" in endpointNode["requestBody"]) {
|
||||
description = endpointNode["requestBody"]["description"].as!string;
|
||||
}
|
||||
|
||||
MimeType[] similarMimeTypes = [];
|
||||
|
||||
foreach(string contentType, const Node content; endpointNode["requestBody"]["content"]) {
|
||||
MimeType mimeType = MimeType.parse(contentType);
|
||||
// Skip if we already had a similar mime type before.
|
||||
if (similarMimeTypes.any!((t) => t.compatible(mimeType))) continue;
|
||||
similarMimeTypes ~= [mimeType];
|
||||
|
||||
RequestParameter param = new RequestParameter();
|
||||
param.location = ParameterLocation.BODY;
|
||||
param.description = description;
|
||||
param.required = true;
|
||||
|
||||
// Hardcode this because the openapi description of Jellyfin seems to contain both
|
||||
if (mimeType.type == "text" && mimeType.subtype == "json") continue;
|
||||
|
||||
if (mimeType.compatible(MIME_APPLICATION_JSON)) {
|
||||
string name = "body";
|
||||
param.type = getType(name, content["schema"], allSchemas);
|
||||
} else {
|
||||
MetaTypeInfo info = new MetaTypeInfo();
|
||||
info.name = "body";
|
||||
info.originalName = "body";
|
||||
info.typeName = "QByteArray";
|
||||
info.isTypeNullable = true;
|
||||
info.needsSystemImport = true;
|
||||
info.typeNullableCheck = ".isEmpty()";
|
||||
info.typeNullableSetter = ".clear()";
|
||||
param.type = info;
|
||||
}
|
||||
endpoint.bodyParameters ~= [param];
|
||||
endpoint.parameters ~= [param];
|
||||
}
|
||||
}
|
||||
|
||||
// Build the parameter structure.
|
||||
if ("parameters" in endpointNode && endpointNode["parameters"].length > 0) {
|
||||
foreach (ref const Node yamlParameter; endpointNode["parameters"]) {
|
||||
|
@ -488,18 +538,19 @@ void generateFileForEndpoints(ref const Node[] endpointNodes,
|
|||
EndpointController endpointController = new EndpointController();
|
||||
endpointController.className = name.applyCasePolicy(OPENAPI_CASING, CPP_CLASS_CASING);
|
||||
endpointController.endpoint = endpoint;
|
||||
endpointController.operation = endpoint.operation.applyCasePolicy(CasePolicy.CAMEL, CasePolicy.PASCAL);
|
||||
endpointControllers ~= [endpointController];
|
||||
}
|
||||
|
||||
|
||||
// Render templates
|
||||
class Controller {
|
||||
struct Controller {
|
||||
EndpointController[] endpoints;
|
||||
string supportNamespace = namespaceString!CPP_NAMESPACE_SUPPORT;
|
||||
string dtoNamespace = namespaceString!CPP_NAMESPACE_DTO;
|
||||
}
|
||||
|
||||
Controller controller = new Controller();
|
||||
Controller controller = Controller();
|
||||
controller.endpoints = endpointControllers[];
|
||||
|
||||
writeHeaderPreamble(headerFile, CPP_NAMESPACE_LOADER_HTTP, categoryName, systemImports, userImports);
|
||||
|
@ -1039,7 +1090,7 @@ class Endpoint {
|
|||
string description;
|
||||
|
||||
/// HTTP method for this endpoint
|
||||
string method;
|
||||
string operation;
|
||||
|
||||
/// List of all parameters for this request
|
||||
RequestParameter[] parameters = [];
|
||||
|
@ -1047,13 +1098,15 @@ class Endpoint {
|
|||
RequestParameter[] requiredPathParameters;
|
||||
RequestParameter[] requiredQueryParameters;
|
||||
RequestParameter[] optionalQueryParameters;
|
||||
RequestParameter[] bodyParameters;
|
||||
}
|
||||
|
||||
enum ParameterLocation {
|
||||
PATH,
|
||||
QUERY,
|
||||
COOKIE,
|
||||
HEADER
|
||||
HEADER,
|
||||
BODY
|
||||
}
|
||||
|
||||
class RequestParameter {
|
||||
|
@ -1062,6 +1115,8 @@ class RequestParameter {
|
|||
bool required;
|
||||
string description;
|
||||
MetaTypeInfo type;
|
||||
// Only for body parameters.
|
||||
string mimeType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1111,6 +1166,82 @@ unittest {
|
|||
assert(namespaceString!["foo", "bar"] == "foo::bar");
|
||||
}
|
||||
|
||||
|
||||
bool areMimesCompatible(string expectedMime, string mime) @safe nothrow {
|
||||
try {
|
||||
MimeType type1 = MimeType.parse(expectedMime);
|
||||
MimeType type2 = MimeType.parse(mime);
|
||||
return type1.compatible(type2);
|
||||
} catch(Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MimeParseException : Exception {
|
||||
mixin basicExceptionCtors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data structure representing a mime type.
|
||||
*/
|
||||
struct MimeType {
|
||||
public:
|
||||
|
||||
this(string type, string subtype, string suffix = null, string parameter = null) @safe {
|
||||
this.type = type;
|
||||
this.subtype = subtype;
|
||||
this.suffix = suffix;
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
string toString() const {
|
||||
Appender!string result = appender!string;
|
||||
result.reserve(type.length + subtype.length + (suffix ? suffix.length + 1 : 0) + (parameter ? parameter.length + 2 : 0));
|
||||
result ~= type;
|
||||
result ~= "/";
|
||||
result ~= subtype;
|
||||
if (suffix) {
|
||||
result ~= "+";
|
||||
result ~= suffix;
|
||||
}
|
||||
if (parameter) {
|
||||
result ~= "; ";
|
||||
result ~= parameter;
|
||||
}
|
||||
return result[];
|
||||
}
|
||||
|
||||
static MimeType parse(string mime) @safe {
|
||||
auto parts = mime.matchFirst(mimeRegex);
|
||||
enforce(parts, mime ~ " is not a valid mimetype");
|
||||
return MimeType(parts["type"], parts["subtype"], parts["suffix"], parts["parameter"]);
|
||||
}
|
||||
|
||||
static auto mimeRegex = regex(`(?P<type>\w*)\/(?P<subtype>(([\w\-]+\.)+)?[\w\-\*]+)(\+(?P<suffix>[\w\-\.]+))?(; (?P<parameter>[\w+-\.=]+))?`);
|
||||
|
||||
string type = null;
|
||||
string subtype = null;;
|
||||
string suffix = null;
|
||||
string parameter = null;
|
||||
|
||||
/**
|
||||
* Checks if two mime types are compatible.
|
||||
*
|
||||
* Two mime types are considered compatible if either one of the following
|
||||
* conditions is true:
|
||||
* 1. The mime types are exactly the same
|
||||
* 2. The expected mime type is in the form "x/*" and the mime type is in the form "x/y"
|
||||
* 3. The expected mime type is in the form "x/z+y" and the mime type is in the form "x/y"
|
||||
* 4. The expected mime type is in the form "x/y" and the mime type is in the form "x/z+y"
|
||||
*/
|
||||
bool compatible(const ref MimeType other) const @safe {
|
||||
if (type != other.type) return false;
|
||||
if (subtype == other.subtype || subtype == "*" || other.subtype == "*") return true;
|
||||
if (subtype == other.suffix || suffix == other.subtype) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given key from the node, if it does not exists, returns the or parameter.
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue