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 ); }