128 lines
3.7 KiB
D
128 lines
3.7 KiB
D
module util.data;
|
|
|
|
import handy_http_primitives;
|
|
import handy_http_data.multipart;
|
|
import std.typecons;
|
|
|
|
Optional!T toOptional(T)(Nullable!T value) {
|
|
if (value.isNull) {
|
|
return Optional!T.empty;
|
|
} else {
|
|
return Optional!T.of(value.get);
|
|
}
|
|
}
|
|
|
|
Nullable!T toNullable(T)(Optional!T value) {
|
|
if (value.isNull) {
|
|
return Nullable!(T)();
|
|
} else {
|
|
return Nullable!T(value.value);
|
|
}
|
|
}
|
|
|
|
auto serializeOptional(T)(Optional!T value) {
|
|
if (value.isNull) {
|
|
return Nullable!T();
|
|
}
|
|
return Nullable!T(value.value);
|
|
}
|
|
|
|
ulong getPathParamOrThrow(T = ulong)(in ServerHttpRequest req, string name) {
|
|
import handy_http_handlers.path_handler;
|
|
import std.conv;
|
|
foreach (param; getPathParams(req)) {
|
|
if (param.name == name) {
|
|
try {
|
|
return param.value.to!T;
|
|
} catch (ConvException e) {
|
|
// Skip and throw if no params match.
|
|
}
|
|
}
|
|
}
|
|
// No params matched, so throw a NOT FOUND error.
|
|
throw new HttpStatusException(HttpStatus.NOT_FOUND, "Missing required path parameter \"" ~ name ~ "\".");
|
|
}
|
|
|
|
struct MultipartFile {
|
|
string name;
|
|
string contentType;
|
|
ubyte[] content;
|
|
}
|
|
|
|
struct MultipartFilesAndBody(T) {
|
|
T payload;
|
|
MultipartFile[] files;
|
|
}
|
|
|
|
MultipartFilesAndBody!T parseMultipartFilesAndBody(T)(
|
|
ref ServerHttpRequest request,
|
|
string payloadPartName = "payload",
|
|
string filesPartName = "file"
|
|
) {
|
|
import asdf : deserialize, SerdeException;
|
|
import std.uni : toLower;
|
|
import std.array;
|
|
|
|
MultipartFormData formData;
|
|
try {
|
|
formData = readBodyAsMultipartFormData(request);
|
|
} catch (MultipartFormatException e) {
|
|
throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Invalid multipart form data.");
|
|
}
|
|
|
|
MultipartFilesAndBody!T result;
|
|
RefAppender!(MultipartFile[]) app = appender(&result.files);
|
|
foreach (element; formData.elements) {
|
|
if (toLower(element.name) == payloadPartName) {
|
|
try {
|
|
result.payload = deserialize!T(element.content);
|
|
} catch (SerdeException e) {
|
|
throw new HttpStatusException(HttpStatus.BAD_REQUEST, e.msg);
|
|
}
|
|
} else if (toLower(element.name) == filesPartName) {
|
|
app ~= parseMultipartFile(element);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private MultipartFile parseMultipartFile(in MultipartElement e) {
|
|
import std.algorithm : splitter, count;
|
|
import std.array;
|
|
import std.string : strip;
|
|
const requiredHeaders = ["Content-Disposition", "Content-Type"];
|
|
static foreach (headerName; requiredHeaders) {
|
|
if (headerName !in e.headers || e.headers[headerName].length < 1) {
|
|
throw new HttpStatusException(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Missing required \"" ~ headerName ~ "\" header for multipart file."
|
|
);
|
|
}
|
|
}
|
|
if ("Content-Disposition" !in e.headers) {
|
|
throw new HttpStatusException(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Missing required \"Content-Disposition\" header for multipart file."
|
|
);
|
|
}
|
|
string filename;
|
|
foreach (part; e.headers["Content-Disposition"].splitter(";")) {
|
|
string[] partSegments = part.splitter("=").array;
|
|
if (count(partSegments) == 2 && partSegments[0].strip() == "filename") {
|
|
filename = partSegments[1].strip().strip("\"");
|
|
break;
|
|
}
|
|
}
|
|
if (filename is null || filename.length < 1) {
|
|
throw new HttpStatusException(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Missing filename for multipart file."
|
|
);
|
|
}
|
|
return MultipartFile(
|
|
filename,
|
|
e.headers["Content-Type"],
|
|
cast(ubyte[]) e.content
|
|
);
|
|
}
|