diff --git a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml index d5d6eae5e6..991b943a26 100644 --- a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml @@ -890,6 +890,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) @@ -2563,6 +2570,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.Base/Specification/Source/CachedResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/CachedResolver.cs index 4a8ad7a716..9c5fdb57c8 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 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. @@ -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); } /// @@ -66,21 +66,21 @@ public CachedResolver(ISyncOrAsyncResourceResolver source, int cacheDuration = D public int CacheDuration { get; } /// - [Obsolete("CachedResolver now works best with asynchronous resolvers. Use ResolveByUriAsync() instead.")] - public Resource ResolveByUri(string url) => TaskHelper.Await(() => ResolveByUriAsync(url)); - + [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. /// 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).ConfigureAwait(false); + return result.Value; } /// - [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)); @@ -90,14 +90,33 @@ 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).ConfigureAwait(false); + 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, 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) { if (url == null) throw Error.ArgumentNull(nameof(url)); return await _resourcesByUri.Get(url, strategy).ConfigureAwait(false); } /// - [Obsolete("CachedResolver now works best with asynchronous resolvers. Use ResolveByCanonicalUriAsync() instead.")] - public Resource ResolveByCanonicalUri(string url) => TaskHelper.Await(() => ResolveByCanonicalUriAsync(url)); + [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. /// The canonical url of the target conformance resource. @@ -105,12 +124,24 @@ 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).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); /// - [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)); @@ -121,8 +152,19 @@ 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; + } + + /// 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)); + return await _resourcesByCanonical.Get(uri, strategy).ConfigureAwait(false); } /// Clear the cache entry for the artifact with the specified url, if it exists. @@ -183,17 +225,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; } @@ -203,15 +245,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; @@ -219,16 +261,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) @@ -246,14 +288,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 { @@ -261,7 +303,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.Base/Specification/Source/IResourceResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/IResourceResolver.cs index 367528452d..2a9e84e552 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. @@ -18,13 +18,43 @@ 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. - 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 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 resource based on its relative or absolute uri. + /// A resource uri. + /// 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 (conformance) resource based on its canonical uri. /// The canonical url of a (conformance) resource. - Resource ResolveByCanonicalUri(string 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 +64,44 @@ public interface IAsyncResourceResolver : ISyncOrAsyncResourceResolver { /// Find a resource based on its relative or absolute uri. /// A resource uri. - 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. - 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. + /// 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).ConfigureAwait(false); +#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).ConfigureAwait(false); +#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..1cf2ad7192 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/InMemoryResourceResolver.cs @@ -94,10 +94,38 @@ 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 ResolverException.NotFound(); + } + + /// + 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 ResolverException.NotFound(); + } + /// public Resource? ResolveByCanonicalUri(string uri) { - return _resources.Where(r => r.Url == uri)?.Select(r => r.Resource).FirstOrDefault(); + return TryResolveByCanonicalUri(uri).Value; } /// @@ -105,11 +133,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 +145,18 @@ 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..b177333107 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,54 +73,86 @@ public void Pop() private IEnumerable allSourcesAsAsync() => _sources.Select(src => src.AsAsync()); - [Obsolete("MultiResolver now works best with asynchronous resolvers. Use ResolveByUriAsync() instead.")] - public Resource ResolveByUri(string uri) => TaskHelper.Await(() => ResolveByUriAsync(uri)); + [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] + public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; + + [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)); - public async Task ResolveByUriAsync(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; + } + + /// + 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..e5d5f1b8fa --- /dev/null +++ b/src/Hl7.Fhir.Base/Specification/Source/ResolverException.cs @@ -0,0 +1,97 @@ +using Hl7.Fhir.Model; +using Hl7.Fhir.Utility; +using System; +using System.Collections.Generic; +using System.Linq; + +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"; + /// + /// 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) + { + } + + 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)); + } + internal static ResolverException SnapshotOutcome(OperationOutcome generatorOutcome) + { + var outcomeMessages = string.Join(Environment.NewLine, generatorOutcome.Issue.Select(x => x.ToString())); + return new ResolverException(SNAPSHOT_FAILURE, outcomeMessages); + } + + 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) + { + 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.Base/Specification/Source/ResolverResult.cs b/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs new file mode 100644 index 0000000000..aee5c67a6c --- /dev/null +++ b/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs @@ -0,0 +1,93 @@ +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 + public ResolverResult(Resource value) + { + Value = value ?? throw Utility.Error.ArgumentNull(nameof(value)); + Error = null; + } + + /// + /// Constructor for failure to resolve, resulting in error + /// + /// Error encountered during resolve + #if NET8_0_OR_GREATER + [SetsRequiredMembers] + #endif + public ResolverResult(ResolverException error) + { + Error = 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 + 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.Base/Specification/Source/SyncToAsyncResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/SyncToAsyncResolver.cs index e9e1d1ba26..ce25da62aa 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,28 @@ 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/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/CommonDirectorySource.cs b/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs index 28e8ec0292..bf9a999208 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs @@ -579,20 +579,60 @@ 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); + + Resource? resource; + try + { + resource = loadResourceInternal(summary); + } + catch(ArgumentException ex) + { + return ResolverException.ArtifactSummaryArgumentException(ex); + } + + 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); + + Resource? resource; + try + { + resource = loadResourceInternal(summary); + } + catch(ArgumentException ex) + { + return ResolverException.ArtifactSummaryArgumentException(ex); + } + + if (resource is null) + return ResolverException.NotFound(); + + return resource; } #endregion @@ -1001,6 +1041,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..01d8b886fa 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs @@ -52,23 +52,29 @@ public CommonWebResolver(Func fhirClientFactory) public Resource? ResolveByUri(string uri) { - if (uri is 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 TryResolveByUri(uri).Value; + } - var id = new ResourceIdentity(uri); + 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)) + return ResolverException.NotValidResourceIdentity(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 +82,36 @@ public CommonWebResolver(Func fhirClientFactory) catch (FhirOperationException foe) { LastError = foe; - return null; + return ResolverException.OperationFailed("Error occurred during Fhir operation", foe); } catch (WebException we) { LastError = we; - return null; + return ResolverException.OperationFailed("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..0816f476fd 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonZipSource.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonZipSource.cs @@ -161,8 +161,18 @@ 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..6b7749bac3 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,13 +53,16 @@ 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.")] 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); } @@ -71,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); } @@ -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.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.ElementModel.Shared.Tests/ElementNodeTests.cs b/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs index ceabdc4419..6f74a1bf6b 100644 --- a/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs @@ -398,6 +398,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) @@ -410,6 +416,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 407a83bdd0..e1e1d8599f 100644 --- a/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs @@ -297,8 +297,14 @@ 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; + } + + public async Tasks.Task TryResolveByCanonicalUriAsync(string uri) + { if (_cache.TryGetValue(uri, out StructureDefinition? sd)) return sd; @@ -312,8 +318,10 @@ 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(); + public Tasks.Task ResolveByUriAsync(string uri) => throw new NotImplementedException(); } private class TypedElementWithoutDefinition : ITypedElement, IResourceTypeSupplier diff --git a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs index aa0176b7c5..07e22f67c0 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs @@ -614,18 +614,48 @@ public Resource LoadBySummary(ArtifactSummary summary) /// A resource uri. public Resource ResolveByUri(string uri) { - if (uri == null) throw Error.ArgumentNull(nameof(uri)); - var summary = GetSummaries().ResolveByUri(uri); - return loadResourceInternal(summary); + 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); + + if(summary is null) + return ResolverException.ArtifactSummaryNoMatch(uri); + + var resource = loadResourceInternal(summary); + + if (resource is null) + return ResolverException.NotFound(); + + return resource; + } + + /// + 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..18b6b1e192 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs @@ -51,14 +51,27 @@ 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); + } + + /// + /// 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)); if (!ResourceIdentity.IsRestResourceIdentity(uri)) - { - // Weakness in FhirClient, need to have the base :-( So return null if we cannot determine it. - return null; - } - + return ResolverException.NotValidResourceIdentity(uri); + var id = new ResourceIdentity(uri); var client = _clientFactory?.Invoke(id.BaseUri) ?? new FhirClient(id.BaseUri); @@ -75,23 +88,24 @@ public Resource ResolveByUri(string uri) catch (FhirOperationException foe) { LastError = foe; - return null; + return ResolverException.OperationFailed("Error occurred during Fhir operation", foe); } catch (WebException we) { LastError = we; - return null; + return ResolverException.OperationFailed("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..890bc82805 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/ZipSource.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/ZipSource.cs @@ -221,14 +221,34 @@ 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/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.Base/Specification/Source/SnapshotSource.cs b/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs index 7d8d30ac83..e1894a3bd3 100644 --- a/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs +++ b/src/Hl7.Fhir.Shims.Base/Specification/Source/SnapshotSource.cs @@ -72,33 +72,70 @@ public SnapshotSource(ISyncOrAsyncResourceResolver source) private IAsyncResourceResolver _resolver => Generator.AsyncResolver; - /// 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); - /// - [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use ResolveByUriAsync() instead.")] - public Resource ResolveByUri(string uri) => TaskHelper.Await(() => ResolveByUriAsync(uri)); + [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) + { + var result = await TryResolveByUriAsync(uri).ConfigureAwait(false); + return result.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; + } - /// - [Obsolete("SnapshotSource now works best with asynchronous resolvers. Use ResolveByCanonicalUriAsync() instead.")] - public Resource ResolveByCanonicalUri(string uri) => TaskHelper.Await(() => ResolveByCanonicalUriAsync(uri)); + /// + /// 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 // 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 is false) + { + return new ResolverResult(sd, 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.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); 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")))); } 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..b7f1c5ba53 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 : ResolverException.NotFound()); + + 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 : ResolverException.NotFound()); + 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/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.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/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()))); } 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;