Added testing module, more docs, and response writing methods.
Build and Test Module / build-and-test (push) Successful in 5s
Details
Build and Test Module / build-and-test (push) Successful in 5s
Details
This commit is contained in:
parent
799da3ff34
commit
bb1d04cfa3
|
@ -1,3 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Defines the core request handler interface that's the starting point for
|
||||||
|
* all HTTP request processing.
|
||||||
|
*/
|
||||||
module handy_http_primitives.handler;
|
module handy_http_primitives.handler;
|
||||||
|
|
||||||
import handy_http_primitives.request;
|
import handy_http_primitives.request;
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
/**
|
||||||
|
* The handy_http_primitives module defines a set of primitive types and
|
||||||
|
* interfaces that are shared among all Handy-Http libraries, and form the
|
||||||
|
* basis of how requests are handled.
|
||||||
|
*/
|
||||||
module handy_http_primitives;
|
module handy_http_primitives;
|
||||||
|
|
||||||
public import handy_http_primitives.request;
|
public import handy_http_primitives.request;
|
||||||
|
@ -6,3 +11,4 @@ public import handy_http_primitives.handler;
|
||||||
public import handy_http_primitives.optional;
|
public import handy_http_primitives.optional;
|
||||||
public import handy_http_primitives.multivalue_map;
|
public import handy_http_primitives.multivalue_map;
|
||||||
public import handy_http_primitives.builder;
|
public import handy_http_primitives.builder;
|
||||||
|
public import handy_http_primitives.testing;
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Defines the request structure and associated types that are generally used
|
||||||
|
* when dealing with a client's HTTP request.
|
||||||
|
*/
|
||||||
module handy_http_primitives.request;
|
module handy_http_primitives.request;
|
||||||
|
|
||||||
import streams;
|
import streams;
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Defines the HTTP response structure and associated types that are generally
|
||||||
|
* used when formulating a response to a client's request.
|
||||||
|
*/
|
||||||
module handy_http_primitives.response;
|
module handy_http_primitives.response;
|
||||||
|
|
||||||
import streams : OutputStream;
|
import streams : OutputStream;
|
||||||
|
@ -15,6 +19,40 @@ struct ServerHttpResponse {
|
||||||
StringMultiValueMap headers;
|
StringMultiValueMap headers;
|
||||||
/// The stream to which the response body is written.
|
/// The stream to which the response body is written.
|
||||||
OutputStream!ubyte outputStream;
|
OutputStream!ubyte outputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes an array of bytes to the response's output stream.
|
||||||
|
* Params:
|
||||||
|
* bytes = The bytes to write.
|
||||||
|
* contentType = The declared content type of the data, which is written
|
||||||
|
* as the "Content-Type" header.
|
||||||
|
*/
|
||||||
|
void writeBodyBytes(ubyte[] bytes, string contentType = ContentTypes.APPLICATION_OCTET_STREAM) {
|
||||||
|
import std.conv : to;
|
||||||
|
headers.add("Content-Type", contentType);
|
||||||
|
headers.add("Content-Length", to!string(bytes.length));
|
||||||
|
// We trust that when we write to the output stream, the transport
|
||||||
|
// implementation will handle properly formatting the headers and other
|
||||||
|
// HTTP boilerplate response content prior to actually writing the body.
|
||||||
|
auto result = outputStream.writeToStream(bytes);
|
||||||
|
if (result.hasError) {
|
||||||
|
throw new Exception(
|
||||||
|
"Failed to write bytes to the response's output stream: " ~
|
||||||
|
cast(string) result.error.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a string of content to the response's output stream.
|
||||||
|
* Params:
|
||||||
|
* content = The content to write.
|
||||||
|
* contentType = The declared content type of the data, which is written
|
||||||
|
* as the "Content-Type" header.
|
||||||
|
*/
|
||||||
|
void writeBodyString(string content, string contentType = ContentTypes.TEXT_PLAIN) {
|
||||||
|
writeBodyBytes(cast(ubyte[]) content, contentType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,10 +144,19 @@ enum HttpStatus : StatusInfo {
|
||||||
enum ContentTypes : string {
|
enum ContentTypes : string {
|
||||||
APPLICATION_JSON = "application/json",
|
APPLICATION_JSON = "application/json",
|
||||||
APPLICATION_XML = "application/xml",
|
APPLICATION_XML = "application/xml",
|
||||||
|
APPLICATION_OCTET_STREAM = "application/octet-stream",
|
||||||
|
APPLICATION_PDF = "application/pdf",
|
||||||
|
|
||||||
TEXT_PLAIN = "text/plain",
|
TEXT_PLAIN = "text/plain",
|
||||||
TEXT_HTML = "text/html",
|
TEXT_HTML = "text/html",
|
||||||
TEXT_CSS = "text/css"
|
TEXT_CSS = "text/css",
|
||||||
|
TEXT_CSV = "text/csv",
|
||||||
|
TEXT_JAVASCRIPT = "text/javascript",
|
||||||
|
TEXT_MARKDOWN = "text/markdown",
|
||||||
|
|
||||||
|
IMAGE_JPEG = "image/jpeg",
|
||||||
|
IMAGE_PNG = "image/png",
|
||||||
|
IMAGE_SVG = "image/svg+xml"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/**
|
||||||
|
* The testing module defines helper methods for testing your HTTP handling
|
||||||
|
* code.
|
||||||
|
*/
|
||||||
|
module handy_http_primitives.testing;
|
||||||
|
|
||||||
|
import handy_http_primitives.response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given response's status matches an expected status.
|
||||||
|
* Params:
|
||||||
|
* response = The response to check.
|
||||||
|
* expectedStatus = The expected status that the response should have.
|
||||||
|
*/
|
||||||
|
void assertStatus(in ServerHttpResponse response, in StatusInfo expectedStatus) {
|
||||||
|
import std.format : format;
|
||||||
|
assert(
|
||||||
|
expectedStatus == response.status,
|
||||||
|
format!"The HTTP response's status of %d (%s) didn't match the expected status %d (%s)."(
|
||||||
|
response.status.code,
|
||||||
|
response.status.text,
|
||||||
|
expectedStatus.code,
|
||||||
|
expectedStatus.text
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
import handy_http_primitives.builder;
|
||||||
|
ServerHttpResponseBuilder()
|
||||||
|
.withStatus(HttpStatus.OK)
|
||||||
|
.build()
|
||||||
|
.assertStatus(HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some common status assertions:
|
||||||
|
|
||||||
|
void assertStatusOk(in ServerHttpResponse response) {
|
||||||
|
assertStatus(response, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertStatusNotFound(in ServerHttpResponse response) {
|
||||||
|
assertStatus(response, HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertStatusBadRequest(in ServerHttpResponse response) {
|
||||||
|
assertStatus(response, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertStatusUnauthorized(in ServerHttpResponse response) {
|
||||||
|
assertStatus(response, HttpStatus.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertStatusForbidden(in ServerHttpResponse response) {
|
||||||
|
assertStatus(response, HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertStatusInternalServerError(in ServerHttpResponse response) {
|
||||||
|
assertStatus(response, HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given response has a header with a given value.
|
||||||
|
* Params:
|
||||||
|
* response = The response to check.
|
||||||
|
* header = The name of the header to check the value of.
|
||||||
|
* expectedValue = The expected value of the header.
|
||||||
|
*/
|
||||||
|
void assertHasHeader(in ServerHttpResponse response, string header, string expectedValue) {
|
||||||
|
import std.format : format;
|
||||||
|
assert(
|
||||||
|
response.headers.contains(header),
|
||||||
|
format!"The HTTP response doesn't have a header named \"%s\"."(header)
|
||||||
|
);
|
||||||
|
string value = response.headers.getFirst(header).orElseThrow();
|
||||||
|
assert(
|
||||||
|
value == expectedValue,
|
||||||
|
format!"The HTTP response's header \"%s\" with value \"%s\" didn't match the expected value \"%s\"."(
|
||||||
|
header,
|
||||||
|
value,
|
||||||
|
expectedValue
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
import streams;
|
||||||
|
import handy_http_primitives.builder;
|
||||||
|
ArrayOutputStream!ubyte bufferOut = byteArrayOutputStream();
|
||||||
|
ServerHttpResponse r1 = ServerHttpResponseBuilder()
|
||||||
|
.withOutputStream(&bufferOut)
|
||||||
|
.build();
|
||||||
|
r1.writeBodyString("Hello, world!");
|
||||||
|
r1.assertHasHeader("Content-Type", ContentTypes.TEXT_PLAIN);
|
||||||
|
}
|
Loading…
Reference in New Issue