diff --git a/Directory.Packages.props b/Directory.Packages.props index 3f16082..d2f25c6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,11 +3,11 @@ true - - + + - - + + diff --git a/src/Facility.Definition.Swagger/Facility.Definition.Swagger.csproj b/src/Facility.Definition.Swagger/Facility.Definition.Swagger.csproj index e2d8e42..85d08dc 100644 --- a/src/Facility.Definition.Swagger/Facility.Definition.Swagger.csproj +++ b/src/Facility.Definition.Swagger/Facility.Definition.Swagger.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.0;net6.0;net7.0;net8.0 Used to interpret Swagger (OpenAPI) 2.0 definitions. Facility FSD Swagger OpenAPI Definition true diff --git a/src/Facility.Definition.Swagger/SwaggerConversion.cs b/src/Facility.Definition.Swagger/SwaggerConversion.cs index 8b26199..acd662d 100644 --- a/src/Facility.Definition.Swagger/SwaggerConversion.cs +++ b/src/Facility.Definition.Swagger/SwaggerConversion.cs @@ -368,7 +368,9 @@ private KeyValuePair ResolveDefinition(SwaggerSchema swag if (swaggerDefinition.Ref != null) { name = GetDefinitionNameFromRef(swaggerDefinition.Ref, position); - if (!m_swaggerService.Definitions!.TryGetValue(name, out swaggerDefinition)) + if (m_swaggerService.Definitions!.TryGetValue(name, out var resolvedDefinition)) + swaggerDefinition = resolvedDefinition; + else m_errors.Add(new ServiceDefinitionError($"Missing definition named '{name}'.", position)); } @@ -383,8 +385,10 @@ private void ResolveOperations(ref SwaggerOperations swaggerOperations, ref Swag if (!swaggerOperations.Ref.StartsWith(refPrefix, StringComparison.Ordinal)) m_errors.Add(new ServiceDefinitionError("Operations $ref must start with '#/paths/'.", context.CreatePosition())); - string name = UnescapeRefPart(swaggerOperations.Ref.Substring(refPrefix.Length)); - if (!m_swaggerService.Paths!.TryGetValue(name, out swaggerOperations)) + var name = UnescapeRefPart(swaggerOperations.Ref.Substring(refPrefix.Length)); + if (m_swaggerService.Paths!.TryGetValue(name, out var resolvedOperations)) + swaggerOperations = resolvedOperations; + else m_errors.Add(new ServiceDefinitionError($"Missing path named '{name}'.", context.CreatePosition())); context = context.Root.CreateContext("paths/" + name); @@ -399,8 +403,10 @@ private SwaggerParameter ResolveParameter(SwaggerParameter swaggerParameter, Ser if (!swaggerParameter.Ref.StartsWith(refPrefix, StringComparison.Ordinal)) m_errors.Add(new ServiceDefinitionError("Parameter $ref must start with '#/parameters/'.", position)); - string name = UnescapeRefPart(swaggerParameter.Ref.Substring(refPrefix.Length)); - if (!m_swaggerService.Parameters!.TryGetValue(name, out swaggerParameter)) + var name = UnescapeRefPart(swaggerParameter.Ref.Substring(refPrefix.Length)); + if (m_swaggerService.Parameters!.TryGetValue(name, out var resolvedParameter)) + swaggerParameter = resolvedParameter; + else m_errors.Add(new ServiceDefinitionError($"Missing parameter named '{name}'.", position)); } @@ -415,8 +421,10 @@ private SwaggerResponse ResolveResponse(SwaggerResponse swaggerResponse, Service if (!swaggerResponse.Ref.StartsWith(refPrefix, StringComparison.Ordinal)) m_errors.Add(new ServiceDefinitionError("Response $ref must start with '#/responses/'.", position)); - string name = UnescapeRefPart(swaggerResponse.Ref.Substring(refPrefix.Length)); - if (!m_swaggerService.Responses!.TryGetValue(name, out swaggerResponse)) + var name = UnescapeRefPart(swaggerResponse.Ref.Substring(refPrefix.Length)); + if (m_swaggerService.Responses!.TryGetValue(name, out var resolvedResponse)) + swaggerResponse = resolvedResponse; + else m_errors.Add(new ServiceDefinitionError($"Missing response named '{name}'.", position)); } @@ -507,7 +515,7 @@ internal static bool IsFacilityError(KeyValuePair swagger return TryGetFacilityTypeName(valueSchema, position); } - private static string UnescapeRefPart(string value) => value.Replace("~1", "/").Replace("~0", "~"); + private static string UnescapeRefPart(string value) => value.ReplaceOrdinal("~1", "/").ReplaceOrdinal("~0", "~"); private static readonly Regex s_pathParameter = new Regex(@"\{[^}]+\}"); diff --git a/src/Facility.Definition.Swagger/SwaggerGenerator.cs b/src/Facility.Definition.Swagger/SwaggerGenerator.cs index 6e4d136..4162bd1 100644 --- a/src/Facility.Definition.Swagger/SwaggerGenerator.cs +++ b/src/Facility.Definition.Swagger/SwaggerGenerator.cs @@ -540,7 +540,7 @@ public OurEventEmitter(IEventEmitter nextEmitter) public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter) { // prefer the literal style for multi-line strings - if (eventInfo.Source.Type == typeof(string) && eventInfo.Style == ScalarStyle.Any && ((string) eventInfo.Source.Value!).IndexOf('\n') != -1) + if (eventInfo.Source.Type == typeof(string) && eventInfo.Style == ScalarStyle.Any && ((string) eventInfo.Source.Value!).ContainsOrdinal("\n")) eventInfo.Style = ScalarStyle.Literal; // ensure strings that look like numbers remain strings @@ -552,6 +552,7 @@ public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter) } private sealed class OurDictionary : IDictionary, IReadOnlyDictionary + where TKey : notnull { public OurDictionary() { @@ -608,7 +609,7 @@ public bool Remove(TKey key) return true; } - public bool TryGetValue(TKey key, out TValue value) => m_dictionary.TryGetValue(key, out value); + public bool TryGetValue(TKey key, out TValue value) => m_dictionary.TryGetValue(key, out value!); public TValue this[TKey key] { diff --git a/src/Facility.Definition.Swagger/SwaggerUtility.cs b/src/Facility.Definition.Swagger/SwaggerUtility.cs index f40446c..978e7f1 100644 --- a/src/Facility.Definition.Swagger/SwaggerUtility.cs +++ b/src/Facility.Definition.Swagger/SwaggerUtility.cs @@ -29,9 +29,19 @@ public static class SwaggerUtility internal static IList EmptyIfNull(this IList? list) => list ?? Array.Empty(); - internal static IReadOnlyDictionary EmptyIfNull(this IReadOnlyDictionary? list) => list ?? new Dictionary(); - - internal static IDictionary EmptyIfNull(this IDictionary? list) => list ?? new Dictionary(); + internal static IReadOnlyDictionary EmptyIfNull(this IReadOnlyDictionary? list) + where TKey : notnull => list ?? new Dictionary(); + + internal static IDictionary EmptyIfNull(this IDictionary? list) + where TKey : notnull => list ?? new Dictionary(); + +#if NET6_0_OR_GREATER + internal static bool ContainsOrdinal(this string text, string value) => text.Contains(value, StringComparison.Ordinal); + internal static string ReplaceOrdinal(this string text, string oldValue, string newValue) => text.Replace(oldValue, newValue, StringComparison.Ordinal); +#else + internal static bool ContainsOrdinal(this string text, string value) => text.Contains(value); + internal static string ReplaceOrdinal(this string text, string oldValue, string newValue) => text.Replace(oldValue, newValue); +#endif private sealed class CamelCaseExceptDictionaryKeysContractResolver : CamelCasePropertyNamesContractResolver {