From 0fad53c0904c63fafa848496989a2987c58a31fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Skowro=C5=84ski?= Date: Mon, 17 Feb 2025 14:49:41 +0100 Subject: [PATCH 1/9] `Try` resolvers with default implementation for propagating errors to the callee --- .../Specification/Source/CachedResolver.cs | 63 ++++++++++++----- .../Specification/Source/IResourceResolver.cs | 65 ++++++++++++++++- .../Source/InMemoryResourceResolver.cs | 42 ++++++++++- .../Specification/Source/MultiResolver.cs | 54 +++++++++++---- .../Specification/Source/ResolverException.cs | 43 ++++++++++++ .../Specification/Source/ResolverResult.cs | 42 +++++++++++ .../Source/SyncToAsyncResolver.cs | 15 +++- .../Source/CommonDirectorySource.cs | 36 ++++++++-- .../Specification/Source/CommonWebResolver.cs | 45 ++++++++---- .../Specification/Source/CommonZipSource.cs | 6 ++ .../Source/ResourceResolverExtensions.cs | 20 ++++-- .../ElementNodeTests.cs | 8 +++ .../ScopedNodeTests.cs | 8 +++ .../Specification/Source/DirectorySource.cs | 39 ++++++++++- .../Source/ResourceResolverExtensions.cs | 24 +++++-- .../Specification/Source/WebResolver.cs | 27 +++++--- .../Specification/Source/ZipSource.cs | 26 +++++-- .../Specification/Source/SnapshotSource.cs | 33 +++++++-- .../Source/ResourceResolverExtensions.cs | 6 +- .../Snapshot/InMemoryProfileResolver.cs | 16 ++++- .../Snapshot/SnapshotGeneratorTest.cs | 2 +- .../Snapshot/TestProfileArtifactSource.cs | 19 +++-- .../Snapshot/TimingSource.cs | 8 ++- .../Source/ResolverAndSourceExtensionTests.cs | 13 ++-- .../Source/ResourceResolverTests.cs | 16 ++--- .../Source/TerminologyTests.cs | 28 ++++++-- .../TimingSource.cs | 8 ++- .../DiscriminatorInterpreterTests.cs | 3 +- .../Snapshot/SnapshotGeneratorTest.cs | 2 +- .../Snapshot/TestProfileArtifactSource.cs | 19 +++-- .../Snapshot/TimingSource.cs | 8 ++- .../Source/ResolverAndSourceExtensionTests.cs | 13 ++-- .../Source/ResourceResolverTests.cs | 14 ++-- .../Source/TerminologyTests.cs | 32 +++++++-- .../TimingSource.cs | 8 ++- .../Specification/AsyncSources.cs | 69 +++++++++++++++---- 36 files changed, 710 insertions(+), 170 deletions(-) create mode 100644 src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs create mode 100644 src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs diff --git a/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs index 4a8ad7a716..6df1fd1b0f 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs @@ -32,8 +32,8 @@ public class CachedResolver : IResourceResolver, IAsyncResourceResolver /// Default expiration time for cached entries. public const int DEFAULT_CACHE_DURATION = 4 * 3600; // 4 hours - readonly Cache _resourcesByUri; - readonly Cache _resourcesByCanonical; + private readonly Cache _resourcesByUri; + private readonly Cache _resourcesByCanonical; /// Creates a new artifact resolver that caches loaded resources in memory. /// Resolver from which artifacts are initially resolved on a cache miss. @@ -46,8 +46,8 @@ public CachedResolver(ISyncOrAsyncResourceResolver source, int cacheDuration = D AsyncResolver = source.AsAsync(); CacheDuration = cacheDuration; - _resourcesByUri = new Cache(id => InternalResolveByUri(id), CacheDuration); - _resourcesByCanonical = new Cache(id => InternalResolveByCanonicalUri(id), CacheDuration); + _resourcesByUri = new(InternalResolveByUri, CacheDuration); + _resourcesByCanonical = new(InternalResolveByCanonicalUri, CacheDuration); } /// @@ -67,16 +67,16 @@ public CachedResolver(ISyncOrAsyncResourceResolver source, int cacheDuration = D /// [Obsolete("CachedResolver now works best with asynchronous resolvers. Use ResolveByUriAsync() instead.")] - public Resource ResolveByUri(string url) => TaskHelper.Await(() => ResolveByUriAsync(url)); - + public Resource ResolveByUri(string url) => TryResolveByUri(url).Value; + /// Retrieve the artifact with the specified url. /// The url of the target artifact. /// A instance, or null if unavailable. /// Return data from memory cache if available, otherwise load on demand from the internal artifact source. public async Task ResolveByUriAsync(string url) { - if (url == null) throw Error.ArgumentNull(nameof(url)); - return await _resourcesByUri.Get(url, CachedResolverLoadingStrategy.LoadOnDemand).ConfigureAwait(false); + var result = await TryResolveByUriAsync(url); + return result.Value; } /// @@ -90,6 +90,17 @@ public Resource ResolveByUri(string url, CachedResolverLoadingStrategy strategy) /// A instance, or null if unavailable. /// Return data from memory cache if available, otherwise load on demand from the internal artifact source. public async Task ResolveByUriAsync(string url, CachedResolverLoadingStrategy strategy) + { + var result = await TryResolveByUriAsync(url, strategy); + return result.Value; + } + + /// Retrieve the artifact with the specified url. + /// The url of the target artifact. + /// Option flag to control the loading strategy. + /// A instance, or null if unavailable. + /// Return data from memory cache if available, otherwise load on demand from the internal artifact source. + public async Task TryResolveByUriAsync(string url, CachedResolverLoadingStrategy strategy) { if (url == null) throw Error.ArgumentNull(nameof(url)); return await _resourcesByUri.Get(url, strategy).ConfigureAwait(false); @@ -97,7 +108,11 @@ public async Task ResolveByUriAsync(string url, CachedResolverLoadingS /// [Obsolete("CachedResolver now works best with asynchronous resolvers. Use ResolveByCanonicalUriAsync() instead.")] - public Resource ResolveByCanonicalUri(string url) => TaskHelper.Await(() => ResolveByCanonicalUriAsync(url)); + public Resource ResolveByCanonicalUri(string url) => TryResolveByCanonicalUri(url).Value; + + public ResolverResult TryResolveByUri(string uri) => TaskHelper.Await(() => TryResolveByUriAsync(uri)); + + public ResolverResult TryResolveByCanonicalUri(string uri) => TaskHelper.Await(() => TryResolveByCanonicalUriAsync(uri)); /// Retrieve the conformance resource with the specified canonical url. /// The canonical url of the target conformance resource. @@ -105,10 +120,14 @@ public async Task ResolveByUriAsync(string url, CachedResolverLoadingS /// Return data from memory cache if available, otherwise load on demand from the internal artifact source. public async Task ResolveByCanonicalUriAsync(string url) { - if (url == null) throw Error.ArgumentNull(nameof(url)); - return await _resourcesByCanonical.Get(url, CachedResolverLoadingStrategy.LoadOnDemand).ConfigureAwait(false); + var result = await TryResolveByCanonicalUriAsync(url); + return result.Value; } + public async Task TryResolveByUriAsync(string uri) => await TryResolveByUriAsync(uri, CachedResolverLoadingStrategy.LoadOnDemand).ConfigureAwait(false); + + public async Task TryResolveByCanonicalUriAsync(string uri) => await TryResolveByCanonicalUriAsync(uri, CachedResolverLoadingStrategy.LoadOnDemand).ConfigureAwait(false); + /// [Obsolete("CachedResolver now works best with asynchronous resolvers. Use ResolveByCanonicalUriAsync() instead.")] public Resource ResolveByCanonicalUri(string url, CachedResolverLoadingStrategy strategy) => @@ -121,8 +140,14 @@ public Resource ResolveByCanonicalUri(string url, CachedResolverLoadingStrategy /// Return data from memory cache if available, otherwise load on demand from the internal artifact source. public async Task ResolveByCanonicalUriAsync(string url, CachedResolverLoadingStrategy strategy) { - if (url == null) throw Error.ArgumentNull(nameof(url)); - return await _resourcesByCanonical.Get(url, strategy).ConfigureAwait(false); + var result = await TryResolveByCanonicalUriAsync(url, strategy).ConfigureAwait(false); + return result.Value; + } + + public async Task TryResolveByCanonicalUriAsync(string uri, CachedResolverLoadingStrategy strategy) + { + if (uri == null) throw Error.ArgumentNull(nameof(uri)); + return await _resourcesByCanonical.Get(uri, strategy).ConfigureAwait(false); } /// Clear the cache entry for the artifact with the specified url, if it exists. @@ -183,17 +208,17 @@ public LoadResourceEventArgs(string url, Resource resource) : base() /// Called when an artifact is loaded into the cache. protected virtual void OnLoad(string url, Resource resource) => Load?.Invoke(this, new LoadResourceEventArgs(url, resource)); - internal async Task InternalResolveByUri(string url) + internal async Task InternalResolveByUri(string url) { - var resource = await AsyncResolver.ResolveByUriAsync(url).ConfigureAwait(false); - OnLoad(url, resource); + var resource = await AsyncResolver.TryResolveByUriAsync(url).ConfigureAwait(false); + OnLoad(url, resource.Value); return resource; } - internal async Task InternalResolveByCanonicalUri(string url) + internal async Task InternalResolveByCanonicalUri(string url) { - var resource = await AsyncResolver.ResolveByCanonicalUriAsync(url).ConfigureAwait(false); - OnLoad(url, resource); + var resource = await AsyncResolver.TryResolveByCanonicalUriAsync(url).ConfigureAwait(false); + OnLoad(url, resource.Value); return resource; } diff --git a/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs index 367528452d..6a4ac1c4a4 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs @@ -19,12 +19,43 @@ public interface IResourceResolver : ISyncOrAsyncResourceResolver { /// Find a resource based on its relative or absolute uri. /// A resource uri. + [Obsolete("This does not provide information about the kind of error that lead to us returning null. Consider implementing TryResolveByUri instead.")] Resource ResolveByUri(string uri); - /// Find a (conformance) resource based on its canonical uri. /// The canonical url of a (conformance) resource. + [Obsolete("This does not provide information about the kind of error that lead to us returning null. Consider implementing TryResolveByCanonicalUri instead.")] Resource ResolveByCanonicalUri(string uri); + + /// Find a (conformance) resource based on its canonical uri. + /// The canonical url of a (conformance) resource. + /// with an actual resource, or the . + ResolverResult TryResolveByUri(string uri) + { +#pragma warning disable CS0618 // Type or member is obsolete + var resource = this.ResolveByUri(uri); +#pragma warning restore CS0618 // Type or member is obsolete + + if (resource is not null) + return resource; + + return ResolverException.NotFound(); + } + + /// Find a resource based on its relative or absolute uri. + /// A resource uri. + /// with an actual resource, or the . + ResolverResult TryResolveByCanonicalUri(string uri) + { +#pragma warning disable CS0618 // Type or member is obsolete + var resource = this.ResolveByCanonicalUri(uri); +#pragma warning restore CS0618 // Type or member is obsolete + + if (resource is not null) + return resource; + + return ResolverException.NotFound(); + } } @@ -34,12 +65,44 @@ public interface IAsyncResourceResolver : ISyncOrAsyncResourceResolver { /// Find a resource based on its relative or absolute uri. /// A resource uri. + [Obsolete("This does not provide information about the kind of error that lead to us returning null. Consider implementing TryResolveByUriAsync instead.")] Task ResolveByUriAsync(string uri); /// Find a (conformance) resource based on its canonical uri. /// The canonical url of a (conformance) resource. + [Obsolete("This does not provide information about the kind of error that lead to us returning null. Consider implementing TryResolveByCanonicalUriAsync instead.")] Task ResolveByCanonicalUriAsync(string uri); // IConformanceResource + + /// Find a resource based on its relative or absolute uri. + /// A resource uri. + /// with an actual resource, or the . + async Task TryResolveByUriAsync(string uri) + { +#pragma warning disable CS0618 // Type or member is obsolete + var resource = await this.ResolveByUriAsync(uri); +#pragma warning restore CS0618 // Type or member is obsolete + + if (resource is not null) + return resource; + + return ResolverException.NotFound(); + } + + /// Find a (conformance) resource based on its canonical uri. + /// The canonical url of a (conformance) resource. + /// with an actual resource, or the . + async Task TryResolveByCanonicalUriAsync(string uri) + { +#pragma warning disable CS0618 // Type or member is obsolete + var resource = await this.ResolveByCanonicalUriAsync(uri); +#pragma warning restore CS0618 // Type or member is obsolete + + if (resource is not null) + return resource; + + return ResolverException.NotFound(); + } } /// diff --git a/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs index 79257b6e62..28bb277261 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs @@ -94,10 +94,36 @@ private void add(Resource resource) : null; } + public ResolverResult TryResolveByUri(string uri) + { + var resource = _resources + .Where(r => r.Uri == uri) + .Select(r => r.Resource) + .FirstOrDefault(); + + if (resource is not null) + return resource; + + return new ResolverException("NOTFOUND", $"No resource matching the {nameof(uri)} found"); + } + + public ResolverResult TryResolveByCanonicalUri(string uri) + { + var resource = _resources + .Where(r => r.Url == uri) + .Select(r => r.Resource) + .FirstOrDefault(); + + if (resource is not null) + return resource; + + return new ResolverException("NOTFOUND", $"No resource matching the {nameof(uri)} found"); + } + /// public Resource? ResolveByCanonicalUri(string uri) { - return _resources.Where(r => r.Url == uri)?.Select(r => r.Resource).FirstOrDefault(); + return TryResolveByCanonicalUri(uri).Value; } /// @@ -105,11 +131,11 @@ private void add(Resource resource) { return Task.FromResult(ResolveByCanonicalUri(uri)); } - + /// public Resource? ResolveByUri(string uri) { - return _resources.Where(r => r.Uri == uri)?.Select(r => r.Resource).FirstOrDefault(); + return TryResolveByUri(uri).Value; } /// @@ -117,6 +143,16 @@ private void add(Resource resource) { return Task.FromResult(ResolveByUri(uri)); } + + public Task TryResolveByUriAsync(string uri) + { + return Task.FromResult(TryResolveByUri(uri)); + } + + public Task TryResolveByCanonicalUriAsync(string uri) + { + return Task.FromResult(TryResolveByCanonicalUri(uri)); + } } } diff --git a/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs index 9c102a6bc9..6f234011c9 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs @@ -73,53 +73,81 @@ public void Pop() [Obsolete("MultiResolver now works best with asynchronous resolvers. Use ResolveByUriAsync() instead.")] - public Resource ResolveByUri(string uri) => TaskHelper.Await(() => ResolveByUriAsync(uri)); + public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; public async Task ResolveByUriAsync(string uri) + { + var resource = await TryResolveByUriAsync(uri); + return resource.Value; + } + + [Obsolete("MultiResolver now works best with asynchronous resolvers. Use ResolveByCanonicalUriAsync() instead.")] + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + + [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] + public ResolverResult TryResolveByUri(string uri) => TaskHelper.Await(() => TryResolveByUriAsync(uri)); + + [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] + public ResolverResult TryResolveByCanonicalUri(string uri) => TaskHelper.Await(() => TryResolveByCanonicalUriAsync(uri)); + + public async Task ResolveByCanonicalUriAsync(string uri) + { + var resource = await TryResolveByCanonicalUriAsync(uri); + return resource.Value; + } + + public async Task TryResolveByUriAsync(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); + List innerErrors = new(); foreach (IAsyncResourceResolver source in allSourcesAsAsync()) { try { - var result = await source.ResolveByUriAsync(uri).ConfigureAwait(false); + var result = await source.TryResolveByUriAsync(uri).ConfigureAwait(false); - if (result != null) return result; + if (result.Success) + return result; + + innerErrors.Add(result.Error); } - catch(NotImplementedException) + catch(NotImplementedException ex) { + innerErrors.Add(ResolverException.NotImplemented(ex)); // Don't do anything, just try the next IArtifactSource } } // None of the IArtifactSources succeeded in returning a result - return null; + return ResolverException.MultiResolverNotFound(innerErrors); } - [Obsolete("MultiResolver now works best with asynchronous resolvers. Use ResolveByCanonicalUriAsync() instead.")] - public Resource ResolveByCanonicalUri(string uri) => TaskHelper.Await(() => ResolveByCanonicalUriAsync(uri)); - - public async Task ResolveByCanonicalUriAsync(string uri) + public async Task TryResolveByCanonicalUriAsync(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); + List innerErrors = new(); foreach (var source in allSourcesAsAsync()) { try { - var result = await source.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + var result = await source.TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); - if (result != null) return result; + if (result.Success) + return result; + + innerErrors.Add(result.Error); } - catch (NotImplementedException) + catch (NotImplementedException ex) { + innerErrors.Add(ResolverException.NotImplemented(ex)); // Don't do anything, just try the next IArtifactSource } } // None of the IArtifactSources succeeded in returning a result - return null; + return ResolverException.MultiResolverNotFound(innerErrors); } // Allow derived classes to override diff --git a/src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs b/src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs new file mode 100644 index 0000000000..49dfa174c7 --- /dev/null +++ b/src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs @@ -0,0 +1,43 @@ +using Hl7.Fhir.Model; +using Hl7.Fhir.Utility; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Hl7.Fhir.Specification.Source; + +public class ResolverException : CodedException +{ + public const string NOT_IMPLEMENTED = "RESOLVE101"; + public const string NOT_FOUND = "RESOLVE102"; + public const string SNAPSHOT_FAILURE = "RESOLVE103"; + public const string ARTIFACT_SUMMARY_NO_MATCH = "RESOLVE104"; + + public ResolverException(string errorCode, string message) : base(errorCode, message) + { + } + + public ResolverException(string errorCode, string message, Exception innerException) : base(errorCode, message, innerException) + { + } + + internal static ResolverException NotImplemented(Exception ex) => new(NOT_IMPLEMENTED, "Resolver does not implement the used Resolve method.", ex); + internal static ResolverException NotFound() => new(NOT_FOUND, "Resource could not be found."); + internal static ResolverException MultiResolverNotFound(List innerErrors) + { + var commaSeparatedErrors = string.Join(", ", innerErrors + .OrderBy(x => x.ErrorCode) + .Select(x => x.Message)); + + return new ResolverException(NOT_FOUND, $"None of the resolvers could find the resource. Following errors reported: {Environment.NewLine}{commaSeparatedErrors}", new AggregateException(innerErrors)); + } + public static ResolverException SnapshotOutcome(OperationOutcome generatorOutcome) + { + var outcomeMessages = string.Join(Environment.NewLine, generatorOutcome.Issue.Select(x => x.ToString())); + return new ResolverException(SNAPSHOT_FAILURE, outcomeMessages); + } + public static ResolverException ArtifactSummaryNoMatch(string uri) + { + return new ResolverException(ARTIFACT_SUMMARY_NO_MATCH, $"No summary matching the provided {nameof(uri)}: {uri}"); + } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs b/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs new file mode 100644 index 0000000000..816f78d88c --- /dev/null +++ b/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs @@ -0,0 +1,42 @@ +using Hl7.Fhir.Model; +using System.Diagnostics.CodeAnalysis; + +namespace Hl7.Fhir.Specification.Source; + +public readonly record struct ResolverResult +{ + public bool Success => Value != null; + +#if NET8_0_OR_GREATER + [MemberNotNullWhen(true, nameof(Success))] +#endif + public Resource Value { get; private init; } + +#if NET8_0_OR_GREATER + [MemberNotNullWhen(false, nameof(Success))] +#endif + public ResolverException Error { get; private init; } + +#if NET8_0_OR_GREATER + [SetsRequiredMembers] +#endif + public ResolverResult(Resource value) + { + Value = value ?? throw Utility.Error.ArgumentNull(nameof(value)); + Error = null; + } + + #if NET8_0_OR_GREATER + [SetsRequiredMembers] + #endif + public ResolverResult(ResolverException error) + { + Error = error; + Value = null; + } + + public static implicit operator bool(ResolverResult result) => result.Success; + + public static implicit operator ResolverResult(Resource value) => new(value); + public static implicit operator ResolverResult(ResolverException error) => new(error); +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Specification/Source/SyncToAsyncResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/SyncToAsyncResolver.cs index e9e1d1ba26..602ff23135 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/SyncToAsyncResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/SyncToAsyncResolver.cs @@ -7,6 +7,7 @@ */ using Hl7.Fhir.Model; +using Hl7.Fhir.Utility; using System; using System.Threading.Tasks; @@ -23,16 +24,26 @@ public class SyncToAsyncResolver : IAsyncResourceResolver public Task ResolveByUriAsync(string uri) { - var result = SyncResolver.ResolveByUri(uri); + var result = SyncResolver.TryResolveByUri(uri).Value; return Task.FromResult(result); } public Task ResolveByCanonicalUriAsync(string uri) { - var result = SyncResolver.ResolveByCanonicalUri(uri); + var result = SyncResolver.TryResolveByCanonicalUri(uri).Value; return Task.FromResult(result); } + + public Task TryResolveByUriAsync(string uri) + { + return Task.FromResult(SyncResolver.TryResolveByUri(uri)); + } + + public Task TryResolveByCanonicalUriAsync(string uri) + { + return Task.FromResult(SyncResolver.TryResolveByCanonicalUri(uri)); + } } public static class SyncToAsyncResolverExtensions diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs b/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs index 28e8ec0292..a1347f4140 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs @@ -579,20 +579,42 @@ public IEnumerable ListSummaries() /// Find a resource based on its relative or absolute uri. /// A resource uri. - public Resource? ResolveByUri(string uri) + public Resource? ResolveByUri(string uri) => TryResolveByUri(uri).Value; + + /// Find a (conformance) resource based on its canonical uri. + /// The canonical url of a (conformance) resource. + public Resource? ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + + public ResolverResult TryResolveByUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); var summary = GetSummaries().ResolveByUri(uri, _inspector); - return summary is not null ? loadResourceInternal(summary) : null; + + if(summary is null) + return ResolverException.ArtifactSummaryNoMatch(uri); + + var resource = loadResourceInternal(summary); + + if (resource is null) + return ResolverException.NotFound(); + + return resource; } - /// Find a (conformance) resource based on its canonical uri. - /// The canonical url of a (conformance) resource. - public Resource? ResolveByCanonicalUri(string uri) + public ResolverResult TryResolveByCanonicalUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); var summary = GetSummaries().ResolveByCanonicalUri(uri, _inspector); - return summary is not null ? loadResourceInternal(summary) : null; + + if(summary is null) + return ResolverException.ArtifactSummaryNoMatch(uri); + + var resource = loadResourceInternal(summary); + + if (resource is null) + return ResolverException.NotFound(); + + return resource; } #endregion @@ -1001,6 +1023,8 @@ protected IEnumerable GetFileNames() => GetSummaries().Where(s => s.Orig public Tasks.Task ResolveByUriAsync(string uri) => Tasks.Task.FromResult(ResolveByUri(uri)); public Tasks.Task ResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(ResolveByCanonicalUri(uri)); + public Tasks.Task TryResolveByUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); + public Tasks.Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByCanonicalUri(uri)); #endregion diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs b/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs index c74e7839ad..043b7d67c1 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs @@ -52,23 +52,30 @@ public CommonWebResolver(Func fhirClientFactory) public Resource? ResolveByUri(string uri) { - if (uri is null) throw Error.ArgumentNull(nameof(uri)); + return TryResolveByUri(uri).Value; + } + + public Resource? ResolveByCanonicalUri(string uri) + { + return TryResolveByCanonicalUri(uri).Value; + } + + public ResolverResult TryResolveByUri(string uri) + { + if (uri == null) throw Error.ArgumentNull(nameof(uri)); if (!ResourceIdentity.IsRestResourceIdentity(uri)) { - // Weakness in FhirClient, need to have the base :-( So return null if we cannot determine it. - return null; + return new ResolverException("NOTVALIDARG", "Provided uri is not a valid resource identity URI"); } - + var id = new ResourceIdentity(uri); - var client = _clientFactory(id.BaseUri); - client.Settings.Timeout = this.TimeOut; client.Settings.ParserSettings = this.ParserSettings; try { - var resultResource = TaskHelper.Await( () => client.ReadAsync(id)); + var resultResource = TaskHelper.Await(() => client.ReadAsync(id)); resultResource.SetOrigin(uri); LastError = null; return resultResource; @@ -76,23 +83,33 @@ public CommonWebResolver(Func fhirClientFactory) catch (FhirOperationException foe) { LastError = foe; - return null; + return new ResolverException("OPERATIONEXCEPTION", "Error occurred during Fhir operation", foe); } catch (WebException we) { LastError = we; - return null; + return new ResolverException("OPERATIONEXCEPTION", "Error occurred during web operation", we); } // Other runtime exceptions are fatal... } - public Resource? ResolveByCanonicalUri(string uri) + public ResolverResult TryResolveByCanonicalUri(string uri) => TryResolveByUri(uri); + + public async Tasks.Task ResolveByUriAsync(string uri) { - return ResolveByUri(uri); + var result = await TryResolveByUriAsync(uri).ConfigureAwait(false); + return result.Value; } - - public Tasks.Task ResolveByUriAsync(string uri) => Tasks.Task.FromResult(ResolveByUri(uri)); - public Tasks.Task ResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(ResolveByCanonicalUri(uri)); + + public async Tasks.Task ResolveByCanonicalUriAsync(string uri) + { + var result = await TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return result.Value; + } + + public Tasks.Task TryResolveByUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); + + public Tasks.Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByCanonicalUri(uri)); // Allow derived classes to override // http://blogs.msdn.com/b/jaredpar/archive/2011/03/18/debuggerdisplay-attribute-best-practices.aspx diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/CommonZipSource.cs b/src/Hl7.Fhir.Conformance/Specification/Source/CommonZipSource.cs index 4edd9b1eeb..a8f9136fde 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonZipSource.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonZipSource.cs @@ -161,8 +161,14 @@ public void Prepare() /// The canonical url of a (conformance) resource. public Resource? ResolveByCanonicalUri(string uri) => FileSource.ResolveByCanonicalUri(uri); + public ResolverResult TryResolveByUri(string uri) => FileSource.TryResolveByUri(uri); + public ResolverResult TryResolveByCanonicalUri(string uri) => FileSource.TryResolveByCanonicalUri(uri); + public Task ResolveByUriAsync(string uri) => FileSource.ResolveByUriAsync(uri); public Task ResolveByCanonicalUriAsync(string uri) => FileSource.ResolveByCanonicalUriAsync(uri); + + public Task TryResolveByUriAsync(string uri) => FileSource.TryResolveByUriAsync(uri); + public Task TryResolveByCanonicalUriAsync(string uri) => FileSource.TryResolveByCanonicalUriAsync(uri); #endregion diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/ResourceResolverExtensions.cs b/src/Hl7.Fhir.Conformance/Specification/Source/ResourceResolverExtensions.cs index 4e6a25a05e..00bfde921f 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/ResourceResolverExtensions.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/ResourceResolverExtensions.cs @@ -34,7 +34,8 @@ public static StructureDefinition FindExtensionDefinition(this IResourceResolver /// Returns a StructureDefinition if it is resolvable and defines an extension, otherwise null. public static async Tasks.Task FindExtensionDefinitionAsync(this IAsyncResourceResolver resolver, string uri) { - if (await resolver.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false) is not StructureDefinition sd) return null; + var result = await resolver.TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + if (result.Value is not StructureDefinition sd) return null; if (!sd.IsExtension) throw Error.Argument(nameof(uri), $"Found StructureDefinition at '{uri}', but is not an extension"); @@ -52,7 +53,10 @@ public static StructureDefinition FindStructureDefinition(this IResourceResolver /// /// The resolved StructureDefinition or null if it cannot be resolved or does not resolve to a StructureDefinition. public static async Tasks.Task FindStructureDefinitionAsync(this IAsyncResourceResolver resolver, string uri) - => await resolver.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false) as StructureDefinition; + { + var result = await resolver.TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return result.Value as StructureDefinition; + } /// [Obsolete("Using synchronous resolvers is not recommended anymore, use FindStructureDefinitionForCoreTypeAsync() instead.")] @@ -84,7 +88,10 @@ public static ValueSet FindValueSet(this IResourceResolver source, string uri) /// Find a ValueSet by canonical url. /// public static async Tasks.Task FindValueSetAsync(this IAsyncResourceResolver source, string uri) - => await source.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false) as ValueSet; + { + var result = await source.TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return result.Value as ValueSet; + } /// [Obsolete("Using synchronous resolvers is not recommended anymore, use FindCodeSystemAsync() instead.")] @@ -95,8 +102,9 @@ public static CodeSystem FindCodeSystem(this IResourceResolver source, string ur /// Find a CodeSystem by canonical url. /// public static async Tasks.Task FindCodeSystemAsync(this IAsyncResourceResolver source, string uri) - => await source.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false) as CodeSystem; - - + { + var result = await source.TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return result.Value as CodeSystem; + } } } \ No newline at end of file diff --git a/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs b/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs index 4610093f16..b4bd16a523 100644 --- a/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs @@ -393,6 +393,12 @@ public CustomResourceResolver() } public async Task ResolveByCanonicalUriAsync(string uri) + { + var resource = await TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return resource.Value; + } + + public async Task TryResolveByCanonicalUriAsync(string uri) { var sd = await _resolver.FindStructureDefinitionAsync(uri); if (!sd.HasSnapshot) @@ -405,6 +411,8 @@ public async Task ResolveByCanonicalUriAsync(string uri) return sd; } + public Task TryResolveByUriAsync(string uri) => throw new NotImplementedException(); + public Task ResolveByUriAsync(string uri) => throw new NotImplementedException(); } diff --git a/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs index d9c28ac5df..ba4216b40b 100644 --- a/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs @@ -297,6 +297,12 @@ public CCDAResourceResolver() public async Tasks.Task ResolveByCanonicalUriAsync(string uri) { + var result = await TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return result.Value; + } + + public async Tasks.Task TryResolveByCanonicalUriAsync(string uri) + { if (_cache.TryGetValue(uri, out StructureDefinition? sd)) return sd; @@ -310,6 +316,8 @@ public async Tasks.Task ResolveByCanonicalUriAsync(string uri) return sd; } + + public Tasks.Task TryResolveByUriAsync(string uri) => throw new NotImplementedException(); public Tasks.Task ResolveByUriAsync(string uri) => throw new NotImplementedException(); } diff --git a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs index aa0176b7c5..afaf1a2997 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs @@ -613,19 +613,49 @@ public Resource LoadBySummary(ArtifactSummary summary) /// Find a resource based on its relative or absolute uri. /// A resource uri. public Resource ResolveByUri(string uri) + { + return TryResolveByUri(uri).Value; + } + + /// Find a (conformance) resource based on its canonical uri. + /// The canonical url of a (conformance) resource. + public Resource ResolveByCanonicalUri(string uri) + { + return TryResolveByCanonicalUri(uri).Value; + } + + public ResolverResult TryResolveByUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); var summary = GetSummaries().ResolveByUri(uri); - return loadResourceInternal(summary); + + if(summary is null) + return ResolverException.ArtifactSummaryNoMatch(uri); + + var resource = loadResourceInternal(summary); + + if (resource is null) + return ResolverException.NotFound(); + + return resource; } /// Find a (conformance) resource based on its canonical uri. /// The canonical url of a (conformance) resource. - public Resource ResolveByCanonicalUri(string uri) + public ResolverResult TryResolveByCanonicalUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); var summary = GetSummaries().ResolveByCanonicalUri(uri); - return loadResourceInternal(summary); + + if(summary is null) + return ResolverException.ArtifactSummaryNoMatch(uri); + + var resource = loadResourceInternal(summary); + + if (resource is null) + return ResolverException.NotFound(); + + return resource; } #endregion @@ -1007,6 +1037,9 @@ ConfigurableNavigatorStreamFactory GetNavigatorStreamFactory() public Tasks.Task ResolveByUriAsync(string uri) => Tasks.Task.FromResult(ResolveByUri(uri)); public Tasks.Task ResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(ResolveByCanonicalUri(uri)); + public Tasks.Task TryResolveByUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); + + public Tasks.Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByCanonicalUri(uri)); #endregion diff --git a/src/Hl7.Fhir.STU3/Specification/Source/ResourceResolverExtensions.cs b/src/Hl7.Fhir.STU3/Specification/Source/ResourceResolverExtensions.cs index 5c8967f287..f865d64dbf 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/ResourceResolverExtensions.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/ResourceResolverExtensions.cs @@ -38,7 +38,9 @@ public static StructureDefinition FindExtensionDefinition(this IResourceResolver /// Returns a StructureDefinition if it is resolvable and defines an extension, otherwise null. public static async Tasks.Task FindExtensionDefinitionAsync(this IAsyncResourceResolver resolver, string uri) { - if (await resolver.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false) is not StructureDefinition sd) return null; + var result = await resolver.TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + + if (result.Value is not StructureDefinition sd) return null; if (!sd.IsExtension) throw Error.Argument(nameof(uri), $"Found StructureDefinition at '{uri}', but is not an extension"); @@ -56,7 +58,10 @@ public static StructureDefinition FindStructureDefinition(this IResourceResolver /// /// The resolved StructureDefinition or null if it cannot be resolved or does not resolve to a StructureDefinition. public static async Tasks.Task FindStructureDefinitionAsync(this IAsyncResourceResolver resolver, string uri) - => await resolver.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false) as StructureDefinition; + { + var result = await resolver.TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return result.Value as StructureDefinition; + } /// [Obsolete("Using synchronous resolvers is not recommended anymore, use FindStructureDefinitionForCoreTypeAsync() instead.")] @@ -97,7 +102,10 @@ public static ValueSet FindValueSet(this IResourceResolver source, string uri) /// Find a ValueSet by canonical url. /// public static async Tasks.Task FindValueSetAsync(this IAsyncResourceResolver source, string uri) - => await source.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false) as ValueSet; + { + var result = await source.TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return result.Value as ValueSet; + } /// [Obsolete("Using synchronous resolvers is not recommended anymore, use FindCodeSystemAsync() instead.")] @@ -108,7 +116,10 @@ public static CodeSystem FindCodeSystem(this IResourceResolver source, string ur /// Find a CodeSystem by canonical url. /// public static async Tasks.Task FindCodeSystemAsync(this IAsyncResourceResolver source, string uri) - => await source.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false) as CodeSystem; + { + var result = await source.TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return result.Value as CodeSystem; + } public static IEnumerable FindAll(this IConformanceSource source) where T : Resource { @@ -118,7 +129,10 @@ public static IEnumerable FindAll(this IConformanceSource source) where T { var resourceType = EnumUtility.ParseLiteral(type); var uris = source.ListResourceUris(resourceType); - return uris.Select(u => source.ResolveByUri(u) as T).Where(r => r != null); + return uris.Select(source.TryResolveByUri) + .Where(r => r.Success) + .Select(x => x.Value as T) + .Where(r => r != null); } else return null; diff --git a/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs b/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs index 01652c774a..08fcd86394 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs @@ -51,14 +51,23 @@ public WebResolver(Func fhirClientFactory) public Exception LastError { get; private set; } public Resource ResolveByUri(string uri) + { + return TryResolveByUri(uri).Value; + } + + public Resource ResolveByCanonicalUri(string uri) + { + return ResolveByUri(uri); + } + + public ResolverResult TryResolveByUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); if (!ResourceIdentity.IsRestResourceIdentity(uri)) { - // Weakness in FhirClient, need to have the base :-( So return null if we cannot determine it. - return null; + return new ResolverException("NOTVALIDARG", "Provided uri is not a valid resource identity URI"); } - + var id = new ResourceIdentity(uri); var client = _clientFactory?.Invoke(id.BaseUri) ?? new FhirClient(id.BaseUri); @@ -75,23 +84,21 @@ public Resource ResolveByUri(string uri) catch (FhirOperationException foe) { LastError = foe; - return null; + return new ResolverException("OPERATIONEXCEPTION", "Error occurred during Fhir operation", foe); } catch (WebException we) { LastError = we; - return null; + return new ResolverException("OPERATIONEXCEPTION", "Error occurred during web operation", we); } // Other runtime exceptions are fatal... } - public Resource ResolveByCanonicalUri(string uri) - { - return ResolveByUri(uri); - } - + public ResolverResult TryResolveByCanonicalUri(string uri) => TryResolveByUri(uri); public Tasks.Task ResolveByUriAsync(string uri) => Tasks.Task.FromResult(ResolveByUri(uri)); public Tasks.Task ResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(ResolveByCanonicalUri(uri)); + public Tasks.Task TryResolveByUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); + public Tasks.Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); // Allow derived classes to override // http://blogs.msdn.com/b/jaredpar/archive/2011/03/18/debuggerdisplay-attribute-best-practices.aspx diff --git a/src/Hl7.Fhir.STU3/Specification/Source/ZipSource.cs b/src/Hl7.Fhir.STU3/Specification/Source/ZipSource.cs index caee1fd84c..2e915fcca1 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/ZipSource.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/ZipSource.cs @@ -221,14 +221,32 @@ public IEnumerable FindConceptMaps(string sourceUri = null, string t /// Find a resource based on its relative or absolute uri. /// A resource uri. - public Resource ResolveByUri(string uri) => FileSource.ResolveByUri(uri); + public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; /// Find a (conformance) resource based on its canonical uri. /// The canonical url of a (conformance) resource. - public Resource ResolveByCanonicalUri(string uri) => FileSource.ResolveByCanonicalUri(uri); + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; - public Task ResolveByUriAsync(string uri) => FileSource.ResolveByUriAsync(uri); - public Task ResolveByCanonicalUriAsync(string uri) => FileSource.ResolveByCanonicalUriAsync(uri); + /// Find a resource based on its relative or absolute uri. + /// A resource uri. + public ResolverResult TryResolveByUri(string uri) => FileSource.TryResolveByUri(uri); + /// Find a (conformance) resource based on its canonical uri. + /// The canonical url of a (conformance) resource. + public ResolverResult TryResolveByCanonicalUri(string uri) => FileSource.TryResolveByCanonicalUri(uri); + + public async Task ResolveByUriAsync(string uri) + { + var result = await TryResolveByUriAsync(uri).ConfigureAwait(false); + return result.Value; + } + public async Task ResolveByCanonicalUriAsync(string uri) + { + var result = await TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return result.Value; + } + + public Task TryResolveByUriAsync(string uri) => FileSource.TryResolveByUriAsync(uri); + public Task TryResolveByCanonicalUriAsync(string uri) => FileSource.TryResolveByCanonicalUriAsync(uri); #endregion diff --git a/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs b/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs index 7d8d30ac83..ddcc424f8d 100644 --- a/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs +++ b/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs @@ -74,31 +74,52 @@ public SnapshotSource(ISyncOrAsyncResourceResolver source) /// Find a resource based on its relative or absolute uri. /// The source ensures that resolved instances have a snapshot component. - public async Tasks.Task ResolveByUriAsync(string uri) => await ensureSnapshot(await _resolver.ResolveByUriAsync(uri).ConfigureAwait(false)).ConfigureAwait(false); + public async Tasks.Task ResolveByUriAsync(string uri) + { + var result = await TryResolveByUriAsync(uri).ConfigureAwait(false); + return result.Value; + } /// [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use ResolveByUriAsync() instead.")] - public Resource ResolveByUri(string uri) => TaskHelper.Await(() => ResolveByUriAsync(uri)); + public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; /// Find a (conformance) resource based on its canonical uri. /// The source ensures that resolved instances have a snapshot component. - public async Tasks.Task ResolveByCanonicalUriAsync(string uri) => await ensureSnapshot(await _resolver.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false)).ConfigureAwait(false); + public async Tasks.Task ResolveByCanonicalUriAsync(string uri) + { + var result = await TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return result.Value; + } + + public async Tasks.Task TryResolveByUriAsync(string uri) => await ensureSnapshot(await _resolver.TryResolveByUriAsync(uri).ConfigureAwait(false)).ConfigureAwait(false); + + public async Tasks.Task TryResolveByCanonicalUriAsync(string uri) => await ensureSnapshot(await _resolver.TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false)).ConfigureAwait(false); /// [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use ResolveByCanonicalUriAsync() instead.")] - public Resource ResolveByCanonicalUri(string uri) => TaskHelper.Await(() => ResolveByCanonicalUriAsync(uri)); + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + + public ResolverResult TryResolveByUri(string uri) => TaskHelper.Await(() => TryResolveByUriAsync(uri)); + + public ResolverResult TryResolveByCanonicalUri(string uri) => TaskHelper.Await(() => TryResolveByCanonicalUriAsync(uri)); #endregion // If the specified resource is a StructureDefinition, // then ensure snapshot component is available, (re-)generate on demand - private async Tasks.Task ensureSnapshot(Resource res) + private async Tasks.Task ensureSnapshot(ResolverResult res) { - if (res is StructureDefinition sd) + if (res.Value is StructureDefinition sd) { if (!sd.HasSnapshot || Generator.Settings.ForceRegenerateSnapshots || !sd.Snapshot.IsCreatedBySnapshotGenerator()) { await Generator.UpdateAsync(sd).ConfigureAwait(false); + + if(!Generator.Outcome.Success) + { + return ResolverException.SnapshotOutcome(Generator.Outcome); + } } } return res; diff --git a/src/Hl7.Fhir.Shims.R4AndUp/Specification/Source/ResourceResolverExtensions.cs b/src/Hl7.Fhir.Shims.R4AndUp/Specification/Source/ResourceResolverExtensions.cs index 52deb68336..b6f100f4ea 100644 --- a/src/Hl7.Fhir.Shims.R4AndUp/Specification/Source/ResourceResolverExtensions.cs +++ b/src/Hl7.Fhir.Shims.R4AndUp/Specification/Source/ResourceResolverExtensions.cs @@ -42,7 +42,11 @@ public static IEnumerable FindAll(this IConformanceSource source) where T { var resourceType = EnumUtility.ParseLiteral(typeName); var uris = source.ListResourceUris(resourceType); - return uris.Select(u => source.ResolveByUri(u) as T).Where(r => r != null); + return uris + .Select(source.TryResolveByUri) + .Where(x => x.Success) + .Select(x => x.Value as T) + .Where(r => r != null); } else return null; diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/InMemoryProfileResolver.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/InMemoryProfileResolver.cs index fb008db62e..f29a41db7f 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/InMemoryProfileResolver.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/InMemoryProfileResolver.cs @@ -33,12 +33,24 @@ public void Reload(IEnumerable profiles) #region IResourceResolver - public Resource ResolveByCanonicalUri(string uri) => _resources[uri].FirstOrDefault(); + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + public ResolverResult TryResolveByUri(string uri) => ResolverException.NotFound(); - public Resource ResolveByUri(string uri) => null; + public ResolverResult TryResolveByCanonicalUri(string uri) + { + var resource = _resources[uri].FirstOrDefault(); + if (resource is not null) + return resource; + + return ResolverException.NotFound(); + } + + public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; public Tasks.Task ResolveByUriAsync(string uri) => Tasks.Task.FromResult(ResolveByCanonicalUri(uri)); public Tasks.Task ResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(ResolveByCanonicalUri(uri)); + public Tasks.Task TryResolveByUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByCanonicalUri(uri)); + public Tasks.Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByCanonicalUri(uri)); #endregion diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/SnapshotGeneratorTest.cs index 70c6eeb39f..df5abe74fa 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -200,7 +200,7 @@ public async Tasks.Task OverriddenNestedStructureDefinitionLists() derivedSD.BaseDefinition = baseSD.Url; var resourceResolver = Substitute.For(); - resourceResolver.ResolveByCanonicalUri(Arg.Any()).Returns(baseSD); + resourceResolver.TryResolveByCanonicalUri(Arg.Any()).Returns(baseSD); var snapshotGenerator = new SnapshotGenerator(resourceResolver, new SnapshotGeneratorSettings()); await snapshotGenerator.UpdateAsync(derivedSD); diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/TestProfileArtifactSource.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/TestProfileArtifactSource.cs index a3e1384775..f69d768e26 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/TestProfileArtifactSource.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/TestProfileArtifactSource.cs @@ -299,16 +299,21 @@ private static StructureDefinition buildOrganizationWithRegexConstraintOnType() return result; } - public Resource ResolveByCanonicalUri(string uri) - { - return TestProfiles.SingleOrDefault(p => p.Url == uri); - } + + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + + public Resource ResolveByUri(string uri) => ResolveByCanonicalUri(uri); - public Resource ResolveByUri(string uri) + public ResolverResult TryResolveByUri(string uri) => TryResolveByCanonicalUri(uri); + + public ResolverResult TryResolveByCanonicalUri(string uri) { - return ResolveByCanonicalUri(uri); - } + var resource = TestProfiles.SingleOrDefault(p => p.Url == uri); + if(resource is not null) + return resource; + return ResolverException.NotFound(); + } private static StructureDefinition buildDutchPatient() { diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/TimingSource.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/TimingSource.cs index 49406b3e80..1f02ba43a6 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/TimingSource.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/TimingSource.cs @@ -27,9 +27,13 @@ public IEnumerable FindConceptMaps(string sourceUri = null, string t public IEnumerable ListResourceUris(ResourceType? filter = default(ResourceType?)) => _source.ListResourceUris(filter); // => measureDuration(() => _source.ListResourceUris(filter)); - public Resource ResolveByCanonicalUri(string uri) => measureDuration(() => _source.ResolveByCanonicalUri(uri)); + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + + public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; + + public ResolverResult TryResolveByUri(string uri) => measureDuration(() => _source.TryResolveByUri(uri)); - public Resource ResolveByUri(string uri) => measureDuration(() => _source.ResolveByUri(uri)); + public ResolverResult TryResolveByCanonicalUri(string uri) => measureDuration(() => _source.TryResolveByCanonicalUri(uri)); T measureDuration(Func f) { diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResolverAndSourceExtensionTests.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResolverAndSourceExtensionTests.cs index 26db890ff2..3a59d9cbc8 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResolverAndSourceExtensionTests.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResolverAndSourceExtensionTests.cs @@ -135,20 +135,21 @@ public async Tasks.Task FindStructureDefinitionForCoreTypeLogicalModel() private class LogicalModelTypeResourceResolver : IResourceResolver { - public Resource ResolveByCanonicalUri(string uri) + public ResolverResult TryResolveByCanonicalUri(string uri) { var customLogicalModelDataTypeXml = File.ReadAllText(Path.Combine("TestData/ccda", "CCDA_ANY.xml")); var sd = new FhirXmlParser().Parse(customLogicalModelDataTypeXml); if (sd.Type.Equals(uri)) return sd; - return null; + return ResolverException.NotFound(); } - public Resource ResolveByUri(string uri) - { - throw new NotImplementedException(); - } + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + + public ResolverResult TryResolveByUri(string uri) => throw new NotImplementedException(); + + public Resource ResolveByUri(string uri) => throw new NotImplementedException(); } } diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResourceResolverTests.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResourceResolverTests.cs index 758d8b5bc2..8a6b85be01 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResourceResolverTests.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResourceResolverTests.cs @@ -37,32 +37,32 @@ public static void SetupSource(TestContext _) [TestMethod] public void ResolveByCanonicalFromZip() { - var extDefn = source.ResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/data-absent-reason"); + var extDefn = source.TryResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/data-absent-reason").Value; Assert.IsNotNull(extDefn); Assert.IsInstanceOfType(extDefn, typeof(StructureDefinition)); - extDefn = source.ResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/Patient"); + extDefn = source.TryResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/Patient").Value; Assert.IsNotNull(extDefn); Assert.IsInstanceOfType(extDefn, typeof(StructureDefinition)); - extDefn = source.ResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/Patient"); + extDefn = source.TryResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/Patient").Value; Assert.IsNotNull(extDefn); Assert.IsInstanceOfType(extDefn, typeof(StructureDefinition)); var dirSource = new DirectorySource(Path.Combine("TestData", "validation")); - extDefn = dirSource.ResolveByCanonicalUri("http://example.com/StructureDefinition/patient-telecom-reslice-ek|1.0"); + extDefn = dirSource.TryResolveByCanonicalUri("http://example.com/StructureDefinition/patient-telecom-reslice-ek|1.0").Value; - Assert.ThrowsException(() => dirSource.ResolveByCanonicalUri("http://example.com/StructureDefinition/patient-telecom-reslice-ek|1.0|")); + Assert.ThrowsException(() => dirSource.TryResolveByCanonicalUri("http://example.com/StructureDefinition/patient-telecom-reslice-ek|1.0|")); } [TestMethod] public void ResolveByUriFromZip() { - var extDefn = source.ResolveByUri("http://hl7.org/fhir/StructureDefinition/data-absent-reason"); + var extDefn = source.TryResolveByUri("http://hl7.org/fhir/StructureDefinition/data-absent-reason").Value; Assert.IsNotNull(extDefn); Assert.IsInstanceOfType(extDefn, typeof(StructureDefinition)); - extDefn = source.ResolveByUri("http://hl7.org/fhir/StructureDefinition/Patient"); + extDefn = source.TryResolveByUri("http://hl7.org/fhir/StructureDefinition/Patient").Value; Assert.IsNotNull(extDefn); Assert.IsInstanceOfType(extDefn, typeof(StructureDefinition)); @@ -78,7 +78,7 @@ public void RetrieveWebArtifact() { var wa = new WebResolver() { TimeOut = DefaultTimeOut }; - var artifact = wa.ResolveByUri("http://test.fhir.org/r3/StructureDefinition/Observation"); + var artifact = wa.TryResolveByUri("http://test.fhir.org/r3/StructureDefinition/Observation").Value; Assert.IsNotNull(artifact); Assert.IsTrue(artifact is StructureDefinition); diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Source/TerminologyTests.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Source/TerminologyTests.cs index 0c756575d6..569120d168 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Source/TerminologyTests.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Source/TerminologyTests.cs @@ -25,7 +25,8 @@ public class TerminologyTests [Fact] public async Tasks.Task ExpansionOfWholeSystem() { - var issueTypeVs = (await _resolverWithoutExpansions.ResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/issue-type")).DeepCopy() as ValueSet; + var result = await _resolverWithoutExpansions.TryResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/issue-type"); + var issueTypeVs = result.Value.DeepCopy() as ValueSet; Assert.False(issueTypeVs.HasExpansion); // Wipe the version so we don't have to update our tests all the time @@ -64,7 +65,8 @@ public async Tasks.Task ExpansionOfWholeSystem() [Fact] public async Tasks.Task ExpansionOfComposeInclude() { - var testVs = (await _resolverWithoutExpansions.ResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/marital-status")).DeepCopy() as ValueSet; + var result = await _resolverWithoutExpansions.TryResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/marital-status"); + var testVs = result.Value.DeepCopy() as ValueSet; Assert.False(testVs.HasExpansion); var expander = new ValueSetExpander(new ValueSetExpanderSettings { ValueSetSource = _resolverWithoutExpansions }); @@ -76,7 +78,8 @@ public async Tasks.Task ExpansionOfComposeInclude() [Fact] public async Tasks.Task ExpansionOfComposeImport() { - var testVs = (await _resolverWithoutExpansions.ResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/v3-ObservationMethod")).DeepCopy() as ValueSet; + var result = await _resolverWithoutExpansions.TryResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/v3-ObservationMethod"); + var testVs = result.Value.DeepCopy() as ValueSet; Assert.False(testVs.HasExpansion); var expander = new ValueSetExpander(new ValueSetExpanderSettings { ValueSetSource = _resolverWithoutExpansions }); @@ -92,7 +95,8 @@ public async Tasks.Task ExpansionOfComposeImport() [Fact] public async Tasks.Task TestIncludeDesignation() { - var testVs = (await _resolverWithoutExpansions.ResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/animal-genderstatus")).DeepCopy() as ValueSet; + var result = await _resolverWithoutExpansions.TryResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/animal-genderstatus"); + var testVs = result.Value.DeepCopy() as ValueSet; Assert.False(testVs.HasExpansion); var expander = new ValueSetExpander(new ValueSetExpanderSettings { ValueSetSource = _resolverWithoutExpansions }); @@ -959,6 +963,10 @@ public async Task ResolveByCanonicalUriAsync(string uri) return await Tasks.Task.FromResult(uri == _myOnlyVS.Url ? _myOnlyVS : null); } + public Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(uri == _myOnlyVS.Url ? _myOnlyVS : null); + + public Task TryResolveByUriAsync(string uri) => throw new NotImplementedException(); + public Task ResolveByUriAsync(string uri) => throw new NotImplementedException(); } @@ -1008,10 +1016,20 @@ public CodeSystem FindCodeSystemByValueSet(string valueSetUri) public NamingSystem FindNamingSystem(string uniqueId) => throw new NotImplementedException(); public IEnumerable ListResourceUris(ResourceType? filter = null) => throw new NotImplementedException(); public Resource ResolveByCanonicalUri(string uri) => throw new NotImplementedException(); + public ResolverResult TryResolveByUri(string uri) => throw new NotImplementedException(); + + public ResolverResult TryResolveByCanonicalUri(string uri) => throw new NotImplementedException(); + public async Task ResolveByCanonicalUriAsync(string uri) { - return await Tasks.Task.FromResult(uri == _onlyCs.Url ? _onlyCs : null); + var result = await TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return result.Value; } + + public Task TryResolveByUriAsync(string uri) => throw new NotImplementedException(); + + public Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(uri == _onlyCs.Url ? _onlyCs : null); + public Resource ResolveByUri(string uri) => throw new NotImplementedException(); public Task ResolveByUriAsync(string uri) => throw new NotImplementedException(); } diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/TimingSource.cs b/src/Hl7.Fhir.Specification.STU3.Tests/TimingSource.cs index 61cde414b0..9a5e7ffd01 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/TimingSource.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/TimingSource.cs @@ -33,9 +33,13 @@ public IEnumerable FindConceptMaps(string sourceUri = null, string t public IEnumerable ListResourceUris(ResourceType? filter = default(ResourceType?)) => _source.ListResourceUris(filter); // => measureDuration(() => _source.ListResourceUris(filter)); - public Resource ResolveByCanonicalUri(string uri) => measureDuration(() => _source.ResolveByCanonicalUri(uri)); + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + + public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; + + public ResolverResult TryResolveByUri(string uri) => measureDuration(() => _source.TryResolveByUri(uri)); - public Resource ResolveByUri(string uri) => measureDuration(() => _source.ResolveByUri(uri)); + public ResolverResult TryResolveByCanonicalUri(string uri) => measureDuration(() => _source.TryResolveByCanonicalUri(uri)); T measureDuration(Func f) { diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/DiscriminatorInterpreterTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/DiscriminatorInterpreterTests.cs index 6e1ca2edc7..b19f045323 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/DiscriminatorInterpreterTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/DiscriminatorInterpreterTests.cs @@ -2,6 +2,7 @@ using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Specification.Source; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.IO; using System.Linq; using Tasks = System.Threading.Tasks; @@ -16,7 +17,7 @@ public static CachedResolver CreateTestResolver() return new CachedResolver( new SnapshotSource( new MultiResolver( - new DirectorySource(@"TestData\validation"), + new DirectorySource(Path.Combine("TestData", "validation")), ZipSource.CreateValidationSource()))); } diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorTest.cs index ba478bba0e..3e49bf28c7 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -207,7 +207,7 @@ public async Tasks.Task OverriddenNestedStructureDefinitionLists() derivedSD.BaseDefinition = baseSD.Url; var resourceResolver = Substitute.For(); - resourceResolver.ResolveByCanonicalUri(Arg.Any()).Returns(baseSD); + resourceResolver.TryResolveByCanonicalUri(Arg.Any()).Returns(baseSD); var snapshotGenerator = new SnapshotGenerator(resourceResolver, new SnapshotGeneratorSettings()); await snapshotGenerator.UpdateAsync(derivedSD); diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/TestProfileArtifactSource.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/TestProfileArtifactSource.cs index e6930bf72f..c7d1e396de 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/TestProfileArtifactSource.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/TestProfileArtifactSource.cs @@ -303,16 +303,21 @@ private static StructureDefinition buildOrganizationWithRegexConstraintOnType() return result; } - public Resource ResolveByCanonicalUri(string uri) - { - return TestProfiles.SingleOrDefault(p => p.Url == uri); - } + + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + + public Resource ResolveByUri(string uri) => ResolveByCanonicalUri(uri); - public Resource ResolveByUri(string uri) + public ResolverResult TryResolveByUri(string uri) => TryResolveByCanonicalUri(uri); + + public ResolverResult TryResolveByCanonicalUri(string uri) { - return ResolveByCanonicalUri(uri); - } + var resource = TestProfiles.SingleOrDefault(p => p.Url == uri); + if(resource is not null) + return resource; + return ResolverException.NotFound(); + } private static StructureDefinition buildDutchPatient() { diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/TimingSource.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/TimingSource.cs index 9774499040..60971edc30 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/TimingSource.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/TimingSource.cs @@ -27,9 +27,13 @@ public IEnumerable FindConceptMaps(string sourceUri = null, string t public IEnumerable ListResourceUris(ResourceType? filter = default(ResourceType?)) => _source.ListResourceUris(filter); // => measureDuration(() => _source.ListResourceUris(filter)); - public Resource ResolveByCanonicalUri(string uri) => measureDuration(() => _source.ResolveByCanonicalUri(uri)); + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + + public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; + + public ResolverResult TryResolveByUri(string uri) => measureDuration(() => _source.TryResolveByUri(uri)); - public Resource ResolveByUri(string uri) => measureDuration(() => _source.ResolveByUri(uri)); + public ResolverResult TryResolveByCanonicalUri(string uri) => measureDuration(() => _source.TryResolveByCanonicalUri(uri)); T measureDuration(Func f) { diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResolverAndSourceExtensionTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResolverAndSourceExtensionTests.cs index 0e0f98e895..51ad495485 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResolverAndSourceExtensionTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResolverAndSourceExtensionTests.cs @@ -137,20 +137,21 @@ public async Tasks.Task FindStructureDefinitionForCoreTypeLogicalModel() private class LogicalModelTypeResourceResolver : IResourceResolver { - public Resource ResolveByCanonicalUri(string uri) + public ResolverResult TryResolveByCanonicalUri(string uri) { var customLogicalModelDataTypeXml = File.ReadAllText(Path.Combine("TestData/ccda", "CCDA_ANY.xml")); var sd = new FhirXmlParser().Parse(customLogicalModelDataTypeXml); if (sd.Type.Equals(uri)) return sd; - return null; + return ResolverException.NotFound(); } - public Resource ResolveByUri(string uri) - { - throw new NotImplementedException(); - } + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + + public ResolverResult TryResolveByUri(string uri) => throw new NotImplementedException(); + + public Resource ResolveByUri(string uri) => throw new NotImplementedException(); } } diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResourceResolverTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResourceResolverTests.cs index 73f28a0acf..f7505ace3e 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResourceResolverTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResourceResolverTests.cs @@ -37,32 +37,32 @@ public static void SetupSource(TestContext _) [TestMethod] public void ResolveByCanonicalFromZip() { - var extDefn = source.ResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/data-absent-reason"); + var extDefn = source.TryResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/data-absent-reason").Value; Assert.IsNotNull(extDefn); Assert.IsInstanceOfType(extDefn, typeof(StructureDefinition)); - extDefn = source.ResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/Patient"); + extDefn = source.TryResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/Patient").Value; Assert.IsNotNull(extDefn); Assert.IsInstanceOfType(extDefn, typeof(StructureDefinition)); - extDefn = source.ResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/Patient"); + extDefn = source.TryResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/Patient").Value; Assert.IsNotNull(extDefn); Assert.IsInstanceOfType(extDefn, typeof(StructureDefinition)); var dirSource = new DirectorySource(Path.Combine("TestData", "validation")); - extDefn = dirSource.ResolveByCanonicalUri("http://example.com/StructureDefinition/patient-telecom-reslice-ek|1.0"); + extDefn = dirSource.TryResolveByCanonicalUri("http://example.com/StructureDefinition/patient-telecom-reslice-ek|1.0").Value; - Assert.ThrowsException(() => dirSource.ResolveByCanonicalUri("http://example.com/StructureDefinition/patient-telecom-reslice-ek|1.0|")); + Assert.ThrowsException(() => dirSource.TryResolveByCanonicalUri("http://example.com/StructureDefinition/patient-telecom-reslice-ek|1.0|").Value); } [TestMethod] public void ResolveByUriFromFhirPackage() { - var extDefn = source.ResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/data-absent-reason"); + var extDefn = source.TryResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/data-absent-reason").Value; Assert.IsNotNull(extDefn); Assert.IsInstanceOfType(extDefn, typeof(StructureDefinition)); - extDefn = source.ResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/Patient"); + extDefn = source.TryResolveByCanonicalUri("http://hl7.org/fhir/StructureDefinition/Patient").Value; Assert.IsNotNull(extDefn); Assert.IsInstanceOfType(extDefn, typeof(StructureDefinition)); diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Source/TerminologyTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Source/TerminologyTests.cs index a25f64f850..0d2d82f00a 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Source/TerminologyTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Source/TerminologyTests.cs @@ -3,6 +3,7 @@ using Hl7.Fhir.Rest; using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Specification.Terminology; +using Hl7.Fhir.Validation; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -25,7 +26,8 @@ public partial class TerminologyTests [Fact] public async Tasks.Task ExpansionOfWholeSystem() { - var issueTypeVs = (await _resolverWithoutExpansions.ResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/issue-type")).DeepCopy() as ValueSet; + var result = await _resolverWithoutExpansions.TryResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/issue-type"); + var issueTypeVs = result.Value.DeepCopy() as ValueSet; Assert.False(issueTypeVs.HasExpansion); // Wipe the version so we don't have to update our tests all the time @@ -75,7 +77,8 @@ public async Tasks.Task ExpansionOfWholeSystem() [Fact] public async Tasks.Task ExpansionOfComposeInclude() { - var testVs = (await _resolver.ResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/resource-security-category")).DeepCopy() as ValueSet; + var result = await _resolver.TryResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/resource-security-category"); + var testVs = result.Value.DeepCopy() as ValueSet; Assert.False(testVs.HasExpansion); var expander = new ValueSetExpander(new ValueSetExpanderSettings { ValueSetSource = _resolver }); @@ -87,7 +90,8 @@ public async Tasks.Task ExpansionOfComposeInclude() [Fact] public async Tasks.Task ExpansionOfComposeImport() { - var testVs = (await _resolverWithoutExpansions.ResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/FHIR-version")).DeepCopy() as ValueSet; + var result = await _resolverWithoutExpansions.TryResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/FHIR-version"); + var testVs = result.Value.DeepCopy() as ValueSet; Assert.False(testVs.HasExpansion); var expander = new ValueSetExpander(new ValueSetExpanderSettings { ValueSetSource = _resolverWithoutExpansions }); @@ -104,7 +108,8 @@ public async Tasks.Task ExpansionOfComposeImport() [Fact] public async Tasks.Task TestIncludeDesignation() { - var testVs = (await _resolver.ResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/animal-genderstatus")).DeepCopy() as ValueSet; + var result = await _resolver.TryResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/animal-genderstatus"); + var testVs = result.Value.DeepCopy() as ValueSet; Assert.False(testVs.HasExpansion); var expander = new ValueSetExpander(new ValueSetExpanderSettings { ValueSetSource = _resolver }); @@ -851,8 +856,13 @@ public async Task ResolveByCanonicalUriAsync(string uri) { return await Tasks.Task.FromResult(uri == _myOnlyVS.Url) ? _myOnlyVS : null; } - + public Task ResolveByUriAsync(string uri) => throw new NotImplementedException(); + + public Task TryResolveByUriAsync(string uri) => throw new NotImplementedException(); + + public Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(uri == _myOnlyVS.Url ? _myOnlyVS : ResolverException.NotFound()); + } private class OnlyCodeSystemResolver : IAsyncResourceResolver, ICommonConformanceSource @@ -899,10 +909,20 @@ public CodeSystem FindCodeSystemByValueSet(string valueSetUri) } public IEnumerable ListResourceUris(ResourceType? filter = null) => throw new NotImplementedException(); public Resource ResolveByCanonicalUri(string uri) => throw new NotImplementedException(); + public ResolverResult TryResolveByUri(string uri) => throw new NotImplementedException(); + + public ResolverResult TryResolveByCanonicalUri(string uri) => throw new NotImplementedException(); + public async Task ResolveByCanonicalUriAsync(string uri) { - return await Tasks.Task.FromResult(uri == _onlyCs.Url ? _onlyCs : null); + var resource = await TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); + return resource.Value; } + + public Task TryResolveByUriAsync(string uri) => throw new NotImplementedException(); + + public Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(uri == _onlyCs.Url ? _onlyCs : ResolverException.NotFound()); + public Resource ResolveByUri(string uri) => throw new NotImplementedException(); public Task ResolveByUriAsync(string uri) => throw new NotImplementedException(); public IEnumerable FindConceptMaps(string sourceUri = null, string targetUri = null) => throw new NotImplementedException(); diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/TimingSource.cs b/src/Hl7.Fhir.Specification.Shared.Tests/TimingSource.cs index cc3a39802f..fbf4c02a62 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/TimingSource.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/TimingSource.cs @@ -32,9 +32,13 @@ public IEnumerable FindConceptMaps(string sourceUri = null, string t public IEnumerable ListResourceUris(ResourceType? filter = default(ResourceType?)) => _source.ListResourceUris(filter); // => measureDuration(() => _source.ListResourceUris(filter)); - public Resource ResolveByCanonicalUri(string uri) => measureDuration(() => _source.ResolveByCanonicalUri(uri)); + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + + public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; + + public ResolverResult TryResolveByUri(string uri) => measureDuration(() => _source.TryResolveByUri(uri)); - public Resource ResolveByUri(string uri) => measureDuration(() => _source.ResolveByUri(uri)); + public ResolverResult TryResolveByCanonicalUri(string uri) => measureDuration(() => _source.TryResolveByCanonicalUri(uri)); T measureDuration(Func f) { diff --git a/src/Hl7.Fhir.Support.Tests/Specification/AsyncSources.cs b/src/Hl7.Fhir.Support.Tests/Specification/AsyncSources.cs index 2105653449..40180d346c 100644 --- a/src/Hl7.Fhir.Support.Tests/Specification/AsyncSources.cs +++ b/src/Hl7.Fhir.Support.Tests/Specification/AsyncSources.cs @@ -17,15 +17,15 @@ public async Task TestAdaptedSyncSource() var adaptee = new SyncResolver(); var adapted = adaptee.AsAsync(); - _ = await adapted.ResolveByUriAsync(""); - _ = await adapted.ResolveByCanonicalUriAsync(""); - _ = await adapted.ResolveByCanonicalUriAsync(""); + _ = await adapted.TryResolveByUriAsync(""); + _ = await adapted.TryResolveByCanonicalUriAsync(""); + _ = await adapted.TryResolveByCanonicalUriAsync(""); Assert.AreEqual(2, adaptee.ByCanonical); Assert.AreEqual(1, adaptee.ByUri); // Now call the async adapted sync resolver synchronously ;-) - TaskHelper.Await(() => adapted.ResolveByUriAsync("")); + TaskHelper.Await(() => adapted.TryResolveByUriAsync("")); Assert.AreEqual(2, adaptee.ByUri); } @@ -36,7 +36,7 @@ public async Task TestAsyncSyncMultiResolver() var multi = new MultiResolver(sr,ar,sar); // calling *any* kind of resolve will involve all child resolvers, since they all return null. - _ = await multi.ResolveByUriAsync(""); + _ = await multi.TryResolveByUriAsync(""); #pragma warning disable CS0618 // Type or member is obsolete _ = multi.ResolveByCanonicalUri(""); #pragma warning restore CS0618 // Type or member is obsolete @@ -106,15 +106,25 @@ public SyncResolver(Resource data) } public Resource ResolveByCanonicalUri(string uri) + { + return TryResolveByCanonicalUri(uri).Value; + } + + public ResolverResult TryResolveByUri(string uri) + { + ByUri += 1; + return Data is null ? ResolverException.NotFound() : Data; + } + + public ResolverResult TryResolveByCanonicalUri(string uri) { ByCanonical += 1; - return Data; + return Data is null ? ResolverException.NotFound() : Data; } public Resource ResolveByUri(string uri) { - ByUri += 1; - return Data; + return TryResolveByUri(uri).Value; } } @@ -131,13 +141,24 @@ public AsyncResolver(Resource data) Data = data; } - public Task ResolveByCanonicalUriAsync(string uri) { ByCanonicalAsync += 1; return Task.FromResult(Data); } + public Task TryResolveByUriAsync(string uri) + { + ByUriAsync += 1; + return Task.FromResult(Data is null ? ResolverException.NotFound() : Data); + } + + public Task TryResolveByCanonicalUriAsync(string uri) + { + ByCanonicalAsync += 1; + return Task.FromResult(Data is null ? ResolverException.NotFound() : Data); + } + public Task ResolveByUriAsync(string uri) { ByUriAsync += 1; @@ -163,23 +184,47 @@ public SyncAsyncResolver(Resource data) } public Resource ResolveByCanonicalUri(string uri) + { + return TryResolveByCanonicalUri(uri).Value; + } + + public ResolverResult TryResolveByUri(string uri) + { + ByUri += 1; + return Data is null ? ResolverException.NotFound() : Data; + } + + public ResolverResult TryResolveByCanonicalUri(string uri) { ByCanonical += 1; - return Data; + return Data is null ? ResolverException.NotFound() : Data; } public Resource ResolveByUri(string uri) { - ByUri += 1; - return Data; + return TryResolveByUri(uri).Value; } + + public Task ResolveByCanonicalUriAsync(string uri) { ByCanonicalAsync += 1; return Task.FromResult(Data); } + public Task TryResolveByUriAsync(string uri) + { + ByUriAsync += 1; + return Task.FromResult(Data is null ? ResolverException.NotFound() : Data); + } + + public Task TryResolveByCanonicalUriAsync(string uri) + { + ByCanonicalAsync += 1; + return Task.FromResult(Data is null ? ResolverException.NotFound() : Data); + } + public Task ResolveByUriAsync(string uri) { ByUriAsync += 1; From 87508893ec1b9423fdfb7cbd04d7b0568a154589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Skowro=C5=84ski?= Date: Mon, 17 Feb 2025 15:12:42 +0100 Subject: [PATCH 2/9] Fix testcase --- .../DiscriminatorInterpreterTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/DiscriminatorInterpreterTests.cs b/src/Hl7.Fhir.Specification.STU3.Tests/DiscriminatorInterpreterTests.cs index 6030fdc506..ed77b83d68 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/DiscriminatorInterpreterTests.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/DiscriminatorInterpreterTests.cs @@ -2,6 +2,7 @@ using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Specification.Source; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.IO; using System.Linq; using Tasks = System.Threading.Tasks; @@ -16,7 +17,7 @@ public static CachedResolver CreateTestResolver() return new CachedResolver( new SnapshotSource( new MultiResolver( - new DirectorySource(@"TestData\validation"), + new DirectorySource(Path.Combine("TestData", "validation")), new ZipSource("specification.zip")))); } From 5754303366e0b7ac41d6bdd16d75aaff46e96bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Skowro=C5=84ski?= Date: Mon, 17 Feb 2025 20:34:58 +0100 Subject: [PATCH 3/9] ConfigureAwait, cleanup --- .../Specification/Source/CachedResolver.cs | 6 +++--- .../Specification/Source/IResourceResolver.cs | 4 ++-- .../Source/InMemoryResourceResolver.cs | 4 ++-- .../Specification/Source/ResolverException.cs | 18 +++++++++++++++--- .../Specification/Source/CommonWebResolver.cs | 8 +++----- .../Specification/Source/WebResolver.cs | 8 +++----- 6 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs index 6df1fd1b0f..413209199f 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs @@ -75,7 +75,7 @@ public CachedResolver(ISyncOrAsyncResourceResolver source, int cacheDuration = D /// Return data from memory cache if available, otherwise load on demand from the internal artifact source. public async Task ResolveByUriAsync(string url) { - var result = await TryResolveByUriAsync(url); + var result = await TryResolveByUriAsync(url).ConfigureAwait(false); return result.Value; } @@ -91,7 +91,7 @@ public Resource ResolveByUri(string url, CachedResolverLoadingStrategy strategy) /// Return data from memory cache if available, otherwise load on demand from the internal artifact source. public async Task ResolveByUriAsync(string url, CachedResolverLoadingStrategy strategy) { - var result = await TryResolveByUriAsync(url, strategy); + var result = await TryResolveByUriAsync(url, strategy).ConfigureAwait(false); return result.Value; } @@ -120,7 +120,7 @@ public async Task TryResolveByUriAsync(string url, CachedResolve /// Return data from memory cache if available, otherwise load on demand from the internal artifact source. public async Task ResolveByCanonicalUriAsync(string url) { - var result = await TryResolveByCanonicalUriAsync(url); + var result = await TryResolveByCanonicalUriAsync(url).ConfigureAwait(false); return result.Value; } diff --git a/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs index 6a4ac1c4a4..070d9d0c9a 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs @@ -80,7 +80,7 @@ public interface IAsyncResourceResolver : ISyncOrAsyncResourceResolver async Task TryResolveByUriAsync(string uri) { #pragma warning disable CS0618 // Type or member is obsolete - var resource = await this.ResolveByUriAsync(uri); + var resource = await this.ResolveByUriAsync(uri).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete if (resource is not null) @@ -95,7 +95,7 @@ async Task TryResolveByUriAsync(string uri) async Task TryResolveByCanonicalUriAsync(string uri) { #pragma warning disable CS0618 // Type or member is obsolete - var resource = await this.ResolveByCanonicalUriAsync(uri); + var resource = await this.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete if (resource is not null) diff --git a/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs index 28bb277261..aef1b4d031 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs @@ -104,7 +104,7 @@ public ResolverResult TryResolveByUri(string uri) if (resource is not null) return resource; - return new ResolverException("NOTFOUND", $"No resource matching the {nameof(uri)} found"); + return ResolverException.NotFound(); } public ResolverResult TryResolveByCanonicalUri(string uri) @@ -117,7 +117,7 @@ public ResolverResult TryResolveByCanonicalUri(string uri) if (resource is not null) return resource; - return new ResolverException("NOTFOUND", $"No resource matching the {nameof(uri)} found"); + return ResolverException.NotFound(); } /// diff --git a/src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs b/src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs index 49dfa174c7..6f00974034 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs @@ -11,7 +11,9 @@ public class ResolverException : CodedException public const string NOT_IMPLEMENTED = "RESOLVE101"; public const string NOT_FOUND = "RESOLVE102"; public const string SNAPSHOT_FAILURE = "RESOLVE103"; - public const string ARTIFACT_SUMMARY_NO_MATCH = "RESOLVE104"; + public const string OPERATION_FAILURE = "RESOLVE104"; + public const string ARTIFACT_SUMMARY_NO_MATCH = "RESOLVE105"; + public const string INVALID_RESOURCE_IDENTITY = "RESOLVE106"; public ResolverException(string errorCode, string message) : base(errorCode, message) { @@ -31,13 +33,23 @@ internal static ResolverException MultiResolverNotFound(List return new ResolverException(NOT_FOUND, $"None of the resolvers could find the resource. Following errors reported: {Environment.NewLine}{commaSeparatedErrors}", new AggregateException(innerErrors)); } - public static ResolverException SnapshotOutcome(OperationOutcome generatorOutcome) + internal static ResolverException SnapshotOutcome(OperationOutcome generatorOutcome) { var outcomeMessages = string.Join(Environment.NewLine, generatorOutcome.Issue.Select(x => x.ToString())); return new ResolverException(SNAPSHOT_FAILURE, outcomeMessages); } - public static ResolverException ArtifactSummaryNoMatch(string uri) + + internal static ResolverException ArtifactSummaryNoMatch(string uri) { return new ResolverException(ARTIFACT_SUMMARY_NO_MATCH, $"No summary matching the provided {nameof(uri)}: {uri}"); } + + internal static ResolverException NotValidResourceIdentity(string uri) + { + return new ResolverException(INVALID_RESOURCE_IDENTITY, $"Provided {nameof(uri)} is not a valid resource identity URI: {uri}"); + } + internal static ResolverException OperationFailed(string message, Exception innerException) + { + return new ResolverException(OPERATION_FAILURE, message, innerException); + } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs b/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs index 043b7d67c1..01825291a5 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs @@ -64,9 +64,7 @@ public ResolverResult TryResolveByUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); if (!ResourceIdentity.IsRestResourceIdentity(uri)) - { - return new ResolverException("NOTVALIDARG", "Provided uri is not a valid resource identity URI"); - } + return ResolverException.NotValidResourceIdentity(uri); var id = new ResourceIdentity(uri); var client = _clientFactory(id.BaseUri); @@ -83,12 +81,12 @@ public ResolverResult TryResolveByUri(string uri) catch (FhirOperationException foe) { LastError = foe; - return new ResolverException("OPERATIONEXCEPTION", "Error occurred during Fhir operation", foe); + return ResolverException.OperationFailed("Error occurred during Fhir operation", foe); } catch (WebException we) { LastError = we; - return new ResolverException("OPERATIONEXCEPTION", "Error occurred during web operation", we); + return ResolverException.OperationFailed("Error occurred during web operation", we); } // Other runtime exceptions are fatal... } diff --git a/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs b/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs index 08fcd86394..acbef85c9f 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs @@ -64,9 +64,7 @@ public ResolverResult TryResolveByUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); if (!ResourceIdentity.IsRestResourceIdentity(uri)) - { - return new ResolverException("NOTVALIDARG", "Provided uri is not a valid resource identity URI"); - } + return ResolverException.NotValidResourceIdentity(uri); var id = new ResourceIdentity(uri); var client = _clientFactory?.Invoke(id.BaseUri) @@ -84,12 +82,12 @@ public ResolverResult TryResolveByUri(string uri) catch (FhirOperationException foe) { LastError = foe; - return new ResolverException("OPERATIONEXCEPTION", "Error occurred during Fhir operation", foe); + return ResolverException.OperationFailed("Error occurred during Fhir operation", foe); } catch (WebException we) { LastError = we; - return new ResolverException("OPERATIONEXCEPTION", "Error occurred during web operation", we); + return ResolverException.OperationFailed("Error occurred during web operation", we); } // Other runtime exceptions are fatal... } From 2f45193aaa15b7f3d6767dbbb9acd13f2e358e11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Skowro=C5=84ski?= Date: Mon, 17 Feb 2025 20:58:37 +0100 Subject: [PATCH 4/9] NullReferenceException snapshot --- src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs b/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs index ddcc424f8d..6c413db2ca 100644 --- a/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs +++ b/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs @@ -116,7 +116,7 @@ private async Tasks.Task ensureSnapshot(ResolverResult res) { await Generator.UpdateAsync(sd).ConfigureAwait(false); - if(!Generator.Outcome.Success) + if(Generator.Outcome?.Success is false) { return ResolverException.SnapshotOutcome(Generator.Outcome); } From 7fddb60c43b250031d0a30447f8b7d28ee5664d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Skowro=C5=84ski?= Date: Mon, 17 Feb 2025 22:38:06 +0100 Subject: [PATCH 5/9] Fix caching contains --- .../Specification/Source/CachedResolver.cs | 24 +++++++++---------- .../Source/TerminologyTests.cs | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs index 413209199f..83d13078d3 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs @@ -32,8 +32,8 @@ public class CachedResolver : IResourceResolver, IAsyncResourceResolver /// Default expiration time for cached entries. public const int DEFAULT_CACHE_DURATION = 4 * 3600; // 4 hours - private readonly Cache _resourcesByUri; - private readonly Cache _resourcesByCanonical; + private readonly ResolverCache _resourcesByUri; + private readonly ResolverCache _resourcesByCanonical; /// Creates a new artifact resolver that caches loaded resources in memory. /// Resolver from which artifacts are initially resolved on a cache miss. @@ -228,15 +228,15 @@ internal async Task InternalResolveByCanonicalUri(string url) internal protected virtual string DebuggerDisplay => $"{GetType().Name} for {AsyncResolver.DebuggerDisplayString()}"; - private class Cache + private class ResolverCache { - readonly Func> _onCacheMiss; + readonly Func> _onCacheMiss; readonly int _duration; readonly Object _getLock = new Object(); - readonly Dictionary> _cache = new Dictionary>(); + readonly Dictionary> _cache = new Dictionary>(); - public Cache(Func> onCacheMiss, int duration) + public ResolverCache(Func> onCacheMiss, int duration) { _onCacheMiss = onCacheMiss; _duration = duration; @@ -244,16 +244,16 @@ public Cache(Func> onCacheMiss, int duration) public bool Contains(string identifier) => - _cache.TryGetValue(identifier, out var entry) && !entry.IsExpired && entry.Data != null; + _cache.TryGetValue(identifier, out var entry) && !entry.IsExpired && entry.Data.Success; - public async Task Get(string identifier, CachedResolverLoadingStrategy strategy) + public async Task Get(string identifier, CachedResolverLoadingStrategy strategy) { lock (_getLock) { // Check the cache if (strategy != CachedResolverLoadingStrategy.LoadFromSource) { - if (_cache.TryGetValue(identifier, out CacheEntry entry)) + if (_cache.TryGetValue(identifier, out CacheEntry entry)) { // If we still have a fresh entry, return it if (!entry.IsExpired) @@ -271,14 +271,14 @@ public async Task Get(string identifier, CachedResolverLoadingStrategy strate if (strategy != CachedResolverLoadingStrategy.LoadFromCache) { // Otherwise, fetch it and cache it. - T newData = await _onCacheMiss(identifier).ConfigureAwait(false); + ResolverResult newData = await _onCacheMiss(identifier).ConfigureAwait(false); lock (_getLock) { // finally double check whether some other thread has not created and added it by now, // since we had to release the lock to run the async onCacheMiss. if (strategy != CachedResolverLoadingStrategy.LoadFromSource && - _cache.TryGetValue(identifier, out CacheEntry existingEntry)) + _cache.TryGetValue(identifier, out CacheEntry existingEntry)) return existingEntry.Data; else { @@ -286,7 +286,7 @@ public async Task Get(string identifier, CachedResolverLoadingStrategy strate // Note that an entry is created, even if the newData is null. // This ensures we don't keep trying to fetch the same url over and over again, // even if the source cannot resolve it. - _cache[identifier] = new CacheEntry(newData, identifier, DateTimeOffset.UtcNow.AddSeconds(_duration)); + _cache[identifier] = new CacheEntry(newData, identifier, DateTimeOffset.UtcNow.AddSeconds(_duration)); return newData; } } diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Source/TerminologyTests.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Source/TerminologyTests.cs index 569120d168..b7f1c5ba53 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Source/TerminologyTests.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Source/TerminologyTests.cs @@ -963,7 +963,7 @@ public async Task ResolveByCanonicalUriAsync(string uri) return await Tasks.Task.FromResult(uri == _myOnlyVS.Url ? _myOnlyVS : null); } - public Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(uri == _myOnlyVS.Url ? _myOnlyVS : null); + public Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(uri == _myOnlyVS.Url ? _myOnlyVS : ResolverException.NotFound()); public Task TryResolveByUriAsync(string uri) => throw new NotImplementedException(); @@ -1028,7 +1028,7 @@ public async Task ResolveByCanonicalUriAsync(string uri) public Task TryResolveByUriAsync(string uri) => throw new NotImplementedException(); - public Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(uri == _onlyCs.Url ? _onlyCs : null); + public Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(uri == _onlyCs.Url ? _onlyCs : ResolverException.NotFound()); public Resource ResolveByUri(string uri) => throw new NotImplementedException(); public Task ResolveByUriAsync(string uri) => throw new NotImplementedException(); From 1d0aa8641945165002f3be8e64e7fb23d269dd53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Skowro=C5=84ski?= Date: Tue, 18 Feb 2025 14:56:23 +0100 Subject: [PATCH 6/9] Fix test --- .../Specification/Source/ResolverResult.cs | 10 +++++++++- .../Specification/Source/SnapshotSource.cs | 2 +- .../StructureDefinitionWalkerTests.cs | 3 ++- .../StructureDefinitionWalkerTests.cs | 3 ++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs b/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs index 816f78d88c..08eb98907b 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs @@ -34,7 +34,15 @@ public ResolverResult(ResolverException error) Error = error; Value = null; } - + +#if NET8_0_OR_GREATER + [SetsRequiredMembers] +#endif + public ResolverResult(Resource value, ResolverException error) : this(value) + { + Error = error; + } + public static implicit operator bool(ResolverResult result) => result.Success; public static implicit operator ResolverResult(Resource value) => new(value); diff --git a/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs b/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs index 6c413db2ca..5edae3aa08 100644 --- a/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs +++ b/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs @@ -118,7 +118,7 @@ private async Tasks.Task ensureSnapshot(ResolverResult res) if(Generator.Outcome?.Success is false) { - return ResolverException.SnapshotOutcome(Generator.Outcome); + return new ResolverResult(sd, ResolverException.SnapshotOutcome(Generator.Outcome)); } } } diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/StructureDefinitionWalkerTests.cs b/src/Hl7.Fhir.Specification.STU3.Tests/StructureDefinitionWalkerTests.cs index d1bb180faa..283c47f5f4 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/StructureDefinitionWalkerTests.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/StructureDefinitionWalkerTests.cs @@ -3,6 +3,7 @@ using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Specification.Source; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.IO; using System.Linq; using Tasks = System.Threading.Tasks; @@ -17,7 +18,7 @@ public static IAsyncResourceResolver CreateTestResolver() return new CachedResolver( new SnapshotSource( new MultiResolver( - new DirectorySource(@"TestData\validation"), + new DirectorySource(Path.Combine("TestData", "validation")), ZipSource.CreateValidationSource()))); } diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/StructureDefinitionWalkerTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/StructureDefinitionWalkerTests.cs index d1bb180faa..283c47f5f4 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/StructureDefinitionWalkerTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/StructureDefinitionWalkerTests.cs @@ -3,6 +3,7 @@ using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Specification.Source; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.IO; using System.Linq; using Tasks = System.Threading.Tasks; @@ -17,7 +18,7 @@ public static IAsyncResourceResolver CreateTestResolver() return new CachedResolver( new SnapshotSource( new MultiResolver( - new DirectorySource(@"TestData\validation"), + new DirectorySource(Path.Combine("TestData", "validation")), ZipSource.CreateValidationSource()))); } From 8775de34f1004131a4fe5981c31ddb1f8cfef51a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Skowro=C5=84ski?= Date: Mon, 24 Feb 2025 20:23:24 +0100 Subject: [PATCH 7/9] Review remarks --- .../Specification/Source/CachedResolver.cs | 10 +++-- .../Specification/Source/IResourceResolver.cs | 18 ++++---- .../Specification/Source/MultiResolver.cs | 21 ++++----- .../Specification/Source/ResolverException.cs | 44 +++++++++++++++++- .../Specification/Source/ResolverResult.cs | 45 ++++++++++++++++++- .../Source/CommonDirectorySource.cs | 22 +++++++-- .../ScopedNodeTests.cs | 4 +- .../Specification/Source/SnapshotSource.cs | 28 ++++++------ 8 files changed, 149 insertions(+), 43 deletions(-) diff --git a/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs index 83d13078d3..67e030411e 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs @@ -66,7 +66,7 @@ public CachedResolver(ISyncOrAsyncResourceResolver source, int cacheDuration = D public int CacheDuration { get; } /// - [Obsolete("CachedResolver now works best with asynchronous resolvers. Use ResolveByUriAsync() instead.")] + [Obsolete("CachedResolver now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] public Resource ResolveByUri(string url) => TryResolveByUri(url).Value; /// Retrieve the artifact with the specified url. @@ -80,7 +80,7 @@ public async Task ResolveByUriAsync(string url) } /// - [Obsolete("CachedResolver now works best with asynchronous resolvers. Use ResolveByUriAsync() instead.")] + [Obsolete("CachedResolver now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] public Resource ResolveByUri(string url, CachedResolverLoadingStrategy strategy) => TaskHelper.Await(() => ResolveByUriAsync(url, strategy)); @@ -107,11 +107,13 @@ public async Task TryResolveByUriAsync(string url, CachedResolve } /// - [Obsolete("CachedResolver now works best with asynchronous resolvers. Use ResolveByCanonicalUriAsync() instead.")] + [Obsolete("CachedResolver now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] public Resource ResolveByCanonicalUri(string url) => TryResolveByCanonicalUri(url).Value; + [Obsolete("CachedResolver now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] public ResolverResult TryResolveByUri(string uri) => TaskHelper.Await(() => TryResolveByUriAsync(uri)); + [Obsolete("CachedResolver now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] public ResolverResult TryResolveByCanonicalUri(string uri) => TaskHelper.Await(() => TryResolveByCanonicalUriAsync(uri)); /// Retrieve the conformance resource with the specified canonical url. @@ -129,7 +131,7 @@ public async Task ResolveByCanonicalUriAsync(string url) public async Task TryResolveByCanonicalUriAsync(string uri) => await TryResolveByCanonicalUriAsync(uri, CachedResolverLoadingStrategy.LoadOnDemand).ConfigureAwait(false); /// - [Obsolete("CachedResolver now works best with asynchronous resolvers. Use ResolveByCanonicalUriAsync() instead.")] + [Obsolete("CachedResolver now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] public Resource ResolveByCanonicalUri(string url, CachedResolverLoadingStrategy strategy) => TaskHelper.Await(() => ResolveByCanonicalUriAsync(url, strategy)); diff --git a/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs index 070d9d0c9a..c40f8fb1bd 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs @@ -5,11 +5,11 @@ * This file is licensed under the BSD 3-Clause license * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ - using System; using System.Threading.Tasks; using Hl7.Fhir.Model; +#nullable enable namespace Hl7.Fhir.Specification.Source { /// Interface for resolving FHIR artifacts by (canonical) uri. @@ -19,13 +19,13 @@ public interface IResourceResolver : ISyncOrAsyncResourceResolver { /// Find a resource based on its relative or absolute uri. /// A resource uri. - [Obsolete("This does not provide information about the kind of error that lead to us returning null. Consider implementing TryResolveByUri instead.")] - Resource ResolveByUri(string uri); + [Obsolete("This method does not provide information about the kind of error that lead to us returning null. Consider using TryResolveByUri instead.")] + Resource? ResolveByUri(string uri); /// Find a (conformance) resource based on its canonical uri. /// The canonical url of a (conformance) resource. - [Obsolete("This does not provide information about the kind of error that lead to us returning null. Consider implementing TryResolveByCanonicalUri instead.")] - Resource ResolveByCanonicalUri(string uri); + [Obsolete("This method does not provide information about the kind of error that lead to us returning null. Consider using TryResolveByCanonicalUri instead.")] + Resource? ResolveByCanonicalUri(string uri); /// Find a (conformance) resource based on its canonical uri. /// The canonical url of a (conformance) resource. @@ -65,14 +65,14 @@ public interface IAsyncResourceResolver : ISyncOrAsyncResourceResolver { /// Find a resource based on its relative or absolute uri. /// A resource uri. - [Obsolete("This does not provide information about the kind of error that lead to us returning null. Consider implementing TryResolveByUriAsync instead.")] - Task ResolveByUriAsync(string uri); + [Obsolete("This method does not provide information about the kind of error that lead to us returning null. Consider using TryResolveByUriAsync instead.")] + Task ResolveByUriAsync(string uri); /// Find a (conformance) resource based on its canonical uri. /// The canonical url of a (conformance) resource. - [Obsolete("This does not provide information about the kind of error that lead to us returning null. Consider implementing TryResolveByCanonicalUriAsync instead.")] - Task ResolveByCanonicalUriAsync(string uri); // IConformanceResource + [Obsolete("This method does not provide information about the kind of error that lead to us returning null. Consider using TryResolveByCanonicalUriAsync instead.")] + Task ResolveByCanonicalUriAsync(string uri); // IConformanceResource /// Find a resource based on its relative or absolute uri. /// A resource uri. diff --git a/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs index 6f234011c9..b1c15328fa 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs @@ -13,7 +13,8 @@ using Hl7.Fhir.Utility; using System.Diagnostics; using System.Threading.Tasks; - + +#nullable enable namespace Hl7.Fhir.Specification.Source { /// @@ -72,16 +73,10 @@ public void Pop() private IEnumerable allSourcesAsAsync() => _sources.Select(src => src.AsAsync()); - [Obsolete("MultiResolver now works best with asynchronous resolvers. Use ResolveByUriAsync() instead.")] + [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; - public async Task ResolveByUriAsync(string uri) - { - var resource = await TryResolveByUriAsync(uri); - return resource.Value; - } - - [Obsolete("MultiResolver now works best with asynchronous resolvers. Use ResolveByCanonicalUriAsync() instead.")] + [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] @@ -90,7 +85,13 @@ public async Task ResolveByUriAsync(string uri) [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] public ResolverResult TryResolveByCanonicalUri(string uri) => TaskHelper.Await(() => TryResolveByCanonicalUriAsync(uri)); - public async Task ResolveByCanonicalUriAsync(string uri) + public async Task ResolveByUriAsync(string uri) + { + var resource = await TryResolveByUriAsync(uri); + return resource.Value; + } + + public async Task ResolveByCanonicalUriAsync(string uri) { var resource = await TryResolveByCanonicalUriAsync(uri); return resource.Value; diff --git a/src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs b/src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs index 6f00974034..e5d5f1b8fa 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs @@ -6,19 +6,56 @@ namespace Hl7.Fhir.Specification.Source; +/// +/// Exception reporting issues during resolving in and . +/// It is built on top of reporting errors as code and reacting to relevant issues appropriatelly. +/// public class ResolverException : CodedException { + /// + /// The used resolve operation was not implemented. + /// public const string NOT_IMPLEMENTED = "RESOLVE101"; + /// + /// The requested resource could not be found. + /// public const string NOT_FOUND = "RESOLVE102"; + /// + /// Failure during snapshot generation. + /// public const string SNAPSHOT_FAILURE = "RESOLVE103"; + /// + /// Failure during requested operation. + /// public const string OPERATION_FAILURE = "RESOLVE104"; + /// + /// No match has been found in the artifact summary for the requested uri. + /// public const string ARTIFACT_SUMMARY_NO_MATCH = "RESOLVE105"; - public const string INVALID_RESOURCE_IDENTITY = "RESOLVE106"; + /// + /// Artifact summary has been found, but required information to resolve the resource were missing. + /// + public const string ARTIFACT_SUMMARY_ARGUMENT_EXCEPTION = "RESOLVE106"; + /// + /// Resource identity does not represent a valid URI. + /// + public const string INVALID_RESOURCE_IDENTITY = "RESOLVE107"; + /// + /// Constructor for . + /// + /// Code relevant for the issue the exception represents + /// Description of the issues public ResolverException(string errorCode, string message) : base(errorCode, message) { } + /// + /// Constructor for . + /// + /// Code relevant for the issue the exception represents + /// Description of the issues + /// Inner exception that is being wrapped by resolver public ResolverException(string errorCode, string message, Exception innerException) : base(errorCode, message, innerException) { } @@ -43,6 +80,11 @@ internal static ResolverException ArtifactSummaryNoMatch(string uri) { return new ResolverException(ARTIFACT_SUMMARY_NO_MATCH, $"No summary matching the provided {nameof(uri)}: {uri}"); } + + internal static ResolverException ArtifactSummaryArgumentException(ArgumentException ex) + { + return new ResolverException(ARTIFACT_SUMMARY_ARGUMENT_EXCEPTION, ex.Message, ex); + } internal static ResolverException NotValidResourceIdentity(string uri) { diff --git a/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs b/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs index 08eb98907b..aee5c67a6c 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs @@ -1,22 +1,40 @@ using Hl7.Fhir.Model; +using System; using System.Diagnostics.CodeAnalysis; namespace Hl7.Fhir.Specification.Source; +/// +/// Choice type representing the current result of resolve operation +/// public readonly record struct ResolverResult { + /// + /// Whether the operation successfully retrieved a resource + /// public bool Success => Value != null; - + + /// + /// Value retrieved from resource resolver + /// #if NET8_0_OR_GREATER [MemberNotNullWhen(true, nameof(Success))] #endif public Resource Value { get; private init; } + /// + /// Error encountered while attempting retrieval of resource + /// #if NET8_0_OR_GREATER [MemberNotNullWhen(false, nameof(Success))] #endif public ResolverException Error { get; private init; } + /// + /// Constructor for successfully resolved resource + /// + /// Resolved resource + /// Resource provided was null #if NET8_0_OR_GREATER [SetsRequiredMembers] #endif @@ -26,6 +44,10 @@ public ResolverResult(Resource value) Error = null; } + /// + /// Constructor for failure to resolve, resulting in error + /// + /// Error encountered during resolve #if NET8_0_OR_GREATER [SetsRequiredMembers] #endif @@ -35,6 +57,12 @@ public ResolverResult(ResolverException error) Value = null; } + /// + /// Constructor for when a resource was successfully resolved, but additional initialization logic failed + /// + /// Resolved resource + /// Error encountered during additional initialization + /// Resource provided was null #if NET8_0_OR_GREATER [SetsRequiredMembers] #endif @@ -43,8 +71,23 @@ public ResolverResult(Resource value, ResolverException error) : this(value) Error = error; } + /// + /// Implicit conversion of the to boolean. + /// + /// Instance of + /// true if choice type has a . otherwise false. public static implicit operator bool(ResolverResult result) => result.Success; + /// + /// Implicit conversion of a resource to + /// + /// Resolved resource + /// representing the successfully retrieved . public static implicit operator ResolverResult(Resource value) => new(value); + /// + /// Implicit conversion of an error to + /// + /// Error encountered during resolving of resource + /// representing the provided error. public static implicit operator ResolverResult(ResolverException error) => new(error); } \ No newline at end of file diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs b/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs index a1347f4140..09a309c83a 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs @@ -592,8 +592,16 @@ public ResolverResult TryResolveByUri(string uri) if(summary is null) return ResolverException.ArtifactSummaryNoMatch(uri); - - var resource = loadResourceInternal(summary); + + Resource? resource; + try + { + resource = loadResourceInternal(summary); + } + catch(ArgumentException ex) + { + return ResolverException.ArtifactSummaryArgumentException(ex); + } if (resource is null) return ResolverException.NotFound(); @@ -609,7 +617,15 @@ public ResolverResult TryResolveByCanonicalUri(string uri) if(summary is null) return ResolverException.ArtifactSummaryNoMatch(uri); - var resource = loadResourceInternal(summary); + Resource? resource; + try + { + resource = loadResourceInternal(summary); + } + catch(ArgumentException ex) + { + return ResolverException.ArtifactSummaryArgumentException(ex); + } if (resource is null) return ResolverException.NotFound(); diff --git a/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs index ba4216b40b..2a6ac98e42 100644 --- a/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs @@ -295,7 +295,7 @@ public CCDAResourceResolver() new DirectorySource("TestData/TestSd"))); } - public async Tasks.Task ResolveByCanonicalUriAsync(string uri) + public async Tasks.Task ResolveByCanonicalUriAsync(string uri) { var result = await TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false); return result.Value; @@ -319,7 +319,7 @@ public async Tasks.Task TryResolveByCanonicalUriAsync(string uri public Tasks.Task TryResolveByUriAsync(string uri) => throw new NotImplementedException(); - public Tasks.Task ResolveByUriAsync(string uri) => throw new NotImplementedException(); + public Tasks.Task ResolveByUriAsync(string uri) => throw new NotImplementedException(); } private class TypedElementWithoutDefinition : ITypedElement, IResourceTypeSupplier diff --git a/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs b/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs index 5edae3aa08..ec1c2ce5ad 100644 --- a/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs +++ b/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs @@ -72,6 +72,20 @@ public SnapshotSource(ISyncOrAsyncResourceResolver source) private IAsyncResourceResolver _resolver => Generator.AsyncResolver; + /// + [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] + public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; + + /// + [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] + public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + + [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] + public ResolverResult TryResolveByUri(string uri) => TaskHelper.Await(() => TryResolveByUriAsync(uri)); + + [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] + public ResolverResult TryResolveByCanonicalUri(string uri) => TaskHelper.Await(() => TryResolveByCanonicalUriAsync(uri)); + /// Find a resource based on its relative or absolute uri. /// The source ensures that resolved instances have a snapshot component. public async Tasks.Task ResolveByUriAsync(string uri) @@ -79,11 +93,7 @@ public async Tasks.Task ResolveByUriAsync(string uri) var result = await TryResolveByUriAsync(uri).ConfigureAwait(false); return result.Value; } - - /// - [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use ResolveByUriAsync() instead.")] - public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; - + /// Find a (conformance) resource based on its canonical uri. /// The source ensures that resolved instances have a snapshot component. public async Tasks.Task ResolveByCanonicalUriAsync(string uri) @@ -96,14 +106,6 @@ public async Tasks.Task ResolveByCanonicalUriAsync(string uri) public async Tasks.Task TryResolveByCanonicalUriAsync(string uri) => await ensureSnapshot(await _resolver.TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false)).ConfigureAwait(false); - /// - [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use ResolveByCanonicalUriAsync() instead.")] - public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; - - public ResolverResult TryResolveByUri(string uri) => TaskHelper.Await(() => TryResolveByUriAsync(uri)); - - public ResolverResult TryResolveByCanonicalUri(string uri) => TaskHelper.Await(() => TryResolveByCanonicalUriAsync(uri)); - #endregion // If the specified resource is a StructureDefinition, From 779cb05d2e82dd878ff1ad23cd497578594b7704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Skowro=C5=84ski?= Date: Mon, 24 Feb 2025 20:51:37 +0100 Subject: [PATCH 8/9] More docs --- .../Specification/Source/CachedResolver.cs | 19 +++++++++++++++++-- .../Specification/Source/IResourceResolver.cs | 9 ++++----- .../Source/InMemoryResourceResolver.cs | 4 ++++ .../Specification/Source/MultiResolver.cs | 4 ++++ .../Source/SyncToAsyncResolver.cs | 2 ++ .../Source/CommonDirectorySource.cs | 2 ++ .../Specification/Source/CommonWebResolver.cs | 4 ++++ .../Specification/Source/CommonZipSource.cs | 4 ++++ .../Specification/Source/DirectorySource.cs | 4 ++-- .../Specification/Source/WebResolver.cs | 9 +++++++++ .../Specification/Source/ZipSource.cs | 2 ++ .../Specification/Source/SnapshotSource.cs | 14 ++++++++++++++ 12 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs index 67e030411e..9c5fdb57c8 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs @@ -98,7 +98,7 @@ public async Task ResolveByUriAsync(string url, CachedResolverLoadingS /// Retrieve the artifact with the specified url. /// The url of the target artifact. /// Option flag to control the loading strategy. - /// A instance, or null if unavailable. + /// A instance, with either or . /// Return data from memory cache if available, otherwise load on demand from the internal artifact source. public async Task TryResolveByUriAsync(string url, CachedResolverLoadingStrategy strategy) { @@ -110,9 +110,11 @@ public async Task TryResolveByUriAsync(string url, CachedResolve [Obsolete("CachedResolver now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] public Resource ResolveByCanonicalUri(string url) => TryResolveByCanonicalUri(url).Value; + /// [Obsolete("CachedResolver now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] public ResolverResult TryResolveByUri(string uri) => TaskHelper.Await(() => TryResolveByUriAsync(uri)); + /// [Obsolete("CachedResolver now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] public ResolverResult TryResolveByCanonicalUri(string uri) => TaskHelper.Await(() => TryResolveByCanonicalUriAsync(uri)); @@ -125,9 +127,17 @@ public async Task ResolveByCanonicalUriAsync(string url) var result = await TryResolveByCanonicalUriAsync(url).ConfigureAwait(false); return result.Value; } - + + /// Retrieve the artifact with the specified url. + /// The url of the target artifact. + /// A instance, with either or . + /// Return data from memory cache if available, otherwise load on demand from the internal artifact source. public async Task TryResolveByUriAsync(string uri) => await TryResolveByUriAsync(uri, CachedResolverLoadingStrategy.LoadOnDemand).ConfigureAwait(false); + /// Retrieve the conformance resource with the specified canonical url. + /// The canonical url of the target conformance resource. + /// A instance, with either or . + /// Return data from memory cache if available, otherwise load on demand from the internal artifact source. public async Task TryResolveByCanonicalUriAsync(string uri) => await TryResolveByCanonicalUriAsync(uri, CachedResolverLoadingStrategy.LoadOnDemand).ConfigureAwait(false); /// @@ -146,6 +156,11 @@ public async Task ResolveByCanonicalUriAsync(string url, CachedResolve return result.Value; } + /// Retrieve the conformance resource with the specified canonical url. + /// The canonical url of the target conformance resource. + /// Option flag to control the loading strategy. + /// A instance, with either or . + /// Return data from memory cache if available, otherwise load on demand from the internal artifact source. public async Task TryResolveByCanonicalUriAsync(string uri, CachedResolverLoadingStrategy strategy) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); diff --git a/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs index c40f8fb1bd..2a9e84e552 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs @@ -18,7 +18,6 @@ public interface IResourceResolver : ISyncOrAsyncResourceResolver #pragma warning restore CS0618 // Type or member is obsolete { /// Find a resource based on its relative or absolute uri. - /// A resource uri. [Obsolete("This method does not provide information about the kind of error that lead to us returning null. Consider using TryResolveByUri instead.")] Resource? ResolveByUri(string uri); @@ -27,8 +26,8 @@ public interface IResourceResolver : ISyncOrAsyncResourceResolver [Obsolete("This method does not provide information about the kind of error that lead to us returning null. Consider using TryResolveByCanonicalUri instead.")] Resource? ResolveByCanonicalUri(string uri); - /// Find a (conformance) resource based on its canonical uri. - /// The canonical url of a (conformance) resource. + /// Find a resource based on its relative or absolute uri. + /// A resource uri. /// with an actual resource, or the . ResolverResult TryResolveByUri(string uri) { @@ -42,8 +41,8 @@ ResolverResult TryResolveByUri(string uri) return ResolverException.NotFound(); } - /// Find a resource based on its relative or absolute uri. - /// A resource uri. + /// Find a (conformance) resource based on its canonical uri. + /// The canonical url of a (conformance) resource. /// with an actual resource, or the . ResolverResult TryResolveByCanonicalUri(string uri) { diff --git a/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs index aef1b4d031..1cf2ad7192 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs @@ -94,6 +94,7 @@ private void add(Resource resource) : null; } + /// public ResolverResult TryResolveByUri(string uri) { var resource = _resources @@ -107,6 +108,7 @@ public ResolverResult TryResolveByUri(string uri) return ResolverException.NotFound(); } + /// public ResolverResult TryResolveByCanonicalUri(string uri) { var resource = _resources @@ -144,11 +146,13 @@ public ResolverResult TryResolveByCanonicalUri(string uri) return Task.FromResult(ResolveByUri(uri)); } + /// public Task TryResolveByUriAsync(string uri) { return Task.FromResult(TryResolveByUri(uri)); } + /// public Task TryResolveByCanonicalUriAsync(string uri) { return Task.FromResult(TryResolveByCanonicalUri(uri)); diff --git a/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs index b1c15328fa..b177333107 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs @@ -79,9 +79,11 @@ public void Pop() [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + /// [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] public ResolverResult TryResolveByUri(string uri) => TaskHelper.Await(() => TryResolveByUriAsync(uri)); + /// [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] public ResolverResult TryResolveByCanonicalUri(string uri) => TaskHelper.Await(() => TryResolveByCanonicalUriAsync(uri)); @@ -97,6 +99,7 @@ public void Pop() return resource.Value; } + /// public async Task TryResolveByUriAsync(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); @@ -124,6 +127,7 @@ public async Task TryResolveByUriAsync(string uri) return ResolverException.MultiResolverNotFound(innerErrors); } + /// public async Task TryResolveByCanonicalUriAsync(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); diff --git a/src/Hl7.Fhir.Base/Specification/Source/SyncToAsyncResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/SyncToAsyncResolver.cs index 602ff23135..ce25da62aa 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/SyncToAsyncResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/SyncToAsyncResolver.cs @@ -35,11 +35,13 @@ public Task ResolveByCanonicalUriAsync(string uri) return Task.FromResult(result); } + /// public Task TryResolveByUriAsync(string uri) { return Task.FromResult(SyncResolver.TryResolveByUri(uri)); } + /// public Task TryResolveByCanonicalUriAsync(string uri) { return Task.FromResult(SyncResolver.TryResolveByCanonicalUri(uri)); diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs b/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs index 09a309c83a..bf9a999208 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs @@ -585,6 +585,7 @@ public IEnumerable ListSummaries() /// The canonical url of a (conformance) resource. public Resource? ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + /// public ResolverResult TryResolveByUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); @@ -609,6 +610,7 @@ public ResolverResult TryResolveByUri(string uri) return resource; } + /// public ResolverResult TryResolveByCanonicalUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs b/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs index 01825291a5..01d8b886fa 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs @@ -60,6 +60,7 @@ public CommonWebResolver(Func fhirClientFactory) return TryResolveByCanonicalUri(uri).Value; } + /// public ResolverResult TryResolveByUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); @@ -91,6 +92,7 @@ public ResolverResult TryResolveByUri(string uri) // Other runtime exceptions are fatal... } + /// public ResolverResult TryResolveByCanonicalUri(string uri) => TryResolveByUri(uri); public async Tasks.Task ResolveByUriAsync(string uri) @@ -105,8 +107,10 @@ public ResolverResult TryResolveByUri(string uri) return result.Value; } + /// public Tasks.Task TryResolveByUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); + /// public Tasks.Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByCanonicalUri(uri)); // Allow derived classes to override diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/CommonZipSource.cs b/src/Hl7.Fhir.Conformance/Specification/Source/CommonZipSource.cs index a8f9136fde..0816f476fd 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonZipSource.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonZipSource.cs @@ -161,13 +161,17 @@ public void Prepare() /// The canonical url of a (conformance) resource. public Resource? ResolveByCanonicalUri(string uri) => FileSource.ResolveByCanonicalUri(uri); + /// public ResolverResult TryResolveByUri(string uri) => FileSource.TryResolveByUri(uri); + /// public ResolverResult TryResolveByCanonicalUri(string uri) => FileSource.TryResolveByCanonicalUri(uri); public Task ResolveByUriAsync(string uri) => FileSource.ResolveByUriAsync(uri); public Task ResolveByCanonicalUriAsync(string uri) => FileSource.ResolveByCanonicalUriAsync(uri); + /// public Task TryResolveByUriAsync(string uri) => FileSource.TryResolveByUriAsync(uri); + /// public Task TryResolveByCanonicalUriAsync(string uri) => FileSource.TryResolveByCanonicalUriAsync(uri); #endregion diff --git a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs index afaf1a2997..07e22f67c0 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs @@ -624,6 +624,7 @@ public Resource ResolveByCanonicalUri(string uri) return TryResolveByCanonicalUri(uri).Value; } + /// public ResolverResult TryResolveByUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); @@ -640,8 +641,7 @@ public ResolverResult TryResolveByUri(string uri) return resource; } - /// Find a (conformance) resource based on its canonical uri. - /// The canonical url of a (conformance) resource. + /// public ResolverResult TryResolveByCanonicalUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); diff --git a/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs b/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs index acbef85c9f..18b6b1e192 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs @@ -60,6 +60,12 @@ public Resource ResolveByCanonicalUri(string uri) return ResolveByUri(uri); } + /// + /// Uses provided to construct and then tries to read the resource from that source. + /// + /// Uri to attempt resolving on + /// with an actual resource, or the . + /// Provided is null. public ResolverResult TryResolveByUri(string uri) { if (uri == null) throw Error.ArgumentNull(nameof(uri)); @@ -92,10 +98,13 @@ public ResolverResult TryResolveByUri(string uri) // Other runtime exceptions are fatal... } + /// public ResolverResult TryResolveByCanonicalUri(string uri) => TryResolveByUri(uri); public Tasks.Task ResolveByUriAsync(string uri) => Tasks.Task.FromResult(ResolveByUri(uri)); public Tasks.Task ResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(ResolveByCanonicalUri(uri)); + /// public Tasks.Task TryResolveByUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); + /// public Tasks.Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); // Allow derived classes to override diff --git a/src/Hl7.Fhir.STU3/Specification/Source/ZipSource.cs b/src/Hl7.Fhir.STU3/Specification/Source/ZipSource.cs index 2e915fcca1..890bc82805 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/ZipSource.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/ZipSource.cs @@ -245,7 +245,9 @@ public async Task ResolveByCanonicalUriAsync(string uri) return result.Value; } + /// public Task TryResolveByUriAsync(string uri) => FileSource.TryResolveByUriAsync(uri); + /// public Task TryResolveByCanonicalUriAsync(string uri) => FileSource.TryResolveByCanonicalUriAsync(uri); #endregion diff --git a/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs b/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs index ec1c2ce5ad..e1894a3bd3 100644 --- a/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs +++ b/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs @@ -80,9 +80,11 @@ public SnapshotSource(ISyncOrAsyncResourceResolver source) [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + /// [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] public ResolverResult TryResolveByUri(string uri) => TaskHelper.Await(() => TryResolveByUriAsync(uri)); + /// [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] public ResolverResult TryResolveByCanonicalUri(string uri) => TaskHelper.Await(() => TryResolveByCanonicalUriAsync(uri)); @@ -102,8 +104,20 @@ public async Tasks.Task ResolveByCanonicalUriAsync(string uri) return result.Value; } + /// + /// Find a resource based on it's relative or absolute uri. + /// + /// A resource uri + /// with an actual resource, combined with the if snapshot generation failed. + /// The source ensures that resolved instances have a snapshot component. If the snapshot generation failed, the will be populated. public async Tasks.Task TryResolveByUriAsync(string uri) => await ensureSnapshot(await _resolver.TryResolveByUriAsync(uri).ConfigureAwait(false)).ConfigureAwait(false); + /// + /// Find a (conformance) resource based on it's canonical uri. + /// + /// A canonical uri of a (conformance) resource. + /// with an actual resource, combined with the if snapshot generation failed. + /// The source ensures that resolved instances have a snapshot component. If the snapshot generation failed, the will be populated. public async Tasks.Task TryResolveByCanonicalUriAsync(string uri) => await ensureSnapshot(await _resolver.TryResolveByCanonicalUriAsync(uri).ConfigureAwait(false)).ConfigureAwait(false); #endregion From 029115f6708ec002e01701845b226cb28cbda325 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Tue, 25 Feb 2025 15:49:07 +0100 Subject: [PATCH 9/9] Copied evertything over from the validator that was not already there. --- .../CompatibilitySuppressions.xml | 14 +++ .../Introspection/ModelInspector.cs | 2 +- src/Hl7.Fhir.Base/Model/Canonical.cs | 93 +++++++++++-------- .../Model/ElementDefinitionExtensions.cs | 4 +- .../Snapshot/SnapshotGenerator.cs | 2 +- .../Source/ResourceResolverExtensions.cs | 4 +- .../Summary/ArtifactSummaryHarvesters.cs | 8 +- .../Model/StructureDefinition.cs | 2 +- .../Model/ModelInfo.cs | 2 +- 9 files changed, 79 insertions(+), 52 deletions(-) diff --git a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml index 2a4077f288..d295b14875 100644 --- a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml @@ -631,6 +631,13 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Model.Canonical.CanonicalUriForFhirCoreType(System.String) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.Model.Date.op_Equality(Hl7.Fhir.Model.Date,Hl7.Fhir.Model.Date) @@ -2108,6 +2115,13 @@ lib/netstandard2.1/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Model.Canonical.CanonicalUriForFhirCoreType(System.String) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.Model.Date.op_Equality(Hl7.Fhir.Model.Date,Hl7.Fhir.Model.Date) diff --git a/src/Hl7.Fhir.Base/Introspection/ModelInspector.cs b/src/Hl7.Fhir.Base/Introspection/ModelInspector.cs index e7543dc43f..1c15dea425 100644 --- a/src/Hl7.Fhir.Base/Introspection/ModelInspector.cs +++ b/src/Hl7.Fhir.Base/Introspection/ModelInspector.cs @@ -290,7 +290,7 @@ private void extractBackbonesFromClasses(IEnumerable classTypes) #region IModelInfo - public Canonical? CanonicalUriForFhirCoreType(string typeName) => Canonical.CanonicalUriForFhirCoreType(typeName); + public Canonical? CanonicalUriForFhirCoreType(string typeName) => Canonical.ForCoreType(typeName); public Canonical? CanonicalUriForFhirCoreType(Type type) => GetFhirTypeNameForType(type) is { } name ? CanonicalUriForFhirCoreType(name) : null; diff --git a/src/Hl7.Fhir.Base/Model/Canonical.cs b/src/Hl7.Fhir.Base/Model/Canonical.cs index 27e44724fb..ecf9966edc 100644 --- a/src/Hl7.Fhir.Base/Model/Canonical.cs +++ b/src/Hl7.Fhir.Base/Model/Canonical.cs @@ -1,34 +1,35 @@ /* Copyright (c) 2011+, HL7, Inc. All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to - endorse or promote products derived from this software without specific + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + */ -using P=Hl7.Fhir.ElementModel.Types; +using P = Hl7.Fhir.ElementModel.Types; using Hl7.Fhir.Utility; +using Hl7.Fhir.Rest; using System; #nullable enable @@ -37,6 +38,11 @@ namespace Hl7.Fhir.Model; public partial class Canonical { + /// + /// The base uri for FHIR core profiles, which is "http://hl7.org/fhir/StructureDefinition/". + /// + public static readonly Uri FHIR_CORE_PROFILE_BASE_URI = new(ResourceIdentity.CORE_BASE_URL); + /// /// Constructs a Canonical based on a given . /// @@ -46,20 +52,19 @@ public Canonical(Uri uri) : this(uri.OriginalString) // nothing } - /// - /// Constructs a canonical from its components. - /// - public Canonical(string? uri, string? version, string? fragment = null) - { - if ((uri is not null) && uri.IndexOfAny(['|', '#']) != -1) - throw Error.Argument(nameof(uri), "cannot contain version/fragment data"); - - if ((version is not null) && version.IndexOfAny(['|', '#']) != -1) - throw Error.Argument(nameof(version), "cannot contain version/fragment data"); + /// + /// Constructs a canonical from its components. + /// + public Canonical(string? uri, string? version, string? fragment = null) + { + if ((uri is not null) && uri.IndexOfAny(['|', '#']) != -1) + throw Error.Argument(nameof(uri), "cannot contain version/fragment data"); - if ((fragment is not null) && fragment.IndexOfAny(['|', '#']) != -1) - throw Error.Argument(nameof(fragment), "already contains version/fragment data"); + if ((version is not null) && version.IndexOfAny(['|', '#']) != -1) + throw Error.Argument(nameof(version), "cannot contain version/fragment data"); + if ((fragment is not null) && fragment.IndexOfAny(['|', '#']) != -1) + throw Error.Argument(nameof(fragment), "already contains version/fragment data"); Value = uri + (version is not null ? "|" + version : null) + @@ -80,7 +85,7 @@ public void Deconstruct(out string? uri, out string? version, out string? fragme /// Converts a string to a canonical. /// /// - public static implicit operator Canonical(string value) => new(value); + public static implicit operator Canonical(string? value) => new(value); /// /// Converts a canonical to a string. @@ -93,9 +98,17 @@ public void Deconstruct(out string? uri, out string? version, out string? fragme /// public static bool IsValidValue(string value) => FhirUri.IsValidValue(value); - public static readonly Uri FHIR_CORE_PROFILE_BASE_URI = new(@"http://hl7.org/fhir/StructureDefinition/"); - public static Canonical CanonicalUriForFhirCoreType(string typename) => new(FHIR_CORE_PROFILE_BASE_URI + typename); - + /// + /// Constructs a Canonical to represent a FHIR core type given by name. + /// + /// If the typename is an absolute url, this function assumes the + /// typename is already fully qualified and will return a canonical with that value. + /// + public static Canonical ForCoreType(string typename) + { + var typeNameUri = new Canonical(typename); + return typeNameUri.IsAbsolute ? typeNameUri : ResourceIdentity.Core(typename).OriginalString; + } /// /// The version string of the canonical (if present). @@ -145,8 +158,8 @@ private static (string? url, string? version, string? fragment) splitCanonical(s if (url.EndsWith(separator.ToString())) url = url[..^1]; var position = url.LastIndexOf(separator); - return position == -1 ? - (url, null) + return position == -1 + ? (url, null) : (url[..position], url[(position + 1)..]); } } @@ -157,8 +170,8 @@ private static (string? url, string? version, string? fragment) splitCanonical(s /// The value of this canonical is null, /// which is not valid for System strings. public P.String ToSystemString() => (P.String?)TryConvertToSystemTypeInternal() ?? - throw new InvalidOperationException("Value is null."); + throw new InvalidOperationException("Value is null."); protected internal override P.Any? TryConvertToSystemTypeInternal() => - Value is not null ? new P.String(Value) : null; + Value is not null ? new P.String(Value) : null; } \ No newline at end of file diff --git a/src/Hl7.Fhir.Conformance/Model/ElementDefinitionExtensions.cs b/src/Hl7.Fhir.Conformance/Model/ElementDefinitionExtensions.cs index 926dff80e3..953e06817c 100644 --- a/src/Hl7.Fhir.Conformance/Model/ElementDefinitionExtensions.cs +++ b/src/Hl7.Fhir.Conformance/Model/ElementDefinitionExtensions.cs @@ -261,14 +261,14 @@ public static IReadOnlyList DistinctTypeCodes(this List public static string? GetTypeProfile(this ElementDefinition.TypeRefComponent elemType) => - elemType?.Profile.SafeSingleOrDefault() ?? (elemType?.Code is not null ? Canonical.CanonicalUriForFhirCoreType(elemType.Code).Value : null); + elemType?.Profile.SafeSingleOrDefault() ?? (elemType?.Code is not null ? Canonical.ForCoreType(elemType.Code).Value : null); /// /// Returns the profiles on the given if specified, /// or otherwise the core profile url for the specified type code. /// public static IEnumerable? GetTypeProfiles(this ElementDefinition.TypeRefComponent elemType) => - elemType?.Profile.Any() == true ? elemType.Profile : (elemType?.Code is not null ? new[] { Canonical.CanonicalUriForFhirCoreType(elemType.Code).Value } : null); + elemType?.Profile.Any() == true ? elemType.Profile : (elemType?.Code is not null ? new[] { Canonical.ForCoreType(elemType.Code).Value } : null); /// /// Determines if the specified element definition represents a . diff --git a/src/Hl7.Fhir.Conformance/Specification/Snapshot/SnapshotGenerator.cs b/src/Hl7.Fhir.Conformance/Specification/Snapshot/SnapshotGenerator.cs index abeeccafa8..24abcc7c3f 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Snapshot/SnapshotGenerator.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Snapshot/SnapshotGenerator.cs @@ -552,7 +552,7 @@ private void fixInvalidSliceNameOnRootElement(ElementDefinition elem, StructureD Debug.Assert(elem.IsRootElement()); if (!string.IsNullOrEmpty(elem.SliceName)) { - if (sd.Url != Canonical.CanonicalUriForFhirCoreType(FhirTypeNames.SIMPLEQUANTITY_NAME)) + if (sd.Url != Canonical.ForCoreType(FhirTypeNames.SIMPLEQUANTITY_NAME)) { addIssueInvalidSliceNameOnRootElement(elem, sd); } diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/ResourceResolverExtensions.cs b/src/Hl7.Fhir.Conformance/Specification/Source/ResourceResolverExtensions.cs index 00bfde921f..6b7749bac3 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/ResourceResolverExtensions.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/ResourceResolverExtensions.cs @@ -62,7 +62,7 @@ public static async Tasks.Task FindStructureDefinitionAsync [Obsolete("Using synchronous resolvers is not recommended anymore, use FindStructureDefinitionForCoreTypeAsync() instead.")] public static StructureDefinition FindStructureDefinitionForCoreType(this IResourceResolver resolver, string typename) { - var url = Uri.IsWellFormedUriString(typename, UriKind.Absolute) ? typename : Canonical.CanonicalUriForFhirCoreType(typename).Value; + var url = Uri.IsWellFormedUriString(typename, UriKind.Absolute) ? typename : Canonical.ForCoreType(typename).Value; return resolver.FindStructureDefinition(url); } @@ -75,7 +75,7 @@ public static StructureDefinition FindStructureDefinitionForCoreType(this IResou public static async Tasks.Task FindStructureDefinitionForCoreTypeAsync(this IAsyncResourceResolver resolver, string typename) { var url = Uri.IsWellFormedUriString(typename, UriKind.Absolute) ? typename : - Canonical.CanonicalUriForFhirCoreType(typename).Value; + Canonical.ForCoreType(typename).Value; return await resolver.FindStructureDefinitionAsync(url).ConfigureAwait(false); } diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/Summary/ArtifactSummaryHarvesters.cs b/src/Hl7.Fhir.Conformance/Specification/Source/Summary/ArtifactSummaryHarvesters.cs index a0e7f2c422..00fc7af943 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/Summary/ArtifactSummaryHarvesters.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/Summary/ArtifactSummaryHarvesters.cs @@ -258,18 +258,18 @@ public static class StructureDefinitionSummaryProperties public static readonly string BaseDefinitionKey = "StructureDefinition.baseDefinition"; public static readonly string DerivationKey = "StructureDefinition.derivation"; - public static readonly string FmmExtensionUrl = Canonical.CanonicalUriForFhirCoreType("structuredefinition-fmm").Value; + public static readonly string FmmExtensionUrl = Canonical.ForCoreType("structuredefinition-fmm").Value; public static readonly string MaturityLevelKey = "StructureDefinition.maturityLevel"; - public static readonly string WgExtensionUrl = Canonical.CanonicalUriForFhirCoreType("structuredefinition-wg").Value; + public static readonly string WgExtensionUrl = Canonical.ForCoreType("structuredefinition-wg").Value; public static readonly string WorkingGroupKey = "StructureDefinition.workingGroup"; // [WMR 20181213] R4 - NEW - public static readonly string StandardsStatusExtensionUrl = Canonical.CanonicalUriForFhirCoreType("structuredefinition-standards-status").Value; + public static readonly string StandardsStatusExtensionUrl = Canonical.ForCoreType("structuredefinition-standards-status").Value; public static readonly string StandardsStatusKey = "StructureDefinition.standardsStatus"; // [WMR 20181213] R4 - NEW - public static readonly string NormativeVersionExtensionUrl = Canonical.CanonicalUriForFhirCoreType("structuredefinition-normative-version").Value; + public static readonly string NormativeVersionExtensionUrl = Canonical.ForCoreType("structuredefinition-normative-version").Value; public static readonly string NormativeVersionKey = "StructureDefinition.normativeVersion"; public static readonly string RootDefinitionKey = "StructureDefinition.rootDefinition"; diff --git a/src/Hl7.Fhir.Shims.Base/Model/StructureDefinition.cs b/src/Hl7.Fhir.Shims.Base/Model/StructureDefinition.cs index dac50d1bfb..c90bdacca5 100644 --- a/src/Hl7.Fhir.Shims.Base/Model/StructureDefinition.cs +++ b/src/Hl7.Fhir.Shims.Base/Model/StructureDefinition.cs @@ -64,5 +64,5 @@ public partial class DifferentialComponent : IElementList; public bool HasSnapshot => Snapshot is not null && Snapshot.Element.Any(); [NotMapped] - public bool IsCoreDefinition => Type == Id && Url == Canonical.CanonicalUriForFhirCoreType(Type); + public bool IsCoreDefinition => Type == Id && Url == Canonical.ForCoreType(Type); } \ No newline at end of file diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Model/ModelInfo.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Model/ModelInfo.cs index 65407d4eac..0eee60635e 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Model/ModelInfo.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Model/ModelInfo.cs @@ -190,7 +190,7 @@ public static bool IsInstanceTypeFor(FHIRAllTypes superclass, FHIRAllTypes subcl } /// - public static Canonical CanonicalUriForFhirCoreType(string typename) => Canonical.CanonicalUriForFhirCoreType(typename); + public static Canonical CanonicalUriForFhirCoreType(string typename) => Canonical.ForCoreType(typename); /// public static Canonical? CanonicalUriForFhirCoreType(Type type) => ModelInspector.CanonicalUriForFhirCoreType(type);