< Summary

Information
Class: NanoRoute.Internals.ValueParserDefinition
Assembly: NanoRoute.dll
File(s): /home/runner/work/nanoroute/nanoroute/Src/NanoRoute/Private/RoutePattern/ValueParserDefinition.cs
Line coverage
100%
Covered lines: 47
Uncovered lines: 0
Coverable lines: 47
Total lines: 117
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBlocks covered Blocks not covered
ValueParserDefinition()51
TryExtractArguments(...)280
Parse(...)300
Equals(...)330
GetHashCode()20

File(s)

/home/runner/work/nanoroute/nanoroute/Src/NanoRoute/Private/RoutePattern/ValueParserDefinition.cs

#LineLine coverage
 1/********************************************************************************
 2* ValueParserDefinition.cs                                                      *
 3*                                                                               *
 4* Author: Denes Solti                                                           *
 5********************************************************************************/
 6using System;
 7using System.Collections.Generic;
 8using System.Diagnostics;
 9using System.Runtime.CompilerServices;
 10using System.Text.RegularExpressions;
 11
 12namespace NanoRoute.Internals
 13{
 14    using Properties;
 15
 16    internal readonly struct ValueParserDefinition
 17    {
 18        private const string
 19            // Matches a valid route parser or parameter identifier.
 20            // Rules:
 21            // - must start with a letter or underscore
 22            // - remaining characters can be letters, digits, or underscores
 23            IDENTIFIER = @"[A-Za-z_]\w*",
 24
 25            // Matches a boolean literal.
 26            BOOLEAN = @"(?:true|false)",
 27
 28            // Matches a numeric literal without exponent notation.
 29            NUMBER = @"-?\d+(?:\.\d+)?",
 30
 31            // Matches a single-quoted string literal.
 32            STRING = @"'(?:\\'|[^'])*'",
 33
 34            // Matches any supported parser-argument value type.
 35            VALUE = $"(?:null|{BOOLEAN}|{NUMBER}|{STRING})",
 36
 37            // Captures a single name=value pair.
 38            NAME_VALUE_PAIR = $@"(?<name>{IDENTIFIER})\s*=\s*(?<value>{VALUE})",
 39
 40            // Matches the full input string and captures every name/value pair.
 41            ARGS_PATTERN = $@"\s*(?:{NAME_VALUE_PAIR}(?:\s*,\s*{NAME_VALUE_PAIR})*)?\s*",
 42
 43            // Matches and captures a complete parser-backed value definition.
 44            PARSER_DEFINITION_PATTERN = $@"\G(?<parserName>{IDENTIFIER})(?:\({ARGS_PATTERN}\))?";
 45
 146        private static readonly Regex s_parserDefinition = new(PARSER_DEFINITION_PATTERN, RuntimeFeature.IsDynamicCodeSu
 47
 48        private static bool TryExtractArguments(Match parsed, out Dictionary<string, string> result)
 149        {
 150            result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 51
 152            CaptureCollection
 153                names = parsed.Groups["name"].Captures,
 154                values = parsed.Groups["value"].Captures;
 55
 156            for (int i = 0; i < names.Count; i++)
 157            {
 158                string
 159                    name = names[i].Value,
 160                    value = values[i].Value;
 61
 162                if (value[0] is '\'')
 163                    value = value
 164                        .Substring(1, value.Length - 2)
 165                        .Replace("\\'", "'");
 66
 167                if (result.ContainsKey(name))
 168                    return false;
 69
 170                result.Add(name, value);
 171            }
 72
 173            return true;
 174        }
 75
 76        public static ValueParserDefinition Parse(string pattern, ref int offset)
 177        {
 178            if (s_parserDefinition.Match(pattern, offset) is not { Success: true, Index: int index, Length: int length }
 179                throw new InvalidOperationException(string.Format(Resources.Culture, Resources.ERR_INVALID_PATTERN, offs
 80
 181            string parserName = parsed.Groups["parserName"].Value;
 182            Debug.Assert(!string.IsNullOrEmpty(parserName), "Parser name could not be extracted");
 83
 184            ValueParserDefinition result = new()
 185            {
 186                Name = parserName,
 187                RawArguments = TryExtractArguments(parsed, out Dictionary<string, string> rawArguments)
 188                    ? rawArguments
 189                    : throw new InvalidOperationException(string.Format(Resources.Culture, Resources.ERR_INVALID_ARGUMEN
 190            };
 91
 192            offset += length;
 193            return result;
 194        }
 95
 96        public required string Name { get; init; }
 97
 98        public required IReadOnlyDictionary<string, string> RawArguments { get; init; }
 99
 100        public override bool Equals(object other)
 1101        {
 1102            if (other is not ValueParserDefinition otherDef || !otherDef.Name.Equals(Name, StringComparison.OrdinalIgnor
 1103                return false;
 104
 1105            if (RawArguments.Count != otherDef.RawArguments.Count)
 1106                return false;
 107
 1108            foreach (KeyValuePair<string, string> kvp in otherDef.RawArguments)
 1109                if (!RawArguments.TryGetValue(kvp.Key, out string val) || !kvp.Value.Equals(val, StringComparison.Ordina
 1110                    return false;
 111
 1112            return true;
 1113        }
 114
 1115        public override int GetHashCode() => throw new NotImplementedException();
 116    }
 117}