Skip to content

Instantly share code, notes, and snippets.

@vnayar
Last active July 21, 2019 13:20
Show Gist options
  • Save vnayar/0da1c50faaece10a8bf65d76aff08456 to your computer and use it in GitHub Desktop.
Save vnayar/0da1c50faaece10a8bf65d76aff08456 to your computer and use it in GitHub Desktop.
Demonstration of using arbitrary MediaTypes for HTTP body conversions in Vibe.d
import vibe.vibe;
////
// Example controller making use of the custom UDAs 'responseBody' and 'requestBodyParam'.
////
/// Interface useful for generating client code with RestInterfaceClient.
@path("/api")
interface ApiRestInterface {
@safe:
@responseBody!(string, MediaType.TEXT_PLAIN)
string get();
@responseBody!(int[string], MediaType.APPLICATION_JSON)
int[string] getJson();
@requestBodyParam!(MediaType.APPLICATION_JSON, Person)("person")
void postPerson(Person person);
}
/// Server-side controller for receiving input data.
class ApiController : ApiRestInterface {
@safe override:
string get() {
logInfo("ApiController.get: ");
return "Hello birdface!";
}
int[string] getJson() {
logInfo("ApiController.getJson: ");
return ["ham": 3, "bird": 7, "cat": 9];
}
void postPerson(Person person) {
logInfo("received person = %s", to!string(person));
}
}
////
// Define arbitrary encoders/decoders for sending/receiving HTTP bodies.
////
@safe void textEncoder(string ret, HTTPServerResponse res) {
logInfo("textEncoder:");
res.writeBody(ret);
}
@safe void jsonEncoder(int[string] ret, HTTPServerResponse res) {
import vibe.data.json;
logInfo("jsonEncoder:");
Json j = Json.emptyObject;
foreach(key, val; ret) {
j[key] = val;
}
res.writeBody(j.serializeToJsonString());
}
@safe T jsonDecoder(T)(HTTPServerRequest req) {
import vibe.data.json;
Json j = req.json();
return j.deserializeJson!T();
}
shared static this() {
registerEncoder(MediaType.TEXT_PLAIN, &textEncoder);
registerEncoder(MediaType.APPLICATION_JSON, &jsonEncoder);
registerDecoder(MediaType.APPLICATION_JSON, &jsonDecoder!Person);
runWorkerTaskDist(() {
auto router = new URLRouter();
router.registerRestInterface(new ApiController());
// Start up the HTTP server
auto settings = new HTTPServerSettings();
settings.port = 8080;
settings.bindAddresses = ["::1", "127.0.0.1"];
settings.options = HTTPServerOption.reusePort;
listenHTTP(settings, router);
});
logInfo("Please open http://127.0.0.1:8080/ in your browser.");
}
////
// The implementation of the custom UDAs.
////
enum MediaType : string {
APPLICATION_JSON = "application/json",
APPLICATION_X_PROTOBUF = "application/x-protobuf",
TEXT_PLAIN = "text/plain"
}
/**
Converts the HTTPServerRequest body from a given MediaType to a RestInterface implementation
method parameter.
Params:
T = The type of the method parameter.
mediaType = The MediaType to decode into a method argument.
param = The name of the method parameter.
*/
@safe auto requestBodyParam(T)(string param) {
return before!(bodyDecode!T)(param);
}
/// Like requestBodyParam, but limits the permitted MediaType.
@safe auto requestBodyParam(string mediaType, T)(string param) {
return before!(bodyDecode!(mediaType, T))(param);
}
/**
Converts the return value of a RestInterface implementation method to the specified media type.
Params:
T = The return type of the annotated method.
mediaType = The MediaType which the return value should be converted into.
*/
alias responseBody(T, string mediaType) = after!(bodyEncode!(T, mediaType));
/// A mapping from the received MediaType to a function that decodes an HTTP body into type T.
shared template decoderByMediaType(T) {
alias DecoderT = @safe T function(HTTPServerRequest);
DecoderT[string] decoderByMediaType;
}
/**
Registers a decoder for use by bodyDecode to automatically convert from MediaTypes.
This method is generally called from the `shared static this()` section of a module defining
decoders.
*/
void registerDecoder(T)(string mediaType, T function(HTTPServerRequest) decoder) {
logInfo("Registering decoder from type %s to MediaType %s.", typeid(T), mediaType);
synchronized {
decoderByMediaType!T[mediaType] = decoder;
}
}
/**
Decodes an HTTP body into the desired type if the MediaType has a decoder.
This method should be used with `@vibe.web.rest.before`.
*/
T bodyDecode(T)(HTTPServerRequest req, HTTPServerResponse res) {
enforce(
req.contentType() in decoderByMediaType!T,
new MediaTypeException("No decoder found from MediaType " ~ req.contentType()
~ " to type " ~ typeid(T).toString() ~ "."));
auto decoder = decoderByMediaType!T[req.contentType()];
return decoder(req);
}
/// Similar to bodyDecode, but enforces that the media type matches expectations.
T bodyDecode(string mediaType, T)(HTTPServerRequest req, HTTPServerResponse res) {
enforce(req.contentType() == mediaType);
return bodyDecode!T(req, res);
}
/// A mapping from the desired MediaType to a function that encodes type T into an HTTP body.
shared template encoderByMediaType(T) {
alias EncoderT = @safe void function(T, HTTPServerResponse);
EncoderT[string] encoderByMediaType;
}
/**
Registers an encoder for use by bodyEncode to automatically convert to MediaTypes.
This method is generally called from the `shared static this()` section of a module defining
encoders.
*/
void registerEncoder(T)(string mediaType, void function(T, HTTPServerResponse) encoder) {
logInfo("Registering encoder from MediaType %s to type %s.", mediaType, typeid(T));
synchronized {
encoderByMediaType!T[mediaType] = encoder;
}
}
/**
Encodes an object of type T into the HTTP body depending on the response content-type.
This method should be used with `@vibe.web.rest.after`.
*/
T bodyEncode(T)(T ret, HTTPServerRequest req, HTTPServerResponse res) {
enforce(
res.contentType() in encoderByMediaType!T,
new MediaTypeException(
"No encoder found from type " ~ typeid(T).toString() ~ " to MediaType "
~ res.contentType() ~ ". Remember to set the Content-Type header of the response!"));
auto encoder = encoderByMediaType!T[res.contentType()];
encoder(ret, res);
return ret;
}
/// Similar to `bodyEncode!T(T, HTTPServerRequest, HTTPServerResponse)` but sets the media type.
T bodyEncode(T, string mediaType)(T ret, HTTPServerRequest req, HTTPServerResponse res) {
res.contentType(mediaType);
bodyEncode(ret, req, res);
return ret;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment