@@ -2,18 +2,23 @@ import * as _ from 'lodash';
22import * as querystring from 'querystring' ;
33
44import { OpenAPIObject , PathItemObject } from 'openapi-directory' ;
5+ import { MethodObject } from '@open-rpc/meta-schema' ;
56import * as Ajv from 'ajv' ;
67
7- import type { ApiRequestMatcher } from './api-interfaces' ;
88import { openApiSchema } from './openapi-schema' ;
99import { dereference } from '../../util/json-schema' ;
10-
10+ import { OpenRpcDocument , OpenRpcMetadata } from './jsonrpc' ;
1111
1212export interface OpenApiMetadata {
1313 type : 'openapi' ;
1414 spec : OpenAPIObject ;
1515 serverMatcher : RegExp ;
16- requestMatchers : Map < ApiRequestMatcher , Path > ;
16+ requestMatchers : Map < OpenApiRequestMatcher , Path > ;
17+ }
18+
19+ interface OpenApiRequestMatcher {
20+ pathMatcher : RegExp ;
21+ queryMatcher : querystring . ParsedUrlQuery ;
1722}
1823
1924interface Path {
@@ -25,7 +30,9 @@ const filterSpec = new Ajv({
2530 removeAdditional : 'failing'
2631} ) . compile ( openApiSchema ) ;
2732
28- function templateStringToRegexString ( template : string ) : string {
33+ // Note that OpenAPI template strings are not the same as JSON template language templates,
34+ // which use ${...} instead of {...}.
35+ function openApiTemplateStringToRegexString ( template : string ) : string {
2936 return template
3037 // Escape all regex chars *except* { }
3138 . replace ( / [ \^ $ \\ . * + ? ( ) [ \] | ] / g, '\\$&' )
@@ -35,7 +42,7 @@ function templateStringToRegexString(template: string): string {
3542 . replace ( / \/ $ / , '' ) ;
3643}
3744
38- export async function buildApiMetadata (
45+ export async function buildOpenApiMetadata (
3946 spec : OpenAPIObject ,
4047 baseUrlOverrides ?: string [ ]
4148) : Promise < OpenApiMetadata > {
@@ -58,18 +65,18 @@ export async function buildApiMetadata(
5865 }
5966
6067 // Now it's relatively small & tidy, dereference everything.
61- spec = < OpenAPIObject > dereference ( spec ) ;
68+ spec = dereference ( spec ) ;
6269
6370 const serverUrlRegexSources = baseUrlOverrides && baseUrlOverrides . length
6471 // Look for one of the given base URLs, ignoring trailing slashes
6572 ? baseUrlOverrides . map ( url => _ . escapeRegExp ( url ) . replace ( / \/ $ / , '' ) )
6673 // Look for any URL of the base servers in the spec
67- : spec . servers ! . map ( s => templateStringToRegexString ( s . url ) ) ;
74+ : spec . servers ! . map ( s => openApiTemplateStringToRegexString ( s . url ) ) ;
6875
6976 // Build a regex that matches any of these at the start of a URL
70- const serverMatcher = new RegExp ( `^(${ serverUrlRegexSources . join ( '|' ) } )` , 'i' )
77+ const serverMatcher = new RegExp ( `^(${ serverUrlRegexSources . join ( '|' ) } )` , 'i' ) ;
7178
72- const requestMatchers = new Map < ApiRequestMatcher , Path > ( ) ;
79+ const requestMatchers = new Map < OpenApiRequestMatcher , Path > ( ) ;
7380 _ . entries ( spec . paths )
7481 // Sort path & pathspec pairs to ensure that more specific paths are
7582 // always listed first, so that later on we can always use the first match
@@ -115,7 +122,7 @@ export async function buildApiMetadata(
115122 requestMatchers . set ( {
116123 // Build a regex that matches this path on any of those base servers
117124 pathMatcher : new RegExp (
118- serverMatcher . source + templateStringToRegexString ( realPath ) + '/?$' ,
125+ serverMatcher . source + openApiTemplateStringToRegexString ( realPath ) + '/?$' ,
119126 'i'
120127 ) ,
121128 // Some specs (AWS) also match requests by specific params
@@ -132,4 +139,38 @@ export async function buildApiMetadata(
132139 serverMatcher,
133140 requestMatchers
134141 } ;
142+ }
143+
144+ // Approximately transform a JSON template string into a regex string that will match
145+ // values, using wildcards for each template. This is based on this draft RFC:
146+ // https://datatracker.ietf.org/doc/draft-jonas-json-template-language/
147+ // Only seems relevant to OpenRPC, doesn't seem widely used elsewhere.
148+ function jsonTemplateStringToRegexString ( template : string ) : string {
149+ return template
150+ // Escape all regex chars *except* $, {, }
151+ . replace ( / [ \^ \\ . * + ? ( ) [ \] | ] / g, '\\$&' )
152+ // Replace templates with wildcards
153+ . replace ( / \$ \{ ( [ ^ / } ] + ) } / g, '([^\/]*)' )
154+ // Drop trailing slashes
155+ . replace ( / \/ $ / , '' ) ;
156+ }
157+
158+ export function buildOpenRpcMetadata ( spec : OpenRpcDocument , baseUrlOverrides ?: string [ ] ) : OpenRpcMetadata {
159+ spec = dereference ( spec ) ;
160+
161+ const serverUrlRegexSources = baseUrlOverrides && baseUrlOverrides . length
162+ // Look for one of the given base URLs, ignoring trailing slashes
163+ ? baseUrlOverrides . map ( url => _ . escapeRegExp ( url ) . replace ( / \/ $ / , '' ) )
164+ // Look for any URL of the base servers in the spec
165+ : spec . servers ! . map ( s => jsonTemplateStringToRegexString ( s . url ) ) ;
166+
167+ // Build a regex that matches any of these at the start of a URL
168+ const serverMatcher = new RegExp ( `^(${ serverUrlRegexSources . join ( '|' ) } )` , 'i' ) ;
169+
170+ return {
171+ type : 'openrpc' ,
172+ spec,
173+ serverMatcher,
174+ requestMatchers : _ . keyBy ( spec . methods , 'name' ) as _ . Dictionary < MethodObject > // Dereferenced
175+ } ;
135176}
0 commit comments