Creating a Custom Policy
A policy is a struct with static methods that return CrateRule objects. TypeRouter discovers these methods at compile time and generates the corresponding operations.
Minimal Policy
Section titled “Minimal Policy”struct XmlApiPolicy(PolicyConfig config = PolicyConfig.init) { enum policyConfig = config;
static immutable { string name = "XML API"; string mime = "application/xml"; }
alias Serializer = XmlSerializer; alias Routing = XmlRouting;
static: private CrateRule templateRule(const ModelDescription definition) { auto serializer = new Serializer(definition); CrateRule rule; rule.request.serializer = serializer; rule.request.requiredHeaders = ["Accept: " ~ mime]; rule.response.serializer = serializer; rule.schemas = serializer.schemas; return rule; }
CrateRule getList(const ModelDescription definition) { auto routing = new Routing(definition); auto rule = templateRule(definition); rule.request.path = config.baseUrl ~ routing.getList; rule.request.method = HTTPMethod.GET; rule.response.statusCode = 200; return rule; }
CrateRule getItem(const ModelDescription definition) { auto routing = new Routing(definition); auto rule = templateRule(definition); rule.request.path = config.baseUrl ~ routing.getItem; rule.request.method = HTTPMethod.GET; rule.response.statusCode = 200; return rule; }
CrateRule createItem(const ModelDescription definition) { auto routing = new Routing(definition); auto rule = templateRule(definition); rule.request.path = config.baseUrl ~ routing.createItem; rule.request.method = HTTPMethod.POST; rule.response.statusCode = 201; return rule; }
CrateRule updateItem(const ModelDescription definition) { auto routing = new Routing(definition); auto rule = templateRule(definition); rule.request.path = config.baseUrl ~ routing.updateItem; rule.request.method = HTTPMethod.PATCH; rule.response.statusCode = 200; return rule; }
CrateRule replaceItem(const ModelDescription definition) { auto routing = new Routing(definition); auto rule = templateRule(definition); rule.request.path = config.baseUrl ~ routing.replaceItem; rule.request.method = HTTPMethod.PUT; rule.response.statusCode = 200; return rule; }
CrateRule deleteItem(const ModelDescription definition) { auto routing = new Routing(definition); auto rule = templateRule(definition); rule.request.path = config.baseUrl ~ routing.deleteItem; rule.request.method = HTTPMethod.DELETE; rule.response.statusCode = 204; return rule; }}Required Components
Section titled “Required Components”Policy Struct
Section titled “Policy Struct”The struct needs:
| Member | Purpose |
|---|---|
policyConfig | Enum storing the PolicyConfig template parameter |
name | Human-readable protocol name |
mime | Default MIME type |
Serializer | Alias to your ModelSerializer implementation |
Routing | Alias to your routing helper |
templateRule() | Private helper that sets common defaults for all rules |
Each static method returning CrateRule becomes an operation. TypeRouter discovers them via compile-time reflection.
Serializer
Section titled “Serializer”Implement the ModelSerializer interface:
class XmlSerializer : ModelSerializer { private ModelDescription _definition;
this(const ModelDescription definition) pure { _definition = definition.dup; }
@safe: ModelDescription definition() { return _definition; }
InputRange!string toResponseList(InputRange!Json data, OperationContext ctx) @trusted { // Convert Json items to XML string chunks }
InputRange!string toResponseItem(Json data, OperationContext ctx) @trusted { // Convert single Json item to XML string }
Json fromRequest(string id, Json data) @trusted { // Parse incoming request body into internal Json }
string respondItemSchemaKey() { return _definition.singular ~ "XmlResponse"; } string respondListSchemaKey() { return _definition.plural ~ "XmlResponse"; } string itemKey() { return _definition.singular; } Schema[string] schemas() { /* OpenAPI schemas */ }}The serializer sits between the database layer (which works with Json) and the HTTP response. toResponseList and toResponseItem return InputRange!string for streaming — chunks are written to the response as they’re produced.
Routing Helper
Section titled “Routing Helper”A simple helper that generates URL paths from model metadata:
class XmlRouting { private string plural;
this(const ModelDescription definition) { this.plural = definition.plural; }
@property string getList() { return "/" ~ plural; } @property string getItem() { return "/" ~ plural ~ "/:id"; } @property string createItem() { return "/" ~ plural; } @property string updateItem() { return "/" ~ plural ~ "/:id"; } @property string replaceItem() { return "/" ~ plural ~ "/:id"; } @property string deleteItem() { return "/" ~ plural ~ "/:id"; }}Lifecycle Hooks
Section titled “Lifecycle Hooks”Optionally define hooks for protocol-level setup:
struct XmlApiPolicy(PolicyConfig config = PolicyConfig.init) { // ... fields and methods ...
static void onRouterInit(ITypeRouter router) { // Called once when the first model is added // Register protocol-level endpoints (schema discovery, etc.) }
static void onRouterReady(IRouterContext router) { // Called lazily on the first HTTP request // Finalize configuration after all models are registered }}CrateRouter checks if these methods exist at compile time and only calls them if defined.
Body-Based Routing
Section titled “Body-Based Routing”For protocols like MCP and GraphQL that use a single endpoint, set bodyMatcher on the request:
CrateRule getItem(const ModelDescription definition) { auto rule = templateRule(definition); rule.request.path = config.baseUrl ~ "/xml-rpc"; rule.request.method = HTTPMethod.POST; rule.request.bodyMatcher = Json(["action": Json("getItem"), "model": Json(definition.singular)]); return rule;}The router matches incoming request bodies against bodyMatcher to dispatch to the correct operation.
Using Your Policy
Section titled “Using Your Policy”auto crateRouter = router.crateSetup!(XmlApiPolicy!());crateRouter.add(productCrate);
// Or combine with built-in policies:auto crateRouter = router.crateSetup!(RestApi, XmlApiPolicy!());crateRouter.add(productCrate);