Added initial types.
This commit is contained in:
parent
3c04644e8b
commit
eae8fd6c0e
|
@ -0,0 +1,16 @@
|
||||||
|
.dub
|
||||||
|
docs.json
|
||||||
|
__dummy.html
|
||||||
|
docs/
|
||||||
|
/handy-http-primitives
|
||||||
|
handy-http-primitives.so
|
||||||
|
handy-http-primitives.dylib
|
||||||
|
handy-http-primitives.dll
|
||||||
|
handy-http-primitives.a
|
||||||
|
handy-http-primitives.lib
|
||||||
|
handy-http-primitives-test-*
|
||||||
|
*.exe
|
||||||
|
*.pdb
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.lst
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"Andrew Lalis"
|
||||||
|
],
|
||||||
|
"copyright": "Copyright © 2024, Andrew Lalis",
|
||||||
|
"dependencies": {
|
||||||
|
"streams": "~>3.5.0"
|
||||||
|
},
|
||||||
|
"description": "Basic HTTP types that can be shared among various projects.",
|
||||||
|
"license": "MIT",
|
||||||
|
"name": "handy-http-primitives"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"fileVersion": 1,
|
||||||
|
"versions": {
|
||||||
|
"streams": "3.5.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
module handy_http_primitives.handler;
|
||||||
|
|
||||||
|
import handy_http_primitives.request;
|
||||||
|
import handy_http_primitives.response;
|
||||||
|
|
||||||
|
interface HttpRequestHandler {
|
||||||
|
void handle(in HttpRequest request, ref HttpResponse response);
|
||||||
|
}
|
|
@ -0,0 +1,448 @@
|
||||||
|
/**
|
||||||
|
* An implementation of a multi-valued mapping, where one key may map to one
|
||||||
|
* or more values.
|
||||||
|
*/
|
||||||
|
module handy_http_primitives.multivalue_map;
|
||||||
|
|
||||||
|
import handy_http_primitives.optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A multi-valued mapping, where a key is mapped to one or more values. The map
|
||||||
|
* is sorted by keys for O(log(n)) lookup and retrieval, and O(n*log(n))
|
||||||
|
* insertion.
|
||||||
|
*
|
||||||
|
* `KeyType` is the type used for the map's keys.
|
||||||
|
* `ValueType` is the type used for the map's values.
|
||||||
|
* `KeySort` is a function that takes two operands and returns true if the
|
||||||
|
* first one is less than the second.
|
||||||
|
* `KeyEquals` is a function that takes two operands and returns true if they
|
||||||
|
* are equal.
|
||||||
|
*/
|
||||||
|
struct MultiValueMap(
|
||||||
|
KeyType,
|
||||||
|
ValueType,
|
||||||
|
alias KeySort = (a, b) => a < b,
|
||||||
|
alias KeyEquals = (a, b) => a == b
|
||||||
|
) {
|
||||||
|
/// The internal structure used to store each key and set of values.
|
||||||
|
static struct Entry {
|
||||||
|
/// The key for this entry.
|
||||||
|
KeyType key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of values associated with this entry's key. This always
|
||||||
|
* contains at least one value.
|
||||||
|
*/
|
||||||
|
ValueType[] values;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a human-readable string representation of this entry.
|
||||||
|
* Returns: A string representation of this entry.
|
||||||
|
*/
|
||||||
|
string toString() const {
|
||||||
|
import std.conv : to;
|
||||||
|
import std.algorithm : map, joiner;
|
||||||
|
import std.array : array;
|
||||||
|
|
||||||
|
string keyStr = key.to!string;
|
||||||
|
string valuesStr = values
|
||||||
|
.map!(v => "\""~v.to!string~"\"")
|
||||||
|
.joiner(", ").array.to!string;
|
||||||
|
return "\"" ~ keyStr ~ "\": " ~ valuesStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The internal, sorted array of entries.
|
||||||
|
private Entry[] entries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the index of the entry with a given key in the internal array.
|
||||||
|
* Params:
|
||||||
|
* k = The key to search for.
|
||||||
|
* Returns: The index if it was found, or -1 if it doesn't exist.
|
||||||
|
*/
|
||||||
|
private long indexOf(KeyType k) const {
|
||||||
|
if (entries.length == 0) return -1;
|
||||||
|
if (entries.length == 1) {
|
||||||
|
return entries[0].key == k ? 0 : -1;
|
||||||
|
}
|
||||||
|
size_t startIdx = 0;
|
||||||
|
size_t endIdx = entries.length - 1;
|
||||||
|
while (startIdx <= endIdx) {
|
||||||
|
size_t mid = startIdx + (endIdx - startIdx) / 2;
|
||||||
|
const key = entries[mid].key;
|
||||||
|
if (KeyEquals(key, k)) return mid;
|
||||||
|
if (KeySort(entries[mid].key, k)) {
|
||||||
|
startIdx = mid + 1;
|
||||||
|
} else {
|
||||||
|
if (mid == 0) return -1;
|
||||||
|
endIdx = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to get the entry for a given key. Complexity is O(log(keyCount)).
|
||||||
|
* Params:
|
||||||
|
* k = The key to look for.
|
||||||
|
* Returns: An optional that may contain the entry that was found.
|
||||||
|
*/
|
||||||
|
private Optional!Entry getEntry(KeyType k) {
|
||||||
|
long idx = indexOf(k);
|
||||||
|
if (idx == -1) return Optional!Entry.empty();
|
||||||
|
return Optional!Entry.of(entries[cast(size_t) idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number of unique keys in this map.
|
||||||
|
* Returns: The number of unique keys in this map.
|
||||||
|
*/
|
||||||
|
size_t length() const {
|
||||||
|
return entries.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if this map contains a value for the given key.
|
||||||
|
* Params:
|
||||||
|
* k = The key to search for.
|
||||||
|
* Returns: True if at least one value exists for the given key.
|
||||||
|
*/
|
||||||
|
bool contains(KeyType k) const {
|
||||||
|
return indexOf(k) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of all keys in this map, allocated in a new array.
|
||||||
|
* Returns: The list of keys in this map.
|
||||||
|
*/
|
||||||
|
KeyType[] keys() const {
|
||||||
|
KeyType[] keysArray = new KeyType[this.length()];
|
||||||
|
foreach (size_t i, const Entry e; entries) {
|
||||||
|
keysArray[i] = e.key;
|
||||||
|
}
|
||||||
|
return keysArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all values associated with a given key, allocated in a new array.
|
||||||
|
* Params:
|
||||||
|
* k = The key to get the values of.
|
||||||
|
* Returns: The values associated with the given key, or an empty array if
|
||||||
|
* no values exist for the key.
|
||||||
|
*/
|
||||||
|
ValueType[] getAll(KeyType k) const {
|
||||||
|
long idx = indexOf(k);
|
||||||
|
if (idx == -1) return [];
|
||||||
|
return entries[cast(size_t) idx].values.dup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the first value associated with a given key, as per the order in
|
||||||
|
* which the values were inserted.
|
||||||
|
* Params:
|
||||||
|
* k = The key to get the first value of.
|
||||||
|
* Returns: An optional contains the value, if there is at least one value
|
||||||
|
* for the given key.
|
||||||
|
*/
|
||||||
|
Optional!ValueType getFirst(KeyType k) const {
|
||||||
|
long idx = indexOf(k);
|
||||||
|
if (idx == -1) return Optional!ValueType.empty();
|
||||||
|
return Optional!ValueType.of(entries[cast(size_t) idx].values[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a single key -> value pair to the map, with time complexity of
|
||||||
|
* O(n*log(n)) due to sorting the new entry by its key.
|
||||||
|
* Params:
|
||||||
|
* k = The key.
|
||||||
|
* v = The value associated with the key.
|
||||||
|
*/
|
||||||
|
void add(KeyType k, ValueType v) {
|
||||||
|
long idx = indexOf(k);
|
||||||
|
if (idx == -1) {
|
||||||
|
entries ~= Entry(k, [v]);
|
||||||
|
import std.algorithm.sorting : sort;
|
||||||
|
sort!((a, b) => KeySort(a.key, b.key))(entries);
|
||||||
|
} else {
|
||||||
|
entries[cast(size_t) idx].values ~= v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears this map of all values.
|
||||||
|
*/
|
||||||
|
void clear() {
|
||||||
|
entries.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a key from the map, thus removing all values associated with
|
||||||
|
* that key.
|
||||||
|
* Params:
|
||||||
|
* k = The key to remove.
|
||||||
|
*/
|
||||||
|
void remove(KeyType k) {
|
||||||
|
long idx = indexOf(k);
|
||||||
|
if (idx == -1) return;
|
||||||
|
if (entries.length == 1) {
|
||||||
|
clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (idx + 1 < entries.length) {
|
||||||
|
const i = cast(size_t) idx;
|
||||||
|
entries[i .. $ - 1] = entries[i + 1 .. $];
|
||||||
|
}
|
||||||
|
entries.length = entries.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets this multivalue map as an associative array, where each key is
|
||||||
|
* mapped to a list of values.
|
||||||
|
* Returns: The associative array.
|
||||||
|
*/
|
||||||
|
ValueType[][KeyType] asAssociativeArray() const {
|
||||||
|
ValueType[][KeyType] aa;
|
||||||
|
foreach (const Entry entry; entries) {
|
||||||
|
aa[entry.key] = entry.values.dup;
|
||||||
|
}
|
||||||
|
return aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a multivalued map from an associative array.
|
||||||
|
* Params:
|
||||||
|
* aa = The associative array to use.
|
||||||
|
* Returns: The multivalued map.
|
||||||
|
*/
|
||||||
|
static MultiValueMap!(KeyType, ValueType, KeySort) fromAssociativeArray(ValueType[][KeyType] aa) {
|
||||||
|
MultiValueMap!(KeyType, ValueType, KeySort) m;
|
||||||
|
foreach (KeyType k, ValueType[] values; aa) {
|
||||||
|
foreach (ValueType v; values) {
|
||||||
|
m.add(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a multivalued map from an associative array of single values.
|
||||||
|
* Params:
|
||||||
|
* aa = The associative array to use.
|
||||||
|
* Returns: The multivalued map.
|
||||||
|
*/
|
||||||
|
static MultiValueMap!(KeyType, ValueType, KeySort) fromAssociativeArray(ValueType[KeyType] aa) {
|
||||||
|
MultiValueMap!(KeyType, ValueType, KeySort) m;
|
||||||
|
foreach (KeyType k, ValueType v; aa) {
|
||||||
|
m.add(k, v);
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An efficient builder that can be used to construct a multivalued map
|
||||||
|
* with successive `add` calls, which is more efficient than doing so
|
||||||
|
* directly due to the builder's deferred sorting.
|
||||||
|
*/
|
||||||
|
static struct Builder {
|
||||||
|
import std.array;
|
||||||
|
|
||||||
|
private MultiValueMap m;
|
||||||
|
private RefAppender!(Entry[]) entryAppender;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a key -> value pair to the builder's map.
|
||||||
|
* Params:
|
||||||
|
* k = The key.
|
||||||
|
* v = The value associated with the key.
|
||||||
|
* Returns: A reference to the builder, for method chaining.
|
||||||
|
*/
|
||||||
|
ref Builder add(KeyType k, ValueType v) {
|
||||||
|
if (entryAppender.data is null) entryAppender = appender(&m.entries);
|
||||||
|
long idx = this.indexOf(k);
|
||||||
|
if (idx == -1) {
|
||||||
|
entryAppender ~= Entry(k, [v]);
|
||||||
|
} else {
|
||||||
|
m.entries[cast(size_t) idx].values ~= v;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the multivalued map.
|
||||||
|
* Returns: The map that was created.
|
||||||
|
*/
|
||||||
|
MultiValueMap build() {
|
||||||
|
if (m.entries.length == 0) return m;
|
||||||
|
import std.algorithm.sorting : sort;
|
||||||
|
sort!((a, b) => KeySort(a.key, b.key))(m.entries);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long indexOf(KeyType k) {
|
||||||
|
foreach (i, entry; m.entries) {
|
||||||
|
if (KeyEquals(entry.key, k)) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPERATOR OVERLOADS below here
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the empty index operator, which just returns the entire list
|
||||||
|
* of entries in this map.
|
||||||
|
* Returns: The list of entries in this map.
|
||||||
|
*/
|
||||||
|
inout(Entry)[] opIndex() inout {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience overload to get the first value for a given key. Note: this
|
||||||
|
* will throw an exception if no values exist for the given key. To avoid
|
||||||
|
* this, use `getFirst` and deal with the missing value yourself.
|
||||||
|
* Params:
|
||||||
|
* key = The key to get the value of.
|
||||||
|
* Returns: The first value for the given key.
|
||||||
|
*/
|
||||||
|
ValueType opIndex(KeyType key) const {
|
||||||
|
import std.conv : to;
|
||||||
|
return getFirst(key).orElseThrow("No values exist for key " ~ key.to!string ~ ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `opApply` implementation to allow iterating over this map by all pairs
|
||||||
|
* of keys and values.
|
||||||
|
* Params:
|
||||||
|
* dg = The foreach body that uses each key -> value pair.
|
||||||
|
* Returns: The result of the delegate call.
|
||||||
|
*/
|
||||||
|
int opApply(int delegate(const ref KeyType, const ref ValueType) dg) const {
|
||||||
|
int result = 0;
|
||||||
|
foreach (const Entry entry; entries) {
|
||||||
|
foreach (ValueType value; entry.values) {
|
||||||
|
result = dg(entry.key, value);
|
||||||
|
if (result) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements opBinaryRight for the "in" operator, such that `k in m` will
|
||||||
|
* resolve to the list of values for key `k` in the multivalue map `m` if
|
||||||
|
* that key exists, or `null` if not.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* lhs = The key to use.
|
||||||
|
* Returns: A list of values for the given key, or null if no such key exists.
|
||||||
|
* ---
|
||||||
|
* StringMultiValueMap m;
|
||||||
|
* m.add("a", "hello");
|
||||||
|
* assert("a" in m);
|
||||||
|
* assert(("a" in m) == ["hello"]);
|
||||||
|
* assert("b" !in m);
|
||||||
|
* assert(("b" in m) is null);
|
||||||
|
* ---
|
||||||
|
*/
|
||||||
|
ValueType[] opBinaryRight(string op : "in")(string lhs) {
|
||||||
|
Optional!Entry optionalEntry = this.getEntry(lhs);
|
||||||
|
if (optionalEntry) {
|
||||||
|
Entry entry = optionalEntry.value;
|
||||||
|
return entry.values;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this map into a human-readable string which lists each key and
|
||||||
|
* all of the values for that key.
|
||||||
|
* Returns: A string representation of this map.
|
||||||
|
*/
|
||||||
|
string toString() const {
|
||||||
|
import std.format : format;
|
||||||
|
return format!"%(%s\n%)"(entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A multivalued map of strings, where each string key refers to zero or more
|
||||||
|
* string values. All keys are case-sensitive.
|
||||||
|
*/
|
||||||
|
alias StringMultiValueMap = MultiValueMap!(string, string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A multivalued map of strings, where keys are NOT case sensitive.
|
||||||
|
*/
|
||||||
|
alias CaseInsensitiveStringMultiValueMap = MultiValueMap!(
|
||||||
|
string, string,
|
||||||
|
(a, b) => a < b,
|
||||||
|
(a, b) {
|
||||||
|
import std.string : toLower;
|
||||||
|
return toLower(a) == toLower(b);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
StringMultiValueMap m;
|
||||||
|
m.add("a", "hello");
|
||||||
|
assert(m.getFirst("a").orElseThrow == "hello");
|
||||||
|
m.add("b", "bye");
|
||||||
|
assert(m.getFirst("b").orElseThrow == "bye");
|
||||||
|
assert(m.asAssociativeArray == ["a": ["hello"], "b": ["bye"]]);
|
||||||
|
assert(m["b"] == "bye");
|
||||||
|
m.remove("a");
|
||||||
|
assert(!m.contains("a"));
|
||||||
|
m.add("b", "hello");
|
||||||
|
assert(m.getAll("b") == ["bye", "hello"]);
|
||||||
|
m.clear();
|
||||||
|
assert(m.length == 0);
|
||||||
|
assert(!m.contains("a"));
|
||||||
|
assert(!m.contains("b"));
|
||||||
|
|
||||||
|
auto m2 = StringMultiValueMap.fromAssociativeArray(["a": "123", "b": "abc"]);
|
||||||
|
assert(m2["a"] == "123");
|
||||||
|
assert(m2["b"] == "abc");
|
||||||
|
|
||||||
|
auto m3 = StringMultiValueMap.fromAssociativeArray(["a": [""], "b": [""], "c": ["hello"]]);
|
||||||
|
assert(m3.contains("a"));
|
||||||
|
assert(m3["a"] == "");
|
||||||
|
assert(m3.contains("b"));
|
||||||
|
assert(m3["b"] == "");
|
||||||
|
assert(m3.contains("c"));
|
||||||
|
assert(m3["c"] == "hello");
|
||||||
|
|
||||||
|
// Test that opApply works:
|
||||||
|
int n = 0;
|
||||||
|
foreach (key, value; m3) {
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
assert(n == 3);
|
||||||
|
|
||||||
|
// Test opBinaryRight with "in" operator.
|
||||||
|
StringMultiValueMap m4;
|
||||||
|
m4.add("a", "1");
|
||||||
|
assert("a" in m4);
|
||||||
|
assert("b" !in m4);
|
||||||
|
auto valuesA = "a" in m4;
|
||||||
|
assert(valuesA == ["1"]);
|
||||||
|
auto valuesB = "b" in m4;
|
||||||
|
assert(valuesB is null);
|
||||||
|
|
||||||
|
// Test opIndex with an empty index.
|
||||||
|
StringMultiValueMap m5;
|
||||||
|
assert(m5[] == []);
|
||||||
|
m5.add("a", "123");
|
||||||
|
assert(m5[] == [StringMultiValueMap.Entry("a", ["123"])]);
|
||||||
|
|
||||||
|
// test on a const instance
|
||||||
|
const(StringMultiValueMap) m6 = m5;
|
||||||
|
assert(m6[] == [StringMultiValueMap.Entry("a", ["123"])]);
|
||||||
|
|
||||||
|
// test the builder with multi-values
|
||||||
|
StringMultiValueMap.Builder builder;
|
||||||
|
builder.add("a", "123");
|
||||||
|
builder.add("a", "456");
|
||||||
|
assert(builder.build()[] == [StringMultiValueMap.Entry("a", ["123", "456"])], builder.build().toString);
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
/**
|
||||||
|
* Module that defines an `Optional` type, which is a simplified version of
|
||||||
|
* Phobos' Nullable, that also supports mapping the underlying data.
|
||||||
|
*/
|
||||||
|
module handy_http_primitives.optional;
|
||||||
|
|
||||||
|
import std.typecons : Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple wrapper around a value to make it optionally present.
|
||||||
|
*/
|
||||||
|
struct Optional(T) {
|
||||||
|
/// The internal value of this optional.
|
||||||
|
T value;
|
||||||
|
|
||||||
|
/// Whether this optional is empty.
|
||||||
|
bool isNull = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an optional value using a given value.
|
||||||
|
* Params:
|
||||||
|
* value = The value to use.
|
||||||
|
* Returns: An optional that contains the given value.
|
||||||
|
*/
|
||||||
|
static Optional!T of(T value) {
|
||||||
|
return Optional!T(value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an optional value using a Phobos nullable.
|
||||||
|
* Params:
|
||||||
|
* nullableValue = The nullable value to use.
|
||||||
|
* Returns: An optional that contains the given nullable value.
|
||||||
|
*/
|
||||||
|
static Optional!T of (Nullable!T nullableValue) {
|
||||||
|
if (nullableValue.isNull) return Optional!T.empty();
|
||||||
|
return Optional!T.of(nullableValue.get);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an optional that's empty.
|
||||||
|
* Returns: An optional that is empty.
|
||||||
|
*/
|
||||||
|
static Optional!T empty() {
|
||||||
|
return Optional!T(T.init, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this optional to a Phobos-style Nullable.
|
||||||
|
* Returns: A `Nullable!T` representing this optional.
|
||||||
|
*/
|
||||||
|
Nullable!T asNullable() {
|
||||||
|
Nullable!T n;
|
||||||
|
if (!this.isNull) {
|
||||||
|
n = this.value;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of this optional if it exists, otherwise uses a given
|
||||||
|
* default value.
|
||||||
|
* Params:
|
||||||
|
* defaultValue = The value to return if no default value exists.
|
||||||
|
* Returns: The value of the optional, or the default value if this
|
||||||
|
* optional is empty.
|
||||||
|
*/
|
||||||
|
T orElse(T defaultValue) {
|
||||||
|
if (this.isNull) return defaultValue;
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of this optional if it exists, or throws an exception.
|
||||||
|
* Params:
|
||||||
|
* msg = A message to put in the exception.
|
||||||
|
* Returns: The value of this optional.
|
||||||
|
*/
|
||||||
|
T orElseThrow(string msg = "Optional value is null.") {
|
||||||
|
if (this.isNull) throw new Exception(msg);
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of this optional if it exists, or throws an exception as
|
||||||
|
* produced by the given delegate.
|
||||||
|
* Params:
|
||||||
|
* exceptionSupplier = A delegate that returns an exception to throw if
|
||||||
|
* this optional is null.
|
||||||
|
* Returns: The value of this optional.
|
||||||
|
*/
|
||||||
|
T orElseThrow(Exception delegate() exceptionSupplier) {
|
||||||
|
if (this.isNull) throw exceptionSupplier();
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a mechanism to allow usage in boolean expressions.
|
||||||
|
* Returns: true if non-null, false if null
|
||||||
|
* ---
|
||||||
|
* auto optInt = Optional!int.empty();
|
||||||
|
* assert(!optInt);
|
||||||
|
* auto optStr = Optional!string.of("Hello");
|
||||||
|
* assert(optStr);
|
||||||
|
* ---
|
||||||
|
*/
|
||||||
|
bool opCast(B : bool)() const {
|
||||||
|
return !this.isNull;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the value of a given optional to another type using a given function.
|
||||||
|
* Params:
|
||||||
|
* opt = The optional to map.
|
||||||
|
* Returns: An optional whose type is the return-type of the given `fn`
|
||||||
|
* template argument function.
|
||||||
|
*/
|
||||||
|
auto mapIfPresent(alias fn, T)(Optional!T opt) {
|
||||||
|
alias U = typeof(fn(T.init));
|
||||||
|
if (opt.isNull) return Optional!U.empty();
|
||||||
|
return Optional!U.of(fn(opt.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
Optional!string s = Optional!string.of("hello");
|
||||||
|
assert(!s.isNull);
|
||||||
|
assert(s.value == "hello");
|
||||||
|
assert(s); // test boolean conversion
|
||||||
|
Optional!int mapped = s.mapIfPresent!(str => 1);
|
||||||
|
assert(!mapped.isNull);
|
||||||
|
assert(mapped.value == 1);
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
module handy_http_primitives;
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
module handy_http_primitives.request;
|
||||||
|
|
||||||
|
import streams;
|
||||||
|
|
||||||
|
import handy_http_primitives.multivalue_map;
|
||||||
|
|
||||||
|
struct HttpRequest {
|
||||||
|
const ubyte httpVersion = 1;
|
||||||
|
const Method method = Method.GET;
|
||||||
|
const string url = "";
|
||||||
|
const(CaseInsensitiveStringMultiValueMap) headers;
|
||||||
|
const(StringMultiValueMap) queryParams;
|
||||||
|
InputStream!ubyte inputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of all possible HTTP request methods as unsigned integer values
|
||||||
|
* for efficient logic.
|
||||||
|
*
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
|
||||||
|
*/
|
||||||
|
public enum Method : ushort {
|
||||||
|
GET = 1 << 0,
|
||||||
|
HEAD = 1 << 1,
|
||||||
|
POST = 1 << 2,
|
||||||
|
PUT = 1 << 3,
|
||||||
|
DELETE = 1 << 4,
|
||||||
|
CONNECT = 1 << 5,
|
||||||
|
OPTIONS = 1 << 6,
|
||||||
|
TRACE = 1 << 7,
|
||||||
|
PATCH = 1 << 8
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
module handy_http_primitives.response;
|
||||||
|
|
||||||
|
import streams;
|
||||||
|
|
||||||
|
import handy_http_primitives.multivalue_map;
|
||||||
|
|
||||||
|
struct HttpResponse {
|
||||||
|
StatusInfo status = HttpStatus.OK;
|
||||||
|
StringMultiValueMap headers;
|
||||||
|
OutputStream!ubyte outputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A struct containing basic information about a response status.
|
||||||
|
*/
|
||||||
|
struct StatusInfo {
|
||||||
|
const ushort code;
|
||||||
|
const string text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum defining all valid HTTP response statuses:
|
||||||
|
* See here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
||||||
|
*/
|
||||||
|
enum HttpStatus : StatusInfo {
|
||||||
|
// Information
|
||||||
|
CONTINUE = StatusInfo(100, "Continue"),
|
||||||
|
SWITCHING_PROTOCOLS = StatusInfo(101, "Switching Protocols"),
|
||||||
|
PROCESSING = StatusInfo(102, "Processing"),
|
||||||
|
EARLY_HINTS = StatusInfo(103, "Early Hints"),
|
||||||
|
|
||||||
|
// Success
|
||||||
|
OK = StatusInfo(200, "OK"),
|
||||||
|
CREATED = StatusInfo(201, "Created"),
|
||||||
|
ACCEPTED = StatusInfo(202, "Accepted"),
|
||||||
|
NON_AUTHORITATIVE_INFORMATION = StatusInfo(203, "Non-Authoritative Information"),
|
||||||
|
NO_CONTENT = StatusInfo(204, "No Content"),
|
||||||
|
RESET_CONTENT = StatusInfo(205, "Reset Content"),
|
||||||
|
PARTIAL_CONTENT = StatusInfo(206, "Partial Content"),
|
||||||
|
MULTI_STATUS = StatusInfo(207, "Multi-Status"),
|
||||||
|
ALREADY_REPORTED = StatusInfo(208, "Already Reported"),
|
||||||
|
IM_USED = StatusInfo(226, "IM Used"),
|
||||||
|
|
||||||
|
// Redirection
|
||||||
|
MULTIPLE_CHOICES = StatusInfo(300, "Multiple Choices"),
|
||||||
|
MOVED_PERMANENTLY = StatusInfo(301, "Moved Permanently"),
|
||||||
|
FOUND = StatusInfo(302, "Found"),
|
||||||
|
SEE_OTHER = StatusInfo(303, "See Other"),
|
||||||
|
NOT_MODIFIED = StatusInfo(304, "Not Modified"),
|
||||||
|
TEMPORARY_REDIRECT = StatusInfo(307, "Temporary Redirect"),
|
||||||
|
PERMANENT_REDIRECT = StatusInfo(308, "Permanent Redirect"),
|
||||||
|
|
||||||
|
// Client error
|
||||||
|
BAD_REQUEST = StatusInfo(400, "Bad Request"),
|
||||||
|
UNAUTHORIZED = StatusInfo(401, "Unauthorized"),
|
||||||
|
PAYMENT_REQUIRED = StatusInfo(402, "Payment Required"),
|
||||||
|
FORBIDDEN = StatusInfo(403, "Forbidden"),
|
||||||
|
NOT_FOUND = StatusInfo(404, "Not Found"),
|
||||||
|
METHOD_NOT_ALLOWED = StatusInfo(405, "Method Not Allowed"),
|
||||||
|
NOT_ACCEPTABLE = StatusInfo(406, "Not Acceptable"),
|
||||||
|
PROXY_AUTHENTICATION_REQUIRED = StatusInfo(407, "Proxy Authentication Required"),
|
||||||
|
REQUEST_TIMEOUT = StatusInfo(408, "Request Timeout"),
|
||||||
|
CONFLICT = StatusInfo(409, "Conflict"),
|
||||||
|
GONE = StatusInfo(410, "Gone"),
|
||||||
|
LENGTH_REQUIRED = StatusInfo(411, "Length Required"),
|
||||||
|
PRECONDITION_FAILED = StatusInfo(412, "Precondition Failed"),
|
||||||
|
PAYLOAD_TOO_LARGE = StatusInfo(413, "Payload Too Large"),
|
||||||
|
URI_TOO_LONG = StatusInfo(414, "URI Too Long"),
|
||||||
|
UNSUPPORTED_MEDIA_TYPE = StatusInfo(415, "Unsupported Media Type"),
|
||||||
|
RANGE_NOT_SATISFIABLE = StatusInfo(416, "Range Not Satisfiable"),
|
||||||
|
EXPECTATION_FAILED = StatusInfo(417, "Expectation Failed"),
|
||||||
|
IM_A_TEAPOT = StatusInfo(418, "I'm a teapot"),
|
||||||
|
MISDIRECTED_REQUEST = StatusInfo(421, "Misdirected Request"),
|
||||||
|
UNPROCESSABLE_CONTENT = StatusInfo(422, "Unprocessable Content"),
|
||||||
|
LOCKED = StatusInfo(423, "Locked"),
|
||||||
|
FAILED_DEPENDENCY = StatusInfo(424, "Failed Dependency"),
|
||||||
|
TOO_EARLY = StatusInfo(425, "Too Early"),
|
||||||
|
UPGRADE_REQUIRED = StatusInfo(426, "Upgrade Required"),
|
||||||
|
PRECONDITION_REQUIRED = StatusInfo(428, "Precondition Required"),
|
||||||
|
TOO_MANY_REQUESTS = StatusInfo(429, "Too Many Requests"),
|
||||||
|
REQUEST_HEADER_FIELDS_TOO_LARGE = StatusInfo(431, "Request Header Fields Too Large"),
|
||||||
|
UNAVAILABLE_FOR_LEGAL_REASONS = StatusInfo(451, "Unavailable For Legal Reasons"),
|
||||||
|
|
||||||
|
// Server error
|
||||||
|
INTERNAL_SERVER_ERROR = StatusInfo(500, "Internal Server Error"),
|
||||||
|
NOT_IMPLEMENTED = StatusInfo(501, "Not Implemented"),
|
||||||
|
BAD_GATEWAY = StatusInfo(502, "Bad Gateway"),
|
||||||
|
SERVICE_UNAVAILABLE = StatusInfo(503, "Service Unavailable"),
|
||||||
|
GATEWAY_TIMEOUT = StatusInfo(504, "Gateway Timeout"),
|
||||||
|
HTTP_VERSION_NOT_SUPPORTED = StatusInfo(505, "HTTP Version Not Supported"),
|
||||||
|
VARIANT_ALSO_NEGOTIATES = StatusInfo(506, "Variant Also Negotiates"),
|
||||||
|
INSUFFICIENT_STORAGE = StatusInfo(507, "Insufficient Storage"),
|
||||||
|
LOOP_DETECTED = StatusInfo(508, "Loop Detected"),
|
||||||
|
NOT_EXTENDED = StatusInfo(510, "Not Extended"),
|
||||||
|
NETWORK_AUTHENTICATION_REQUIRED = StatusInfo(511, "Network Authentication Required")
|
||||||
|
}
|
Loading…
Reference in New Issue