Last active
July 21, 2019 13:20
-
-
Save vnayar/0da1c50faaece10a8bf65d76aff08456 to your computer and use it in GitHub Desktop.
Demonstration of using arbitrary MediaTypes for HTTP body conversions in Vibe.d
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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