< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.UriTemplateCompoundPathSegment
Assembly: CoreWCF.WebHttp
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.WebHttp/src/CoreWCF/UriTemplateCompoundPathSegment.cs
Line coverage
35%
Covered lines: 55
Uncovered lines: 99
Coverable lines: 154
Total lines: 494
Line coverage: 35.7%
Branch coverage
28%
Covered branches: 32
Total branches: 113
Branch coverage: 28.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.WebHttp/src/CoreWCF/UriTemplateCompoundPathSegment.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Collections.Specialized;
 7using System.Text;
 8using CoreWCF.Runtime;
 9
 10namespace CoreWCF
 11{
 12    // Thin wrapper around formatted string; use type system to help ensure we
 13    // are doing canonicalization right/consistently - the literal sections are held in an
 14    // un-escaped format
 15    // We are assuming that the string will be always built as Lit{Var}Lit[{Var}Lit[{Var}Lit[...]]],
 16    // when the first and last literals may be empty
 17    internal class UriTemplateCompoundPathSegment : UriTemplatePathSegment, IComparable<UriTemplateCompoundPathSegment>
 18    {
 19        private readonly string _firstLiteral;
 20        private readonly List<VarAndLitPair> _varLitPairs;
 21        private CompoundSegmentClass _csClass;
 22
 23        private UriTemplateCompoundPathSegment(string originalSegment, bool endsWithSlash, string firstLiteral)
 2424            : base(originalSegment, UriTemplatePartType.Compound, endsWithSlash)
 25        {
 2426            _firstLiteral = firstLiteral;
 2427            _varLitPairs = new List<VarAndLitPair>();
 2428        }
 29
 30        public static new UriTemplateCompoundPathSegment CreateFromUriTemplate(string segment, UriTemplate template)
 31        {
 2432            string origSegment = segment;
 2433            bool endsWithSlash = segment.EndsWith("/", StringComparison.Ordinal);
 2434            if (endsWithSlash)
 35            {
 036                segment = segment.Remove(segment.Length - 1);
 37            }
 38
 2439            int nextVarStart = segment.IndexOf("{", StringComparison.Ordinal);
 40            Fx.Assert(nextVarStart >= 0, "The method is only called after identifying a '{' character in the segment");
 2441            string firstLiteral = ((nextVarStart > 0) ? segment.Substring(0, nextVarStart) : string.Empty);
 2442            if (firstLiteral.IndexOf(UriTemplate.WildcardPath, StringComparison.Ordinal) != -1)
 43            {
 044                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
 045                    SR.Format(SR.UTInvalidWildcardInVariableOrLiteral, template._originalTemplate, UriTemplate.WildcardP
 46            }
 47
 2448            UriTemplateCompoundPathSegment result = new UriTemplateCompoundPathSegment(origSegment, endsWithSlash,
 2449                ((firstLiteral != string.Empty) ? Uri.UnescapeDataString(firstLiteral) : string.Empty));
 50            do
 51            {
 4852                int nextVarEnd = segment.IndexOf("}", nextVarStart + 1, StringComparison.Ordinal);
 4853                if (nextVarEnd < nextVarStart + 2)
 54                {
 055                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
 056                        SR.Format(SR.UTInvalidFormatSegmentOrQueryPart, segment)));
 57                }
 58
 4859                string varName = template.AddPathVariable(UriTemplatePartType.Compound,
 4860                    segment.Substring(nextVarStart + 1, nextVarEnd - nextVarStart - 1), out bool hasDefault);
 4861                if (hasDefault)
 62                {
 063                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
 064                        SR.Format(SR.UTDefaultValueToCompoundSegmentVar, template, origSegment, varName)));
 65                }
 66
 4867                nextVarStart = segment.IndexOf("{", nextVarEnd + 1, StringComparison.Ordinal);
 68                string literal;
 4869                if (nextVarStart > 0)
 70                {
 2471                    if (nextVarStart == nextVarEnd + 1)
 72                    {
 073                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(nameof(template),
 074                            SR.Format(SR.UTDoesNotSupportAdjacentVarsInCompoundSegment, template, segment));
 75                    }
 2476                    literal = segment.Substring(nextVarEnd + 1, nextVarStart - nextVarEnd - 1);
 77                }
 2478                else if (nextVarEnd + 1 < segment.Length)
 79                {
 080                    literal = segment.Substring(nextVarEnd + 1);
 81                }
 82                else
 83                {
 2484                    literal = string.Empty;
 85                }
 86
 4887                if (literal.IndexOf(UriTemplate.WildcardPath, StringComparison.Ordinal) != -1)
 88                {
 089                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
 090                        SR.Format(SR.UTInvalidWildcardInVariableOrLiteral, template._originalTemplate, UriTemplate.Wildc
 91                }
 92
 4893                if (literal.IndexOf('}') != -1)
 94                {
 095                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
 096                        SR.Format(SR.UTInvalidFormatSegmentOrQueryPart, segment)));
 97                }
 98
 4899                result._varLitPairs.Add(new VarAndLitPair(varName, ((literal == string.Empty) ? string.Empty : Uri.Unesc
 48100            } while (nextVarStart > 0);
 101
 24102            if (string.IsNullOrEmpty(result._firstLiteral))
 103            {
 24104                if (string.IsNullOrEmpty(result._varLitPairs[result._varLitPairs.Count - 1].Literal))
 105                {
 24106                    result._csClass = CompoundSegmentClass.HasNoPrefixNorSuffix;
 107                }
 108                else
 109                {
 0110                    result._csClass = CompoundSegmentClass.HasOnlySuffix;
 111                }
 112            }
 113            else
 114            {
 0115                if (string.IsNullOrEmpty(result._varLitPairs[result._varLitPairs.Count - 1].Literal))
 116                {
 0117                    result._csClass = CompoundSegmentClass.HasOnlyPrefix;
 118                }
 119                else
 120                {
 0121                    result._csClass = CompoundSegmentClass.HasPrefixAndSuffix;
 122                }
 123            }
 124
 24125            return result;
 126        }
 127
 128        public override void Bind(string[] values, ref int valueIndex, StringBuilder path)
 129        {
 130            Fx.Assert(valueIndex + _varLitPairs.Count <= values.Length, "Not enough values to bind");
 131
 0132            path.Append(_firstLiteral);
 0133            for (int pairIndex = 0; pairIndex < _varLitPairs.Count; pairIndex++)
 134            {
 0135                path.Append(values[valueIndex++]);
 0136                path.Append(_varLitPairs[pairIndex].Literal);
 137            }
 138
 0139            if (EndsWithSlash)
 140            {
 0141                path.Append("/");
 142            }
 0143        }
 144
 145        public override bool IsEquivalentTo(UriTemplatePathSegment other, bool ignoreTrailingSlash)
 146        {
 0147            if (other == null)
 148            {
 149                Fx.Assert("why would we ever call this?");
 0150                return false;
 151            }
 152
 0153            if (!ignoreTrailingSlash && (EndsWithSlash != other.EndsWithSlash))
 154            {
 0155                return false;
 156            }
 157
 0158            if (!(other is UriTemplateCompoundPathSegment otherAsCompound))
 159            {
 160                // if other can't be cast as a compound then it can't be equivalent
 0161                return false;
 162            }
 163
 0164            if (_varLitPairs.Count != otherAsCompound._varLitPairs.Count)
 165            {
 0166                return false;
 167            }
 168
 0169            if (StringComparer.OrdinalIgnoreCase.Compare(_firstLiteral, otherAsCompound._firstLiteral) != 0)
 170            {
 0171                return false;
 172            }
 173
 0174            for (int pairIndex = 0; pairIndex < _varLitPairs.Count; pairIndex++)
 175            {
 0176                if (StringComparer.OrdinalIgnoreCase.Compare(_varLitPairs[pairIndex].Literal,
 0177                    otherAsCompound._varLitPairs[pairIndex].Literal) != 0)
 178                {
 0179                    return false;
 180                }
 181            }
 182
 0183            return true;
 184        }
 185
 186        public override bool IsMatch(UriTemplateLiteralPathSegment segment, bool ignoreTrailingSlash)
 187        {
 1188            if (!ignoreTrailingSlash && (EndsWithSlash != segment.EndsWithSlash))
 189            {
 0190                return false;
 191            }
 192
 1193            return TryLookup(segment.AsUnescapedString(), null);
 194        }
 195
 196        public override void Lookup(string segment, NameValueCollection boundParameters)
 197        {
 1198            if (!TryLookup(segment, boundParameters))
 199            {
 200                Fx.Assert("How can that be? Lookup is expected to be called after IsMatch");
 0201                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
 0202                    SR.Format(SR.UTCSRLookupBeforeMatch)));
 203            }
 1204        }
 205
 206        private bool TryLookup(string segment, NameValueCollection boundParameters)
 207        {
 2208            int segmentPosition = 0;
 2209            if (!string.IsNullOrEmpty(_firstLiteral))
 210            {
 0211                if (segment.StartsWith(_firstLiteral, StringComparison.Ordinal))
 212                {
 0213                    segmentPosition = _firstLiteral.Length;
 214                }
 215                else
 216                {
 0217                    return false;
 218                }
 219            }
 220
 8221            for (int pairIndex = 0; pairIndex < _varLitPairs.Count - 1; pairIndex++)
 222            {
 2223                int nextLiteralPosition = segment.IndexOf(_varLitPairs[pairIndex].Literal, segmentPosition, StringCompar
 2224                if (nextLiteralPosition < segmentPosition + 1)
 225                {
 0226                    return false;
 227                }
 2228                if (boundParameters != null)
 229                {
 1230                    string varValue = segment.Substring(segmentPosition, nextLiteralPosition - segmentPosition);
 1231                    boundParameters.Add(_varLitPairs[pairIndex].VarName, varValue);
 232                }
 2233                segmentPosition = nextLiteralPosition + _varLitPairs[pairIndex].Literal.Length;
 234            }
 235
 2236            if (segmentPosition < segment.Length)
 237            {
 2238                if (string.IsNullOrEmpty(_varLitPairs[_varLitPairs.Count - 1].Literal))
 239                {
 2240                    if (boundParameters != null)
 241                    {
 1242                        boundParameters.Add(_varLitPairs[_varLitPairs.Count - 1].VarName,
 1243                            segment.Substring(segmentPosition));
 244                    }
 2245                    return true;
 246                }
 0247                else if ((segmentPosition + _varLitPairs[_varLitPairs.Count - 1].Literal.Length < segment.Length) &&
 0248                    segment.EndsWith(_varLitPairs[_varLitPairs.Count - 1].Literal, StringComparison.Ordinal))
 249                {
 0250                    if (boundParameters != null)
 251                    {
 0252                        boundParameters.Add(_varLitPairs[_varLitPairs.Count - 1].VarName,
 0253                            segment.Substring(segmentPosition, segment.Length - segmentPosition - _varLitPairs[_varLitPa
 254                    }
 0255                    return true;
 256                }
 257                else
 258                {
 0259                    return false;
 260                }
 261            }
 262            else
 263            {
 0264                return false;
 265            }
 266        }
 267
 268        // A note about comparing compound segments:
 269        //  We are using this for generating the sorted collections at the nodes of the UriTemplateTrieNode.
 270        //  The idea is that we are sorting the segments based on preferred matching, when we have two
 271        //  compound segments matching the same wire segment, we will give preference to the preceding one.
 272        //  The order is based on the following concepts:
 273        //   - We are defining four classes of compound segments: prefix+suffix, prefix-only, suffix-only
 274        //      and none
 275        //   - Whenever we are comparing segments from different class the preferred one is the segment with
 276        //      the prefared class, based on the order we defined them (p+s \ p \ s \ n).
 277        //   - Within each class the preference is based on the prefix\suffix, while prefix has precedence
 278        //      over suffix if both exists.
 279        //   - If after comparing the class, as well as the prefix\suffix, we didn't reach to a conclusion,
 280        //      the preference is given to the segment with more variables parts.
 281        //  This order mostly follows the intuitive common sense; the major issue comes from preferring the
 282        //  prefix over the suffix in the case where both exist. This is derived from the problematic of any
 283        //  other type of solution that don't prefere the prefix over the suffix or vice versa. To better
 284        //  understanding lets considered the following example:
 285        //   In comparing 'foo{x}bar' and 'food{x}ar', unless we are preferring prefix or suffix, we have
 286        //   to state that they have the same order. So is the case with 'foo{x}babar' and 'food{x}ar', which
 287        //   will lead us to claiming the 'foo{x}bar' and 'foo{x}babar' are from the same order, which they
 288        //   clearly are not.
 289        //  Taking other approaches to this problem results in similar cases. The only solution is preferring
 290        //  either the prefix or the suffix over the other; since we already preferred prefix over suffix
 291        //  implicitly (we preferred the prefix only class over the suffix only, we also prefared literal
 292        //  over variable, if in the same path segment) that still maintain consistency.
 293        //  Therefore:
 294        //    - 'food{var}' should be before 'foo{var}'; '{x}.{y}.{z}' should be before '{x}.{y}'.
 295        //    - the order between '{var}bar' and '{var}qux' is not important
 296        //    - '{x}.{y}' and '{x}_{y}' should have the same order
 297        //    - 'foo{x}bar' is less preferred than 'food{x}ar'
 298        //  In the above third case - if we are opening the table with allowDuplicate=false, we will throw;
 299        //  if we are opening it with allowDuplicate=true we will let it go and might match both templates
 300        //  for certain wire candidates.
 301        int IComparable<UriTemplateCompoundPathSegment>.CompareTo(UriTemplateCompoundPathSegment other)
 302        {
 303            Fx.Assert(other != null, "We are only expected to get here for comparing real compound segments");
 304
 0305            switch (_csClass)
 306            {
 307                case CompoundSegmentClass.HasPrefixAndSuffix:
 0308                    switch (other._csClass)
 309                    {
 310                        case CompoundSegmentClass.HasPrefixAndSuffix:
 0311                            return CompareToOtherThatHasPrefixAndSuffix(other);
 312
 313                        case CompoundSegmentClass.HasOnlyPrefix:
 314                        case CompoundSegmentClass.HasOnlySuffix:
 315                        case CompoundSegmentClass.HasNoPrefixNorSuffix:
 0316                            return -1;
 317
 318                        default:
 319                            Fx.Assert("Invalid other.CompoundSegmentClass");
 0320                            return 0;
 321                    }
 322
 323                case CompoundSegmentClass.HasOnlyPrefix:
 0324                    switch (other._csClass)
 325                    {
 326                        case CompoundSegmentClass.HasPrefixAndSuffix:
 0327                            return 1;
 328
 329                        case CompoundSegmentClass.HasOnlyPrefix:
 0330                            return CompareToOtherThatHasOnlyPrefix(other);
 331
 332                        case CompoundSegmentClass.HasOnlySuffix:
 333                        case CompoundSegmentClass.HasNoPrefixNorSuffix:
 0334                            return -1;
 335
 336                        default:
 337                            Fx.Assert("Invalid other.CompoundSegmentClass");
 0338                            return 0;
 339                    }
 340
 341                case CompoundSegmentClass.HasOnlySuffix:
 0342                    switch (other._csClass)
 343                    {
 344                        case CompoundSegmentClass.HasPrefixAndSuffix:
 345                        case CompoundSegmentClass.HasOnlyPrefix:
 0346                            return 1;
 347
 348                        case CompoundSegmentClass.HasOnlySuffix:
 0349                            return CompareToOtherThatHasOnlySuffix(other);
 350
 351                        case CompoundSegmentClass.HasNoPrefixNorSuffix:
 0352                            return -1;
 353
 354                        default:
 355                            Fx.Assert("Invalid other.CompoundSegmentClass");
 0356                            return 0;
 357                    }
 358
 359                case CompoundSegmentClass.HasNoPrefixNorSuffix:
 0360                    switch (other._csClass)
 361                    {
 362                        case CompoundSegmentClass.HasPrefixAndSuffix:
 363                        case CompoundSegmentClass.HasOnlyPrefix:
 364                        case CompoundSegmentClass.HasOnlySuffix:
 0365                            return 1;
 366
 367                        case CompoundSegmentClass.HasNoPrefixNorSuffix:
 0368                            return CompareToOtherThatHasNoPrefixNorSuffix(other);
 369
 370                        default:
 371                            Fx.Assert("Invalid other.CompoundSegmentClass");
 0372                            return 0;
 373                    }
 374
 375                default:
 376                    Fx.Assert("Invalid this.CompoundSegmentClass");
 0377                    return 0;
 378            }
 379        }
 380
 381        private int CompareToOtherThatHasPrefixAndSuffix(UriTemplateCompoundPathSegment other)
 382        {
 383            Fx.Assert(_csClass == CompoundSegmentClass.HasPrefixAndSuffix, "Otherwise, how did we got here?");
 384            Fx.Assert(other._csClass == CompoundSegmentClass.HasPrefixAndSuffix, "Otherwise, how did we got here?");
 385
 386            // In this case we are determining the order based on the prefix of the two segments,
 387            //  then by their suffix and then based on the number of variables
 0388            int prefixOrder = ComparePrefixToOtherPrefix(other);
 0389            if (prefixOrder == 0)
 390            {
 0391                int suffixOrder = CompareSuffixToOtherSuffix(other);
 0392                if (suffixOrder == 0)
 393                {
 0394                    return (other._varLitPairs.Count - _varLitPairs.Count);
 395                }
 396                else
 397                {
 0398                    return suffixOrder;
 399                }
 400            }
 401            else
 402            {
 0403                return prefixOrder;
 404            }
 405        }
 406
 407        private int CompareToOtherThatHasOnlyPrefix(UriTemplateCompoundPathSegment other)
 408        {
 409            Fx.Assert(_csClass == CompoundSegmentClass.HasOnlyPrefix, "Otherwise, how did we got here?");
 410            Fx.Assert(other._csClass == CompoundSegmentClass.HasOnlyPrefix, "Otherwise, how did we got here?");
 411
 412            // In this case we are determining the order based on the prefix of the two segments,
 413            //  then based on the number of variables
 0414            int prefixOrder = ComparePrefixToOtherPrefix(other);
 0415            if (prefixOrder == 0)
 416            {
 0417                return (other._varLitPairs.Count - _varLitPairs.Count);
 418            }
 419            else
 420            {
 0421                return prefixOrder;
 422            }
 423        }
 424
 425        private int CompareToOtherThatHasOnlySuffix(UriTemplateCompoundPathSegment other)
 426        {
 427            Fx.Assert(_csClass == CompoundSegmentClass.HasOnlySuffix, "Otherwise, how did we got here?");
 428            Fx.Assert(other._csClass == CompoundSegmentClass.HasOnlySuffix, "Otherwise, how did we got here?");
 429
 430            // In this case we are determining the order based on the suffix of the two segments,
 431            //  then based on the number of variables
 0432            int suffixOrder = CompareSuffixToOtherSuffix(other);
 0433            if (suffixOrder == 0)
 434            {
 0435                return (other._varLitPairs.Count - _varLitPairs.Count);
 436            }
 437            else
 438            {
 0439                return suffixOrder;
 440            }
 441        }
 442
 443        private int CompareToOtherThatHasNoPrefixNorSuffix(UriTemplateCompoundPathSegment other)
 444        {
 445            Fx.Assert(_csClass == CompoundSegmentClass.HasNoPrefixNorSuffix, "Otherwise, how did we got here?");
 446            Fx.Assert(other._csClass == CompoundSegmentClass.HasNoPrefixNorSuffix, "Otherwise, how did we got here?");
 447
 448            // In this case the order is determined by the number of variables
 0449            return (other._varLitPairs.Count - _varLitPairs.Count);
 450        }
 451
 0452        private int ComparePrefixToOtherPrefix(UriTemplateCompoundPathSegment other) => string.Compare(other._firstLiter
 453
 454        private int CompareSuffixToOtherSuffix(UriTemplateCompoundPathSegment other)
 455        {
 0456            string reversedSuffix = ReverseString(_varLitPairs[_varLitPairs.Count - 1].Literal);
 0457            string reversedOtherSuffix = ReverseString(other._varLitPairs[other._varLitPairs.Count - 1].Literal);
 458
 0459            return string.Compare(reversedOtherSuffix, reversedSuffix, StringComparison.OrdinalIgnoreCase);
 460        }
 461
 462        private static string ReverseString(string stringToReverse)
 463        {
 0464            char[] reversedString = new char[stringToReverse.Length];
 0465            for (int i = 0; i < stringToReverse.Length; i++)
 466            {
 0467                reversedString[i] = stringToReverse[stringToReverse.Length - i - 1];
 468            }
 469
 0470            return new string(reversedString);
 471        }
 472
 473        internal enum CompoundSegmentClass
 474        {
 475            Undefined,
 476            HasPrefixAndSuffix,
 477            HasOnlyPrefix,
 478            HasOnlySuffix,
 479            HasNoPrefixNorSuffix
 480        }
 481
 482        internal struct VarAndLitPair
 483        {
 484            public VarAndLitPair(string varName, string literal)
 485            {
 48486                VarName = varName;
 48487                Literal = literal;
 48488            }
 489
 30490            public string Literal { get; }
 2491            public string VarName { get; }
 492        }
 493    }
 494}