/// <summary> /// Implementation of parsing, returning the error message for a FormatException if parsing fails. /// </summary> private string TryParseNameInternal(string name, out TemplatedResourceName result) { GaxPreconditions.CheckNotNull(name, nameof(name)); string serviceName = null; if (name.StartsWith("//")) { int nameEnd = name.IndexOf('/', 2); // Can't call ValidateServiceName as we don't want to throw... if (nameEnd == 2) { result = null; return("Service name cannot be empty"); } // It's *just about* plausible to have a template of ** and a value of "//service". if (nameEnd == -1) { serviceName = name.Substring(2); name = ""; } else { serviceName = name.Substring(2, nameEnd - 2); name = name.Substring(nameEnd + 1); } } string[] nameSegments = name == "" ? new string[0] : name.Split(s_slashSplit); if (_hasPathWildcard) { // The path wildcard can be empty... if (nameSegments.Length < _segments.Count - 1) { result = null; return("Name does not match template: too few segments"); } } else if (nameSegments.Length != _segments.Count) { result = null; return("Name does not match template: incorrect number of segments"); } string[] resourceIds = new string[ParameterCount]; int resourceIdIndex = 0; int nameSegmentIndex = 0; foreach (var segment in _segments) { switch (segment.Kind) { case SegmentKind.Literal: var nameSegment = nameSegments[nameSegmentIndex++]; if (nameSegment != segment.Value) { result = null; return($"Name does not match template in literal segment: '{nameSegment}' != '{segment.Value}'"); } break; case SegmentKind.Wildcard: // Could use segment.ValidateWildcard, but the exception wouldn't be as clean. var value = nameSegments[nameSegmentIndex++]; if (value == "") { result = null; return("Name does not match template: wildcard segment is empty"); } resourceIds[resourceIdIndex++] = value; break; case SegmentKind.PathWildcard: // Work out how many segments to consume based on the number of segments in the template and the // actual number of segments in the specified name int count = nameSegments.Length - _segments.Count + 1; // Make the common case more efficient if (count == 1) { resourceIds[resourceIdIndex++] = nameSegments[nameSegmentIndex++]; } else { resourceIds[resourceIdIndex++] = string.Join("/", nameSegments.Skip(nameSegmentIndex).Take(count)); nameSegmentIndex += count; } break; } } result = TemplatedResourceName.CreateWithShallowCopy(this, serviceName, resourceIds); return(null); // Success! }
/// <summary> /// Attempts to parse the given resource name against this template, returning <c>null</c> on failure. /// </summary> /// <remarks> /// Although this method returns <c>null</c> if a name is passed in which doesn't match the template, /// it still throws <see cref="ArgumentNullException"/> if <paramref name="name"/> is null, as this would /// usually indicate a programming error rather than a data error. /// </remarks> /// <param name="name">The resource name to parse against this template. Must not be null.</param> /// <param name="result">When this method returns, the parsed resource name or <c>null</c> if parsing fails.</param> /// <returns><c>true</c> if the name was parsed successfully; <c>false</c> otherwise.</returns> public bool TryParseName(string name, out TemplatedResourceName result) { return(TryParseNameInternal(name, out result) == null); }