Added builder module, made client address base types non-const.
Build and Test Module / build-and-test (push) Successful in 6s Details

This commit is contained in:
Andrew Lalis 2025-03-26 14:31:18 -04:00
parent 79261ce20a
commit 799da3ff34
3 changed files with 168 additions and 9 deletions

View File

@ -8,8 +8,8 @@ module handy_http_primitives.address;
* that the connection was assigned to.
*/
struct IPv4InternetAddress {
const ubyte[4] bytes;
const ushort port;
ubyte[4] bytes;
ushort port;
}
/**
@ -17,8 +17,8 @@ struct IPv4InternetAddress {
* machine that the connection was assigned to.
*/
struct IPv6InternetAddress {
const ubyte[16] bytes;
const ushort port;
ubyte[16] bytes;
ushort port;
}
/**
@ -26,7 +26,7 @@ struct IPv6InternetAddress {
* IO operations take place.
*/
struct UnixSocketAddress {
const string path;
string path;
}
/// Defines the different possible address types, used by `ClientAddress`.
@ -42,10 +42,10 @@ enum ClientAddressType {
* request. Use `type` to determine which information is available.
*/
struct ClientAddress {
const ClientAddressType type;
const IPv4InternetAddress ipv4InternetAddress;
const IPv6InternetAddress ipv6InternetAddress;
const UnixSocketAddress unixSocketAddress;
ClientAddressType type;
IPv4InternetAddress ipv4InternetAddress;
IPv6InternetAddress ipv6InternetAddress;
UnixSocketAddress unixSocketAddress;
/**
* Serializes this address in a human-readable string representation.

View File

@ -0,0 +1,158 @@
/**
* Defines builder types to more easily construct various HTTP objects, often
* useful for testing scenarios.
*/
module handy_http_primitives.builder;
import streams;
import handy_http_primitives.request;
import handy_http_primitives.response;
import handy_http_primitives.address;
import handy_http_primitives.multivalue_map;
/**
* Fluent interface for building ServerHttpRequest objects.
*/
struct ServerHttpRequestBuilder {
HttpVersion httpVersion = HttpVersion.V1;
ClientAddress clientAddress = ClientAddress.unknown;
string method = HttpMethod.GET;
string url = "";
string[][string] headers;
QueryParameter[] queryParams;
InputStream!ubyte inputStream = inputStreamObjectFor(NoOpInputStream!ubyte());
ref withVersion(HttpVersion httpVersion) {
this.httpVersion = httpVersion;
return this;
}
ref withClientAddress(ClientAddress addr) {
this.clientAddress = addr;
return this;
}
ref withMethod(string method) {
this.method = method;
return this;
}
ref withUrl(string url) {
this.url = url;
return this;
}
ref withHeader(string headerName, string value) {
if (headerName !in this.headers) {
this.headers[headerName] = [];
}
this.headers[headerName] ~= value;
return this;
}
ref withQueryParam(string paramName, string value) {
foreach (ref param; this.queryParams) {
if (param.key == paramName) {
param.values ~= value;
return this;
}
}
this.queryParams ~= QueryParameter(paramName, [value]);
return this;
}
ref withInputStream(S)(S stream) if (isByteInputStream!S) {
this.inputStream = inputStreamObjectFor(stream);
return this;
}
ref withBody(ubyte[] bodyBytes) {
return withInputStream(arrayInputStreamFor(bodyBytes));
}
ref withBody(string bodyStr) {
return withBody(cast(ubyte[]) bodyStr);
}
ServerHttpRequest build() {
return ServerHttpRequest(
httpVersion,
clientAddress,
method,
url,
headers,
queryParams,
inputStream
);
}
}
unittest {
ServerHttpRequest r1 = ServerHttpRequestBuilder()
.withUrl("/test-url")
.withVersion(HttpVersion.V2)
.withMethod(HttpMethod.PATCH)
.withBody("Hello world!")
.withClientAddress(ClientAddress.ofUnixSocket(UnixSocketAddress("/tmp/socket")))
.withHeader("Content-Type", "text/plain")
.withHeader("Content-Length", "12")
.withQueryParam("idx", "42")
.build();
assert(r1.httpVersion == HttpVersion.V2);
assert(r1.url == "/test-url");
assert(r1.method == HttpMethod.PATCH);
string r1Body = r1.readBodyAsString();
assert(r1Body == "Hello world!");
assert(r1.clientAddress.type == ClientAddressType.UNIX);
assert(r1.clientAddress.unixSocketAddress.path == "/tmp/socket");
assert(r1.getHeaderAs!string("Content-Type") == "text/plain");
assert(r1.getHeaderAs!ulong("Content-Length") == 12);
assert(r1.getParamAs!ulong("idx") == 42);
}
/**
* Fluent interface for building ServerHttpResponse objects.
*/
struct ServerHttpResponseBuilder {
StatusInfo initialStatus = HttpStatus.OK;
StringMultiValueMap initialHeaders;
OutputStream!ubyte outputStream = outputStreamObjectFor(NoOpOutputStream!ubyte());
ref withStatus(StatusInfo status) {
this.initialStatus = status;
return this;
}
ref withHeader(string headerName, string value) {
this.initialHeaders.add(headerName, value);
return this;
}
ref withOutputStream(S)(S stream) if (isByteOutputStream!S) {
this.outputStream = outputStreamObjectFor(stream);
return this;
}
ServerHttpResponse build() {
return ServerHttpResponse(
initialStatus,
initialHeaders,
outputStream
);
}
}
unittest {
ArrayOutputStream!ubyte bufferOut = byteArrayOutputStream();
ServerHttpResponse r1 = ServerHttpResponseBuilder()
.withStatus(HttpStatus.BAD_REQUEST)
.withHeader("Test", "okay")
.withOutputStream(&bufferOut)
.build();
assert(r1.status == HttpStatus.BAD_REQUEST);
assert(r1.headers.getFirst("Test").value == "okay");
r1.outputStream.writeToStream(cast(ubyte[]) "Hello world!");
assert(bufferOut.toArray() == cast(ubyte[]) "Hello world!");
}

View File

@ -5,3 +5,4 @@ public import handy_http_primitives.response;
public import handy_http_primitives.handler;
public import handy_http_primitives.optional;
public import handy_http_primitives.multivalue_map;
public import handy_http_primitives.builder;