Skip to content

Commit

Permalink
openapi: generate response schema from @types/hafas-client
Browse files Browse the repository at this point in the history
  • Loading branch information
traines-source committed Jan 15, 2025
1 parent 6484ab3 commit 70cf767
Show file tree
Hide file tree
Showing 17 changed files with 128 additions and 27 deletions.
1 change: 1 addition & 0 deletions examples/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const config = {
description: 'An HTTP API for Deutsche Bahn.',
homepage: 'http://example.org/',
docsLink: 'http://example.org/docs',
openapiSpec: true,
logging: true,
healthCheck: async () => {
const stop = await hafas.stop('8011306')
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ const createHafasRestApi = async (hafas, config, attachMiddleware) => {
api.get(path, route)
}

if (config.openapiSpec) serveOpenapiSpec(api)
if (config.openapiSpec) serveOpenapiSpec(hafas, api)

const rootLinks = {}
for (const [path, route] of Object.entries(routes)) {
Expand Down
22 changes: 22 additions & 0 deletions lib/format-product-parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,28 @@ const formatProductParams = (products) => {
return params
}

const formatProductsAsOpenapiParameters = (products) => {
const schema = formatProductParams(products);
for (let s in schema) {
delete schema[s].parse;
}
return {
type: 'object',
properties: schema
}
}

const profileSpecificProductsAsOpenapiParameters = () => {
return {
name: 'products',
in: 'query',
description: 'Filter by profile-specific products (e.g. regional transport only).',
schema: {'$ref': '#/components/schemas/ProfileSpecificProducts'}
}
}

export {
formatProductParams,
formatProductsAsOpenapiParameters,
profileSpecificProductsAsOpenapiParameters
}
19 changes: 19 additions & 0 deletions lib/generate-schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as TJS from "typescript-json-schema";

const generateOpenapiSchema = () => {
const files = ['node_modules/@types/hafas-client/index.d.ts'];
const program = TJS.getProgramFromFiles(files);
const schema = TJS.generateSchema(program, "*", {}, files);
let components = schema.definitions;
const openApiSchema = JSON.stringify(components)
.replaceAll('#/definitions/', '#/components/schemas/')
.replaceAll(/"type":\[.*?\]/g, '"type":"string"') // type as a list is not valid in OpenAPI, using string as default!
.replaceAll('"type":"null"', '"type":"string"') // type null is not valid in OpenAPI, using string as default!
.replaceAll(/"const":"([^"]+)"/g, '"enum":["$1"]'); // const is not valid in OpenAPI, using singular enum!

return JSON.parse(openApiSchema);
}

export {
generateOpenapiSchema
}
14 changes: 10 additions & 4 deletions lib/openapi-spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import LinkHeader from 'http-link-header'
import { generateOpenapiSchema } from './generate-schema.js'
import { formatProductsAsOpenapiParameters } from './format-product-parameters.js';

const openapiContentType = 'application/vnd.oai.openapi;version=3.0.3'

const generateSpec = (config, routes, logger = console) => {
const generateSpec = (hafas, config, routes, logger = console) => {
const schemaDefinitions = generateOpenapiSchema();
schemaDefinitions.ProfileSpecificProducts = formatProductsAsOpenapiParameters(hafas.profile.products);
const spec = {
openapi: '3.0.3',
info: {
Expand All @@ -14,6 +18,9 @@ const generateSpec = (config, routes, logger = console) => {
version: config.version,
},
paths: {},
components: {
schemas: schemaDefinitions
}
}
if (config.docsLink) {
spec.externalDocs = {
Expand Down Expand Up @@ -48,10 +55,9 @@ const setOpenapiLink = (res) => {
res.setHeader('Link', header.toString())
}

const serveOpenapiSpec = (api) => {
const serveOpenapiSpec = (hafas, api) => {
const {config, logger} = api.locals
const spec = generateSpec(config, api.routes, logger)

const spec = generateSpec(hafas, config, api.routes, logger)
api.get([
wellKnownPath,
'/openapi.json',
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"node": ">=18"
},
"dependencies": {
"@types/hafas-client": "^6.2.0",
"compression": "^1.7.2",
"cors": "^2.8.4",
"date-fns": "^2.12.0",
Expand All @@ -46,14 +47,15 @@
"pino": "^8.8.0",
"pino-http": "^8.2.1",
"shorthash": "0.0.2",
"stringify-entities": "^4.0.3"
"stringify-entities": "^4.0.3",
"typescript-json-schema": "^0.65.1"
},
"devDependencies": {
"axios": "^1.4.0",
"cached-hafas-client": "^5.0.0",
"eslint": "^8.0.1",
"get-port": "^7.0.0",
"hafas-client": "^6.0.0",
"hafas-client": "^6.3.3",
"ioredis": "^5.2.4",
"pino-pretty": "^10.0.1",
"tap-min": "^2.0.0",
Expand Down
5 changes: 3 additions & 2 deletions routes/arrivals.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
jsonPrettyPrintingParam,
} from '../lib/json-pretty-printing.js'
import {formatParsersAsOpenapiParams} from '../lib/format-parsers-as-openapi.js'
import {formatProductParams} from '../lib/format-product-parameters.js'
import {formatProductParams, profileSpecificProductsAsOpenapiParameters} from '../lib/format-product-parameters.js'

const err400 = (msg) => {
const err = new Error(msg)
Expand Down Expand Up @@ -138,6 +138,7 @@ Works like \`/stops/{id}/departures\`, except that it uses [\`hafasClient.arriva
// todo: examples?
},
...formatParsersAsOpenapiParams(parsers),
profileSpecificProductsAsOpenapiParameters(),
jsonPrettyPrintingOpenapiParam,
],
responses: {
Expand All @@ -150,7 +151,7 @@ Works like \`/stops/{id}/departures\`, except that it uses [\`hafasClient.arriva
properties: {
arrivals: {
type: 'array',
items: {type: 'object'}, // todo
items: {'$ref': '#/components/schemas/Alternative'},
},
realtimeDataUpdatedAt: {
type: 'integer',
Expand Down
5 changes: 3 additions & 2 deletions routes/departures.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
jsonPrettyPrintingParam,
} from '../lib/json-pretty-printing.js'
import {formatParsersAsOpenapiParams} from '../lib/format-parsers-as-openapi.js'
import {formatProductParams} from '../lib/format-product-parameters.js'
import {formatProductParams, profileSpecificProductsAsOpenapiParameters} from '../lib/format-product-parameters.js'

const MINUTE = 60 * 1000

Expand Down Expand Up @@ -150,6 +150,7 @@ Uses [\`hafasClient.departures()\`](https://github.com/public-transport/hafas-cl
// todo: examples?
},
...formatParsersAsOpenapiParams(parsers),
profileSpecificProductsAsOpenapiParameters(),
jsonPrettyPrintingOpenapiParam,
],
responses: {
Expand All @@ -162,7 +163,7 @@ Uses [\`hafasClient.departures()\`](https://github.com/public-transport/hafas-cl
properties: {
departures: {
type: 'array',
items: {type: 'object'}, // todo
items: {'$ref': '#/components/schemas/Alternative'},
},
realtimeDataUpdatedAt: {
type: 'integer',
Expand Down
5 changes: 3 additions & 2 deletions routes/journeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
jsonPrettyPrintingParam,
} from '../lib/json-pretty-printing.js'
import {formatParsersAsOpenapiParams} from '../lib/format-parsers-as-openapi.js'
import {formatProductParams} from '../lib/format-product-parameters.js'
import {formatProductParams, profileSpecificProductsAsOpenapiParameters} from '../lib/format-product-parameters.js'

const WITHOUT_FROM_TO = {
from: null,
Expand Down Expand Up @@ -271,6 +271,7 @@ Uses [\`hafasClient.journeys()\`](https://github.com/public-transport/hafas-clie
schema: {type: 'number'}
},
...formatParsersAsOpenapiParams(parsers),
profileSpecificProductsAsOpenapiParameters(),
jsonPrettyPrintingOpenapiParam,
],
responses: {
Expand All @@ -283,7 +284,7 @@ Uses [\`hafasClient.journeys()\`](https://github.com/public-transport/hafas-clie
properties: {
journeys: {
type: 'array',
items: {type: 'object'}, // todo
items: {'$ref': '#/components/schemas/Journey'},
},
realtimeDataUpdatedAt: {
type: 'integer',
Expand Down
16 changes: 14 additions & 2 deletions routes/locations.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,20 @@ Uses [\`hafasClient.locations()\`](https://github.com/public-transport/hafas-cli
content: {
'application/json': {
schema: {
type: 'array',
items: {type: 'object'}, // todo
'type': 'array',
'items': {
'anyOf': [
{
'$ref': '#/components/schemas/Location'
},
{
'$ref': '#/components/schemas/Station'
},
{
'$ref': '#/components/schemas/Stop'
}
]
}
},
// todo: example(s)
},
Expand Down
21 changes: 19 additions & 2 deletions routes/nearby.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ Uses [\`hafasClient.nearby()\`](https://github.com/public-transport/hafas-client
url: 'https://github.com/public-transport/hafas-client/blob/6/docs/nearby.md',
},
parameters: [
{
name: 'location',
in: 'query',
schema: {'$ref': '#/components/schemas/Location'}
},
...formatParsersAsOpenapiParams(parsers),
jsonPrettyPrintingOpenapiParam,
],
Expand All @@ -101,8 +106,20 @@ Uses [\`hafasClient.nearby()\`](https://github.com/public-transport/hafas-client
content: {
'application/json': {
schema: {
type: 'array',
items: {type: 'object'}, // todo
'type': 'array',
'items': {
'anyOf': [
{
'$ref': '#/components/schemas/Location'
},
{
'$ref': '#/components/schemas/Station'
},
{
'$ref': '#/components/schemas/Stop'
}
]
}
},
// todo: example(s)
},
Expand Down
7 changes: 6 additions & 1 deletion routes/radar.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ Uses [\`hafasClient.radar()\`](https://github.com/public-transport/hafas-client/
url: 'https://github.com/public-transport/hafas-client/blob/6/docs/radar.md',
},
parameters: [
{
name: 'bbox',
in: 'query',
schema: {'$ref': '#/components/schemas/BoundingBox'}
},
...formatParsersAsOpenapiParams(parsers),
jsonPrettyPrintingOpenapiParam,
],
Expand All @@ -97,7 +102,7 @@ Uses [\`hafasClient.radar()\`](https://github.com/public-transport/hafas-client/
properties: {
movements: {
type: 'array',
items: {type: 'object'}, // todo
items: {'$ref': '#/components/schemas/Movement'},
},
realtimeDataUpdatedAt: {
type: 'integer',
Expand Down
10 changes: 8 additions & 2 deletions routes/reachable-from.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
jsonPrettyPrintingParam,
} from '../lib/json-pretty-printing.js'
import {formatParsersAsOpenapiParams} from '../lib/format-parsers-as-openapi.js'
import {formatProductParams} from '../lib/format-product-parameters.js'
import {formatProductParams, profileSpecificProductsAsOpenapiParameters} from '../lib/format-product-parameters.js'

const err400 = (msg) => {
const err = new Error(msg)
Expand Down Expand Up @@ -84,7 +84,13 @@ Uses [\`hafasClient.reachableFrom()\`](https://github.com/public-transport/hafas
url: 'https://github.com/public-transport/hafas-client/blob/6/docs/reachable-from.md',
},
parameters: [
{
name: 'address',
in: 'query',
schema: {'$ref': '#/components/schemas/Location'}
},
...formatParsersAsOpenapiParams(parsers),
profileSpecificProductsAsOpenapiParameters(),
jsonPrettyPrintingOpenapiParam,
],
responses: {
Expand All @@ -97,7 +103,7 @@ Uses [\`hafasClient.reachableFrom()\`](https://github.com/public-transport/hafas
properties: {
reachable: {
type: 'array',
items: {type: 'object'}, // todo
items: {'$ref': '#/components/schemas/Duration'},
},
realtimeDataUpdatedAt: {
type: 'integer',
Expand Down
3 changes: 1 addition & 2 deletions routes/refresh-journey.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,7 @@ The journey will be the same (equal \`from\`, \`to\`, \`via\`, date/time & vehic
type: 'object',
properties: {
journey: {
type: 'object',
// todo
'$ref': '#/components/schemas/Journey'
},
realtimeDataUpdatedAt: {
type: 'integer',
Expand Down
13 changes: 11 additions & 2 deletions routes/stop.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,17 @@ Uses [\`hafasClient.stop()\`](https://github.com/public-transport/hafas-client/b
content: {
'application/json': {
schema: {
type: 'object',
// todo
'anyOf': [
{
'$ref': '#/components/schemas/Location'
},
{
'$ref': '#/components/schemas/Station'
},
{
'$ref': '#/components/schemas/Stop'
}
]
},
// todo: example(s)
},
Expand Down
3 changes: 1 addition & 2 deletions routes/trip.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ Uses [\`hafasClient.trip()\`](https://github.com/public-transport/hafas-client/b
type: 'object',
properties: {
trip: {
type: 'object',
// todo
'$ref': '#/components/schemas/Trip'
},
realtimeDataUpdatedAt: {
type: 'integer',
Expand Down
3 changes: 2 additions & 1 deletion routes/trips.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
jsonPrettyPrintingParam,
} from '../lib/json-pretty-printing.js'
import {formatParsersAsOpenapiParams} from '../lib/format-parsers-as-openapi.js'
import {formatProductParams} from '../lib/format-product-parameters.js'
import {formatProductParams, profileSpecificProductsAsOpenapiParameters} from '../lib/format-product-parameters.js'

// const MINUTE = 60 * 1000

Expand Down Expand Up @@ -139,6 +139,7 @@ Uses [\`hafasClient.tripsByName()\`](https://github.com/public-transport/hafas-c
},
parameters: [
...formatParsersAsOpenapiParams(parsers),
profileSpecificProductsAsOpenapiParameters(),
jsonPrettyPrintingOpenapiParam,
],
responses: {
Expand Down

0 comments on commit 70cf767

Please sign in to comment.