// Copyright © WireMock.Net using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.OpenApi; using Newtonsoft.Json; using Stef.Validation; using WireMock.Admin.Mappings; using WireMock.Net.OpenApiParser.Extensions; using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Types; using WireMock.Net.OpenApiParser.Utils; using SystemTextJsonSerializer = System.Text.Json.JsonSerializer; namespace WireMock.Net.OpenApiParser.Mappers; internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings) { private const string HeaderContentType = "Content-Type"; private readonly WireMockOpenApiParserSettings _settings = Guard.NotNull(settings); private readonly ExampleValueGenerator _exampleValueGenerator = new(settings); public IReadOnlyList ToMappingModels(OpenApiPaths? paths, IList servers) { return paths? .OrderBy(p => p.Key) .Select(p => MapPath(p.Key, p.Value, servers)) .SelectMany(x => x) .ToArray() ?? []; } private MappingModel[] MapPath(string path, IOpenApiPathItem pathItem, IList servers) { return pathItem.Operations?.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)).ToArray() ?? []; } private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation, IList servers) { var queryParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Query) ?? []; var pathParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Path) ?? []; var requestHeaders = operation.Parameters?.Where(p => p.In == ParameterLocation.Header) ?? []; return new MappingModel { Guid = Guid.NewGuid(), Request = new RequestModel { Methods = [httpMethod], Path = PathUtils.Combine(MapBasePath(servers), MapPathWithParameters(path, pathParameters)), Params = MapQueryParameters(queryParameters), Headers = MapRequestHeaders(requestHeaders), Body = GetRequestBodyModel(operation.RequestBody) }, Response = GetResponseModel(operation.Responses?.FirstOrDefault()) }; } private BodyModel GetRequestBodyModel(IOpenApiRequestBody? openApiRequestBody) { if (openApiRequestBody is not { Content: not null, Required: true }) { return new BodyModel(); } var content = openApiRequestBody.Content; TryGetContent(content, out var requestContent, out _); var requestExample = requestContent?.Example; var requestExamples = requestContent?.Examples; var requestSchemaExample = requestContent?.Schema?.Example; var requestSchemaExamples = requestContent?.Schema?.Examples; JsonNode? request; if (requestExample != null) { request = requestExample; } else if (requestSchemaExample != null) { request = requestSchemaExample; } else if (requestExamples != null) { request = requestExamples.FirstOrDefault().Value.Value; } else if (requestSchemaExamples != null) { request = requestSchemaExamples.FirstOrDefault(); } else { var requestSchema = content?.FirstOrDefault().Value.Schema; request = MapSchemaToObject(requestSchema); } return MapRequestBody(request) ?? new BodyModel(); } private ResponseModel GetResponseModel(KeyValuePair? openApiResponse) { var content = openApiResponse?.Value.Content; TryGetContent(content, out var responseContent, out var contentType); var responseExample = responseContent?.Example; var responseExamples = responseContent?.Examples; var responseSchemaExample = responseContent?.Schema?.Example; var responseSchemaExamples = responseContent?.Schema?.Examples; JsonNode? response; if (responseExample != null) { response = responseExample; } else if (responseSchemaExample != null) { response = responseSchemaExample; } else if (responseExamples != null) { response = responseExamples.FirstOrDefault().Value.Value; } else if (responseSchemaExamples != null) { response = responseSchemaExamples.FirstOrDefault(); } else { var responseSchema = content?.FirstOrDefault().Value?.Schema; response = MapSchemaToObject(responseSchema); } return new ResponseModel { StatusCode = int.TryParse(openApiResponse?.Key, out var httpStatusCode) ? httpStatusCode : 200, Headers = MapHeaders(contentType, openApiResponse?.Value.Headers), BodyAsJson = response != null ? JsonConvert.DeserializeObject(SystemTextJsonSerializer.Serialize(response)) : null }; } private BodyModel? MapRequestBody(JsonNode? requestBody) { if (requestBody == null) { return null; } return new BodyModel { Matcher = new MatcherModel { Name = "JsonMatcher", Pattern = SystemTextJsonSerializer.Serialize(requestBody, new JsonSerializerOptions { WriteIndented = true }), IgnoreCase = _settings.RequestBodyIgnoreCase } }; } private static bool TryGetContent(IDictionary? contents, [NotNullWhen(true)] out IOpenApiMediaType? openApiMediaType, [NotNullWhen(true)] out string? contentType) { openApiMediaType = null; contentType = null; if (contents == null || contents.Values.Count == 0) { return false; } if (contents.TryGetValue("application/json", out var content)) { openApiMediaType = content; contentType = "application/json"; } else { var first = contents.FirstOrDefault(); openApiMediaType = first.Value; contentType = first.Key; } return true; } private JsonNode? MapSchemaToObject(IOpenApiSchema? schema) { if (schema == null) { return null; } switch (schema.GetSchemaType(out _)) { case JsonSchemaType.Array: var array = new JsonArray(); for (var i = 0; i < _settings.NumberOfArrayItems; i++) { if (schema.Items?.Properties?.Count > 0) { var item = new JsonObject(); foreach (var property in schema.Items.Properties) { item[property.Key] = MapSchemaToObject(property.Value); } array.Add(item); } else { var arrayItem = MapSchemaToObject(schema.Items); array.Add(arrayItem); } } if (schema.AllOf?.Count > 0) { array.Add(MapSchemaAllOfToObject(schema)); } return array; case JsonSchemaType.Boolean: case JsonSchemaType.Integer: case JsonSchemaType.Number: case JsonSchemaType.String: return _exampleValueGenerator.GetExampleValue(schema); case JsonSchemaType.Object: var propertyAsJsonObject = new JsonObject(); foreach (var schemaProperty in schema.Properties ?? new Dictionary()) { propertyAsJsonObject[schemaProperty.Key] = MapPropertyAsJsonNode(schemaProperty.Value); } if (schema.AllOf?.Count > 0) { foreach (var group in schema.AllOf.SelectMany(p => p.Properties ?? new Dictionary()).GroupBy(x => x.Key)) { propertyAsJsonObject[group.Key] = MapPropertyAsJsonNode(group.First().Value); } } return propertyAsJsonObject; default: return null; } } private JsonObject MapSchemaAllOfToObject(IOpenApiSchema schema) { var arrayItem = new JsonObject(); foreach (var property in schema.AllOf ?? []) { foreach (var item in property.Properties ?? new Dictionary()) { arrayItem[item.Key] = MapPropertyAsJsonNode(item.Value); } } return arrayItem; } private JsonNode? MapPropertyAsJsonNode(IOpenApiSchema openApiSchema) { var schemaType = openApiSchema.GetSchemaType(out _); if (schemaType is JsonSchemaType.Object or JsonSchemaType.Array) { return MapSchemaToObject(openApiSchema); } return _exampleValueGenerator.GetExampleValue(openApiSchema); } private string MapPathWithParameters(string path, IEnumerable? parameters) { if (parameters == null) { return path; } var newPath = path; foreach (var parameter in parameters) { var exampleMatcherModel = GetExampleMatcherModel(parameter.Schema, _settings.PathPatternToUse); newPath = newPath.Replace($"{{{parameter.Name}}}", exampleMatcherModel.Pattern as string); } return newPath; } private Dictionary? MapHeaders(string? responseContentType, IDictionary? headers) { var mappedHeaders = headers? .ToDictionary(item => item.Key, _ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern!) ?? []; if (responseContentType != null) { mappedHeaders.TryAdd(HeaderContentType, responseContentType); } return mappedHeaders.Count > 0 ? mappedHeaders : null; } private IList? MapQueryParameters(IEnumerable queryParameters) { var list = queryParameters .Where(req => req.Required) .Select(qp => new ParamModel { Name = qp.Name ?? string.Empty, IgnoreCase = _settings.QueryParameterPatternIgnoreCase, Matchers = [ GetExampleMatcherModel(qp.Schema, _settings.QueryParameterPatternToUse) ] }) .ToList(); return list.Any() ? list : null; } private IList? MapRequestHeaders(IEnumerable headers) { var list = headers .Where(req => req.Required) .Select(qp => new HeaderModel { Name = qp.Name ?? string.Empty, IgnoreCase = _settings.HeaderPatternIgnoreCase, Matchers = [ GetExampleMatcherModel(qp.Schema, _settings.HeaderPatternToUse) ] }) .ToList(); return list.Any() ? list : null; } private MatcherModel GetExampleMatcherModel(IOpenApiSchema? schema, ExampleValueType type) { return type switch { ExampleValueType.Value => new MatcherModel { Name = "ExactMatcher", Pattern = GetExampleValueAsStringForSchemaType(schema), IgnoreCase = _settings.IgnoreCaseExampleValues }, _ => new MatcherModel { Name = "WildcardMatcher", Pattern = "*" } }; } private string GetExampleValueAsStringForSchemaType(IOpenApiSchema? schema) { var value = _exampleValueGenerator.GetExampleValue(schema); if (value.GetValueKind() == JsonValueKind.String) { return value.GetValue(); } return value.ToString(); } private static string MapBasePath(IList? servers) { var server = servers?.FirstOrDefault(); if (server == null) { return string.Empty; } if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out var uriResult)) { return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString(); } return string.Empty; } }