Skip to main content
Glama
SearchControlField.ts5.73 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { InternalSchemaElement, SearchRequest } from '@medplum/core'; import { getElementDefinition, getSearchParameter, getSearchParameterDetails, getSearchParameters, } from '@medplum/core'; import type { ResourceType, SearchParameter } from '@medplum/fhirtypes'; /** * The SearchControlField type describes a field in the search control. * * In a SearchRequest, a field is a simple string. Strings can be one of the following: * 1) Simple property names, which refer to InternalSchemaElement objects * 2) Search parameter names, which refer to SearchParameter resources * * Consider a few examples of how this becomes complicated. * * "name" (easy) * - element definition path="Patient.name" * - search parameter code="name" * * "birthDate" (medium) * - refers to the element definition path="Patient.birthDate" * - refers to the search parameter code="birthdate" (note the capitalization) * * "email" (hard) * - refers to the search parameter code="email" * - refers to the element definition path="Patient.telecom" * * In the last case, we start with the search parameter, and walk backwards to the * element definition in order to get type details for rendering. * * Overall, we want columns, fields, properties, and search parameters to feel seamless, * so we try our darndest to make this work. */ export interface SearchControlField { readonly name: string; readonly elementDefinition?: InternalSchemaElement; readonly searchParams?: SearchParameter[]; } /** * Returns the collection of field definitions for the search request. * @param search - The search request definition. * @returns An array of field definitions. */ export function getFieldDefinitions(search: SearchRequest): SearchControlField[] { const resourceType = search.resourceType; const fields = [] as SearchControlField[]; for (const name of search.fields || ['id', '_lastUpdated']) { fields.push(getFieldDefinition(resourceType, name)); } return fields; } /** * Return the field definition for a given field name. * Field names can be either property names or search parameter codes. * @param resourceType - The resource type. * @param name - The search field name (either property name or search parameter code). * @returns The field definition. */ function getFieldDefinition(resourceType: string, name: string): SearchControlField { if (name === '_lastUpdated') { return { name: '_lastUpdated', searchParams: [ { resourceType: 'SearchParameter', base: ['Resource' as ResourceType], code: '_lastUpdated', name: '_lastUpdated', type: 'date', expression: 'Resource.meta.lastUpdated', } as SearchParameter, ], }; } if (name === 'meta.versionId') { return { name: 'meta.versionId', searchParams: [ { resourceType: 'SearchParameter', base: ['Resource' as ResourceType], code: '_versionId', name: '_versionId', type: 'token', expression: 'Resource.meta.versionId', } as SearchParameter, ], }; } const exactElementDefinition = getElementDefinition(resourceType, name); const exactSearchParam = getSearchParameter(resourceType, name.toLowerCase()); // Best case: Exact match of element definition or search parameter. // Examples: ServiceRequest.subject, Patient.name, Patient.birthDate // In this case, we only show the one search parameter. if (exactElementDefinition && exactSearchParam) { return { name, elementDefinition: exactElementDefinition, searchParams: [exactSearchParam] }; } // Next best case: Exact match of element definition // Examples: Observation.value // In this case, there could be zero or more search parameters that are a function of the element definition. // So search for those search parameters. if (exactElementDefinition) { const allSearchParams = getSearchParameters(resourceType); let searchParams: SearchParameter[] | undefined = undefined; if (allSearchParams) { // To avoid matching names that happen to be prefixes of other names, e.g. id and identifier, // match ${resourceType}.${name} followed by a non-name character OR the end of the string // Name characters include letters, numbers, underscores, and hyphens const pathRegex = new RegExp(`${resourceType}\\.${name.replaceAll('[x]', '')}([^\\w-]|$)`); searchParams = Object.values(allSearchParams).filter((p) => !!p.expression && pathRegex.test(p?.expression)); if (searchParams.length === 0) { searchParams = undefined; } } return { name, elementDefinition: exactElementDefinition, searchParams }; } // Search parameter case: Exact match of search parameter // Examples: Observation.value-quantity, Patient.email // Here we have a search parameter, but no element definition. // Observation.value-quantity is a search parameter for the Observation.value element. // Patient.email is a search parameter for the Patient.telecom element. // So we need to walk backwards to find the element definition. if (exactSearchParam) { const details = getSearchParameterDetails(resourceType, exactSearchParam); return { name, elementDefinition: details.elementDefinitions?.[0], searchParams: [exactSearchParam] }; } // Worst case: no element definition and no search parameter. // This is probably a malformed URL that includes an unknown field. // We will render the column header, but all cells will be empty. return { name }; }

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