| | | 1 | | namespace Kestrun.Utilities; |
| | | 2 | | |
| | | 3 | | internal static class MediaTypeHelper |
| | | 4 | | { |
| | 1 | 5 | | private static readonly Dictionary<string, string> ExactMap = |
| | 1 | 6 | | new(StringComparer.OrdinalIgnoreCase) |
| | 1 | 7 | | { |
| | 1 | 8 | | // Canonical types |
| | 1 | 9 | | ["application/json"] = "application/json", |
| | 1 | 10 | | ["application/xml"] = "application/xml", |
| | 1 | 11 | | ["application/yaml"] = "application/yaml", |
| | 1 | 12 | | ["application/cbor"] = "application/cbor", |
| | 1 | 13 | | |
| | 1 | 14 | | // Common aliases |
| | 1 | 15 | | ["application/x-yaml"] = "application/yaml", |
| | 1 | 16 | | ["text/yaml"] = "application/yaml", |
| | 1 | 17 | | ["text/xml"] = "application/xml", |
| | 1 | 18 | | |
| | 1 | 19 | | // Other supported |
| | 1 | 20 | | ["application/bson"] = "application/bson", |
| | 1 | 21 | | ["text/csv"] = "text/csv", |
| | 1 | 22 | | ["application/x-www-form-urlencoded"] = "application/x-www-form-urlencoded", |
| | 1 | 23 | | |
| | 1 | 24 | | // Wildcards (treat as "fine, give me the default") |
| | 1 | 25 | | ["*/*"] = "text/plain", |
| | 1 | 26 | | ["text/*"] = "text/plain", |
| | 1 | 27 | | ["application/*"] = "text/plain", |
| | 1 | 28 | | }; |
| | | 29 | | |
| | 1 | 30 | | private static readonly (string Suffix, string Canonical)[] StructuredSuffixMap = |
| | 1 | 31 | | [ |
| | 1 | 32 | | ("+json", "application/json"), |
| | 1 | 33 | | ("+xml", "application/xml"), |
| | 1 | 34 | | ("+yaml", "application/yaml"), |
| | 1 | 35 | | ("+yml", "application/yaml"), |
| | 1 | 36 | | ("+cbor", "application/cbor"), |
| | 1 | 37 | | ]; |
| | | 38 | | |
| | | 39 | | /// <summary> |
| | | 40 | | /// Normalizes the given content type by trimming whitespace and removing parameters. |
| | | 41 | | /// </summary> |
| | | 42 | | /// <param name="contentType">The content type to normalize.</param> |
| | | 43 | | /// <returns>The normalized media type.</returns> |
| | | 44 | | public static string Normalize(string? contentType) |
| | | 45 | | { |
| | 43 | 46 | | if (string.IsNullOrWhiteSpace(contentType)) |
| | | 47 | | { |
| | 6 | 48 | | return string.Empty; |
| | | 49 | | } |
| | | 50 | | |
| | 37 | 51 | | var semicolon = contentType.IndexOf(';'); |
| | 37 | 52 | | var mediaType = semicolon >= 0 ? contentType[..semicolon] : contentType; |
| | | 53 | | |
| | 37 | 54 | | return mediaType.Trim().ToLowerInvariant(); |
| | | 55 | | } |
| | | 56 | | |
| | | 57 | | /// <summary> |
| | | 58 | | /// Canonicalizes the given content type to a known media type. |
| | | 59 | | /// </summary> |
| | | 60 | | /// <param name="contentType">The content type to canonicalize.</param> |
| | | 61 | | /// <returns>The canonical media type.</returns> |
| | | 62 | | public static string Canonicalize(string? contentType) |
| | | 63 | | { |
| | 36 | 64 | | var normalized = Normalize(contentType); |
| | 36 | 65 | | if (normalized.Length == 0) |
| | | 66 | | { |
| | 3 | 67 | | return string.Empty; |
| | | 68 | | } |
| | | 69 | | |
| | 33 | 70 | | if (ExactMap.TryGetValue(normalized, out var exact)) |
| | | 71 | | { |
| | 25 | 72 | | return exact; |
| | | 73 | | } |
| | | 74 | | |
| | 53 | 75 | | foreach (var (suffix, canonical) in StructuredSuffixMap) |
| | | 76 | | { |
| | 22 | 77 | | if (normalized.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) |
| | | 78 | | { |
| | 7 | 79 | | return canonical; |
| | | 80 | | } |
| | | 81 | | } |
| | | 82 | | |
| | 1 | 83 | | return normalized; |
| | | 84 | | } |
| | | 85 | | } |