Added testing module, more docs, and response writing methods.
Build and Test Module / build-and-test (push) Successful in 5s Details

This commit is contained in:
Andrew Lalis 2025-07-01 22:08:44 -04:00
parent 799da3ff34
commit bb1d04cfa3
5 changed files with 157 additions and 1 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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"
} }
/** /**

View File

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