Skip to main content
Glama
openapi.ts12.5 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { isResourceWithId } from '@medplum/core'; import type { Request, Response } from 'express'; import type { JSONSchema4 } from 'json-schema'; import type { ComponentsObject, OpenAPIObject, PathsObject, ReferenceObject, SchemaObject, TagObject, } from 'openapi3-ts/oas31'; import { getConfig } from './config/loader'; import { getJsonSchemaDefinitions } from './fhir/jsonschema'; type OpenAPIObjectWithPaths = OpenAPIObject & { paths: PathsObject }; type SchemaMap = { [schema: string]: SchemaObject | ReferenceObject }; let cachedSpec: OpenAPIObjectWithPaths; export function openApiHandler(_req: Request, res: Response): void { res.status(200).json(getSpec()); } function getSpec(): OpenAPIObjectWithPaths { if (!cachedSpec) { cachedSpec = buildSpec(); } return cachedSpec; } function buildSpec(): OpenAPIObjectWithPaths { const result = buildBaseSpec(); const definitions = getJsonSchemaDefinitions(); Object.entries(definitions).forEach(([name, definition]) => buildFhirType(result, name, definition)); buildPaths(result); return result; } /** * Builds the base structure of the OpenAPI specification. * See: https://swagger.io/specification/ * @returns The OpenAPI specification. */ function buildBaseSpec(): OpenAPIObjectWithPaths { const config = getConfig(); return { openapi: '3.1.0', info: { title: 'Medplum - OpenAPI 3.0', description: 'Medplum OpenAPI 3.0 specification. Learn more about Medplum at [https://www.medplum.com](https://www.medplum.com).', termsOfService: 'https://www.medplum.com/terms', contact: { email: 'hello@medplum.com', }, license: { name: 'Apache 2.0', url: 'https://www.apache.org/licenses/LICENSE-2.0.html', }, version: '1.0.5', }, externalDocs: { description: 'Learn more about Medplum', url: 'https://www.medplum.com/', }, servers: [ { url: config.baseUrl, }, ], security: [ { BasicAuth: [], BearerAuth: [], OpenID: [], }, ], tags: [], paths: {}, components: { schemas: {}, securitySchemes: { BasicAuth: { type: 'http', scheme: 'basic', }, BearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', }, OpenID: { type: 'openIdConnect', openIdConnectUrl: config.baseUrl + '.well-known/openid-configuration', }, }, }, }; } /** * Builds the OpenAPI specification details for a FHIR type. * @param result - The OpenAPI specification output. * @param typeName - The FHIR type name. * @param typeDefinition - The FHIR type definition. */ function buildFhirType(result: OpenAPIObject, typeName: string, typeDefinition: JSONSchema4): void { buildSchema(result, typeName, typeDefinition); if (isResourceType(typeDefinition)) { buildTags(result, typeName, typeDefinition); } } /** * Builds the schema for a FHIR type. * See: https://swagger.io/specification/#schema-object * @param result - The OpenAPI specification output. * @param typeName - The FHIR type name. * @param typeDefinition - The FHIR type definition. */ function buildSchema(result: OpenAPIObject, typeName: string, typeDefinition: JSONSchema4): void { ((result.components as ComponentsObject).schemas as SchemaMap)[typeName] = buildObjectSchema(typeDefinition); } /** * Converts a JSONSchema type definition to an OpenAPI type definition. * @param definition - The JSONSchema type definition. * @returns The OpenAPI type definition. */ function buildObjectSchema(definition: JSONSchema4): SchemaObject { const result = JSON.parse(JSON.stringify(definition, refReplacer)) as SchemaObject; const resourceTypeProperty = result.properties?.resourceType as any; if (resourceTypeProperty?.const) { delete resourceTypeProperty.const; resourceTypeProperty.type = 'string'; } return result; } /** * Replaces JSONSchema references with OpenAPI references. * Can be used as 2nd parameter in JSON.stringify. * @param key - The JSON property key. * @param value - The JSON property value. * @returns The updated JSON property value. */ function refReplacer(key: string, value: any): any { if (key === '$ref') { return (value as string).replace('#/definitions/', '#/components/schemas/'); } if (key.startsWith('_')) { return undefined; } return value; } /** * Builds the tags for a FHIR type. * See: https://swagger.io/specification/#tag-object * @param result - The OpenAPI specification output. * @param typeName - The FHIR type name. * @param typeDefinition - The FHIR type definition. */ function buildTags(result: OpenAPIObject, typeName: string, typeDefinition: JSONSchema4): void { (result.tags as TagObject[]).push({ name: typeName, description: typeDefinition.description, externalDocs: { url: 'https://www.medplum.com/docs/api/fhir/resources/' + typeName.toLowerCase(), }, }); } /** * Builds the paths for a FHIR resource type. * @param result - The OpenAPI specification output. */ function buildPaths(result: OpenAPIObjectWithPaths): void { result.paths[`/fhir/R4/{resourceType}`] = { get: buildSearchPath(), post: buildCreatePath(), }; result.paths[`/fhir/R4/{resourceType}/{id}`] = { get: buildReadPath(), put: buildUpdatePath(), delete: buildDeletePath(), patch: buildPatchPath(), }; result.paths[`/fhir/R4/{resourceType}/{id}/_history`] = { get: buildReadHistoryPath(), }; result.paths[`/fhir/R4/{resourceType}/{id}/_history/{versionId}`] = { get: buildReadVersionPath(), }; } function buildSearchPath(): any { return { summary: 'Search', description: 'Search', operationId: 'search', parameters: [ { name: 'resourceType', in: 'path', description: 'Resource Type', required: true, schema: { type: 'string', }, }, ], responses: { '200': { description: 'Success', content: { 'application/fhir+json': { schema: { $ref: '#/components/schemas/Bundle', }, }, }, }, }, }; } function buildCreatePath(): any { return { summary: 'Create Resource', description: 'Create Resource', operationId: 'createResource', parameters: [ { name: 'resourceType', in: 'path', description: 'Resource Type', required: true, schema: { type: 'string', }, }, ], requestBody: { description: 'Create Resource', content: { 'application/fhir+json': { schema: { $ref: '#/components/schemas/ResourceList', }, }, }, required: true, }, responses: { '201': { description: 'Success', content: { 'application/fhir+json': { schema: { $ref: '#/components/schemas/ResourceList', }, }, }, }, }, }; } function buildReadPath(): any { return { summary: 'Read Resource', description: 'Read Resource', operationId: 'readResource', parameters: [ { name: 'resourceType', in: 'path', description: 'Resource Type', required: true, schema: { type: 'string', }, }, { name: 'id', in: 'path', description: 'Resource ID', required: true, schema: { type: 'string', format: 'uuid', }, }, ], responses: { '200': { description: 'Success', content: { 'application/fhir+json': { schema: { $ref: '#/components/schemas/ResourceList', }, }, }, }, }, }; } function buildReadHistoryPath(): any { return { summary: 'Read Resource History', description: 'Read Resource History', operationId: 'readResourceHistory', parameters: [ { name: 'resourceType', in: 'path', description: 'Resource Type', required: true, schema: { type: 'string', }, }, { name: 'id', in: 'path', description: 'Resource ID', required: true, schema: { type: 'string', format: 'uuid', }, }, ], responses: { '200': { description: 'Success', content: { 'application/fhir+json': { schema: { $ref: '#/components/schemas/Bundle', }, }, }, }, }, }; } function buildReadVersionPath(): any { return { summary: 'Read Version', description: 'Read Version', operationId: 'readVersion', parameters: [ { name: 'resourceType', in: 'path', description: 'Resource Type', required: true, schema: { type: 'string', }, }, { name: 'id', in: 'path', description: 'Resource ID', required: true, schema: { type: 'string', format: 'uuid', }, }, { name: 'versionId', in: 'path', description: 'Version ID', required: true, schema: { type: 'string', format: 'uuid', }, }, ], responses: { '200': { description: 'Success', content: { 'application/fhir+json': { schema: { $ref: '#/components/schemas/ResourceList', }, }, }, }, }, }; } function buildUpdatePath(): any { return { summary: 'Update Resource', description: 'Update Resource', operationId: 'updateResource', parameters: [ { name: 'resourceType', in: 'path', description: 'Resource Type', required: true, schema: { type: 'string', }, }, { name: 'id', in: 'path', description: 'Resource ID', required: true, schema: { type: 'string', format: 'uuid', }, }, ], requestBody: { description: 'Update Resource', content: { 'application/fhir+json': { schema: { $ref: '#/components/schemas/ResourceList', }, }, }, required: true, }, responses: { '200': { description: 'Success', content: { 'application/fhir+json': { schema: { $ref: '#/components/schemas/ResourceList', }, }, }, }, }, }; } function buildDeletePath(): any { return { summary: 'Delete Resource', description: 'Delete Resource', operationId: 'deleteResource', parameters: [ { name: 'resourceType', in: 'path', description: 'Resource Type', required: true, schema: { type: 'string', }, }, { name: 'id', in: 'path', description: 'Resource ID', required: true, schema: { type: 'string', format: 'uuid', }, }, ], responses: { '204': { description: 'Success', }, }, }; } function buildPatchPath(): any { return { summary: 'Patch Resource', description: 'Patch Resource', operationId: 'patchResource', parameters: [ { name: 'resourceType', in: 'path', description: 'Resource Type', required: true, schema: { type: 'string', }, }, { name: 'id', in: 'path', description: 'Resource ID', required: true, schema: { type: 'string', format: 'uuid', }, }, ], responses: { '204': { description: 'Success', }, }, }; } function isResourceType(definition: JSONSchema4): boolean { const props = definition.properties; return !!(isResourceWithId(props) && 'meta' in props); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server