in src/Microsoft.Health.Fhir.Core/Features/Definition/SearchParameterDefinitionBuilder.cs [113:284]
private static List<(string ResourceType, SearchParameterInfo SearchParameter)> ValidateAndGetFlattenedList(
IReadOnlyCollection<ITypedElement> searchParamCollection,
IDictionary<string, SearchParameterInfo> uriDictionary,
IModelInfoProvider modelInfoProvider)
{
var issues = new List<OperationOutcomeIssue>();
var searchParameters = searchParamCollection.Select((x, entryIndex) =>
{
try
{
return new SearchParameterWrapper(x);
}
catch (ArgumentException)
{
AddIssue(Core.Resources.SearchParameterDefinitionInvalidResource, entryIndex);
return null;
}
}).ToList();
// Do the first pass to make sure all resources are SearchParameter.
for (int entryIndex = 0; entryIndex < searchParameters.Count; entryIndex++)
{
SearchParameterWrapper searchParameter = searchParameters[entryIndex];
if (searchParameter == null)
{
continue;
}
try
{
SearchParameterInfo searchParameterInfo = GetOrCreateSearchParameterInfo(searchParameter, uriDictionary);
uriDictionary.Add(searchParameter.Url, searchParameterInfo);
}
catch (FormatException)
{
AddIssue(Resources.SearchParameterDefinitionInvalidDefinitionUri, entryIndex);
continue;
}
catch (ArgumentException)
{
AddIssue(Resources.SearchParameterDefinitionDuplicatedEntry, searchParameter.Url);
continue;
}
}
EnsureNoIssues();
var validatedSearchParameters = new List<(string ResourceType, SearchParameterInfo SearchParameter)>
{
// _type is currently missing from the search params definition bundle, so we inject it in here.
(KnownResourceTypes.Resource, SearchParameterInfo.ResourceTypeSearchParameter),
};
// Do the second pass to make sure the definition is valid.
foreach (var searchParameter in searchParameters)
{
if (searchParameter == null)
{
continue;
}
// If this is a composite search parameter, then make sure components are defined.
if (string.Equals(searchParameter.Type, SearchParamType.Composite.GetLiteral(), StringComparison.OrdinalIgnoreCase))
{
if (modelInfoProvider.Version == FhirSpecification.R5 && _knownBrokenR5.Contains(new Uri(searchParameter.Url)))
{
continue;
}
var composites = searchParameter.Component;
if (composites.Count == 0)
{
AddIssue(Core.Resources.SearchParameterDefinitionInvalidComponent, searchParameter.Url);
continue;
}
SearchParameterInfo compositeSearchParameter = GetOrCreateSearchParameterInfo(searchParameter, uriDictionary);
for (int componentIndex = 0; componentIndex < composites.Count; componentIndex++)
{
ITypedElement component = composites[componentIndex];
var definitionUrl = GetComponentDefinition(component);
if (definitionUrl == null ||
!uriDictionary.TryGetValue(definitionUrl, out SearchParameterInfo componentSearchParameter))
{
AddIssue(
Core.Resources.SearchParameterDefinitionInvalidComponentReference,
searchParameter.Url,
componentIndex);
continue;
}
if (componentSearchParameter.Type == SearchParamType.Composite)
{
AddIssue(
Core.Resources.SearchParameterDefinitionComponentReferenceCannotBeComposite,
searchParameter.Url,
componentIndex);
continue;
}
if (string.IsNullOrWhiteSpace(component.Scalar("expression")?.ToString()))
{
AddIssue(
Core.Resources.SearchParameterDefinitionInvalidComponentExpression,
searchParameter.Url,
componentIndex);
continue;
}
compositeSearchParameter.Component[componentIndex].ResolvedSearchParameter = componentSearchParameter;
}
}
// Make sure the base is defined.
IReadOnlyList<string> bases = searchParameter.Base;
if (bases.Count == 0)
{
AddIssue(Core.Resources.SearchParameterDefinitionBaseNotDefined, searchParameter.Url);
continue;
}
for (int baseElementIndex = 0; baseElementIndex < bases.Count; baseElementIndex++)
{
var code = bases[baseElementIndex];
string baseResourceType = code;
// Make sure the expression is not empty unless they are known to have empty expression.
// These are special search parameters that searches across all properties and needs to be handled specially.
if (ShouldExcludeEntry(baseResourceType, searchParameter.Name, modelInfoProvider)
|| (modelInfoProvider.Version == FhirSpecification.R5 && _knownBrokenR5.Contains(new Uri(searchParameter.Url))))
{
continue;
}
else
{
if (string.IsNullOrWhiteSpace(searchParameter.Expression))
{
AddIssue(Core.Resources.SearchParameterDefinitionInvalidExpression, searchParameter.Url);
continue;
}
}
validatedSearchParameters.Add((baseResourceType, GetOrCreateSearchParameterInfo(searchParameter, uriDictionary)));
}
}
EnsureNoIssues();
return validatedSearchParameters;
void AddIssue(string format, params object[] args)
{
issues.Add(new OperationOutcomeIssue(
OperationOutcomeConstants.IssueSeverity.Fatal,
OperationOutcomeConstants.IssueType.Invalid,
string.Format(CultureInfo.InvariantCulture, format, args)));
}
void EnsureNoIssues()
{
if (issues.Count != 0)
{
throw new InvalidDefinitionException(
Core.Resources.SearchParameterDefinitionContainsInvalidEntry,
issues.ToArray());
}
}
}