path-hints
description: Match API requests and responses to your schema
sidebar_position: 6
Provide Path Hints
The Speakeasy SDK out of the box will do its best to match requests to your provided OpenAPI Schema. It does this by extracting the path template used by the Mapping annotation for your controller handlers and attempting to match it to the paths defined in the OpenAPI Schema, for example:
- Go
- Java
- Typescript
- Rust
r := mux.NewRouter()
r.Use(sdkInstance.Middleware)
r.HandleFunc("/v1/users/{id}", MyHandler) // The path template "/v1/users/{id}" is captured automatically by the SDK
@GetMapping("/v1/users/{id}") // The path template "/v1/users/{id}" is captured automatically by the SDK
public String getUser(@PathVariable("id") String id) {
// your handler logic here
}
Express
const app = express();
app.use(speakeasy.expressMiddleware());
app.all("/v1/user/:id/action/:action", myHandler); // The path template "/v1/user/{id}/action/{action}" is captured automatically by the SDK after being normalized to the OpenAPI spec format for paths.
NestJS
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('/v1/user/:id/action/:action') // The path template "/v1/user/{id}/action/{action}" is captured automatically by the SDK after being normalized to the OpenAPI spec format for paths.
myHandler(): string {
// handler code here
}
}
// The path template "/v1/users/{id}" is captured automatically by the SDK
#[get("v1/users/{id}")]
async fn handler_function(id: web::Path<String>) -> impl Responder {
// handler function code
}
This isn't always successful or even possible, meaning requests received by Speakeasy will be marked as unmatched
, and potentially not associated with your Api, Version or ApiEndpoints in the Speakeasy Dashboard.
This normally happens if your path contains regex patterns or is a catch all path and your handler parses the routes manually.
To help the SDK in these situations you can provide path hints per request handler that match the paths in your OpenAPI Schema:
- Go
- Java
- Typescript
- Rust
func MyHandler(w http.ResponseWriter, r *http.Request) {
// Provide a path hint for the request using the OpenAPI Path Templating format: https://swagger.io/specification/#path-templating-matching
ctrl := speakeasy.MiddlewareController(req)
ctrl.PathHint("/v1/users/{id}")
// the rest of your handlers code
}
Wildcard path matching in Echo & Chi will end up with a OpenAPI path paramater called {wildcard} which will only match single level values represented by the wildcard. This is a restriction of the OpenAPI spec (Detail Here). For example:
chi template: /user/{id}/path/* => openapi template: /user/{id}/path/{wildcard}
And in the above example a path like /user/1/path/some/sub/path
won't match but /user/1/path/somesubpathstring
will, as /
characters are not matched in path parameters by the OpenAPI spec.
import dev.speakeasyapi.sdk.utils.SpeakeasyInterceptor;
@GetMapping("/")
public String handle(@RequestAttribute(SpeakeasyInterceptor.ControllerKey) SpeakeasyMiddlewareController controller) {
controller.setPathHint("/v1/users/{id}"); // This path hint will be used to match requests to your OpenAPI Schema
// your handler logic here
}
The following example will work for NestJS as well, just get the Speakeasy MiddlewareController from the request object.
const app = express();
app.use(speakeasy.expressMiddleware());
app.all("/", (req, res) => {
// Provide a path hint for the request using the OpenAPI Path Templating format: https://swagger.io/specification/#path-templating-matching
req.controller.setPathHint("/v1/user/{id}/action/{action}");
// the rest of your handlers code
});
If using nested Routers in express or Controller path prefixs in NestJS the SpeakeasySDK will not be able to get the full path template for the request due to a current issue in express. To work around this you can manually set path hints as above or we can monkey patch in a modification to express to enable the SpeakeasySDK to get the full path template:
/* eslint-disable */
// @ts-nocheck
import express from "express";
/* Credit to @watson and @jagadish-kb https://github.com/expressjs/express/issues/2879#issuecomment-269433170 */
const origUse = express.Router.use;
express.Router.use = function (fn) {
if (typeof fn === "string" && Array.isArray(this.stack)) {
let offset = this.stack.length;
const result = origUse.apply(this, arguments);
let layer;
for (; offset < this.stack.length; offset++) {
layer = this.stack[offset];
// I'm not sure if my check for `fast_slash` is the way to go here
// But if I don't check for it, each stack element will add a slash to the path
if (layer && layer.regexp && !layer.regexp.fast_slash)
layer.__mountpath = fn;
}
return result;
} else {
return origUse.apply(this, arguments);
}
};
var origPP = express.Router.process_params;
express.Router.process_params = function (layer, called, req, res, done) {
const path =
(req.route &&
(req.route.path || (req.route.regexp && req.route.regexp.source))) ||
layer.__mountpath ||
"";
if (req.__route && path) {
const searchFromIdx = req.__route.length - path.length;
if (req.__route.indexOf(path, searchFromIdx) > 0) {
return origPP.apply(this, arguments);
}
}
req.__route = (req.__route || "") + path;
return origPP.apply(this, arguments);
};
Create a file called expressmonkeypatch.ts
or similar and import it into your service's main.ts
file import "./expressmonkeypatch";
. This will path express and allow the SDK to determine the full path automatically.
#[post("/special_route")]
async fn special_route(controller: ReqData<Arc<RwLock<MiddlewareController>>>) -> HttpResponse {
// Provide a path hint for the request using the OpenAPI Path templating format:
// https://swagger.io/specification/#path-templating-matching
controller
.write()
.unwrap()
.set_path_hint("/special_route/{wildcard}");
// the rest of your handlers code
}