1 /** 2 * Auxiliary utilities for dealing with URLs, besides the main matching logic. 3 */ 4 module path_matcher.url_util; 5 6 import std.range.primitives : isRandomAccessRange, hasLength, ElementType; 7 8 @safe: 9 10 /** 11 * Extracts segments from a slash-separated URL path and stores them in a given 12 * `store` array which has been pre-allocated. 13 * Params: 14 * path = The path to parse. 15 * store = The array to store segments in. Consider allocating this on the 16 * stack for performance improvements. 17 * Returns: The number of segments that were parsed, or -1 if the given store 18 * is too small to fit all of them. 19 */ 20 int toSegments(string path, scope string[] store) @nogc { 21 uint i = 0; 22 uint storeIdx = 0; 23 while (i < path.length) { 24 while (i < path.length && path[i] == '/') i++; 25 if (i >= path.length || path[i] == '?') return storeIdx; 26 immutable uint segmentStart = i; 27 uint segmentEnd = i + 1; 28 while (segmentEnd < path.length && path[segmentEnd] != '/' && path[segmentEnd] != '?') segmentEnd++; 29 if (storeIdx == store.length) return -1; 30 store[storeIdx++] = path[segmentStart .. segmentEnd]; 31 i = segmentEnd++; 32 } 33 return storeIdx; 34 } 35 36 unittest { 37 void doTest(string path, string[] expectedSegments) { 38 import std.format : format; 39 string[32] segments; 40 int count = toSegments(path, segments); 41 assert(count >= 0); 42 assert( 43 segments[0..count] == expectedSegments, 44 format!"Expected segments %s for path %s, but got %s."( 45 expectedSegments, 46 path, 47 segments 48 ) 49 ); 50 } 51 52 doTest("/test", ["test"]); 53 doTest("/test/", ["test"]); 54 doTest("test/", ["test"]); 55 doTest("/test?query=hello", ["test"]); 56 doTest("/test/one", ["test", "one"]); 57 doTest("/test/one", ["test", "one"]); 58 doTest("/abc/123/test/yes", ["abc", "123", "test", "yes"]); 59 doTest("a", ["a"]); 60 doTest("a/b", ["a", "b"]); 61 doTest("/a/b?c=d", ["a", "b"]); 62 doTest("", []); 63 doTest("/", []); 64 doTest("///", []); 65 } 66 67 /** 68 * Helper function to pop a single segment from an array of segments, and 69 * increment a referenced index variable. 70 * Params: 71 * segments = The list of segments to pop from. 72 * idx = The referenced index to increment. 73 * Returns: The segment that was popped, or null if we've reached the end of 74 * the segments list. 75 */ 76 string popSegment(I)(I segments, ref uint idx) if ( 77 isRandomAccessRange!I && 78 hasLength!I && 79 is(ElementType!I : string) 80 ) { 81 if (idx >= segments.length) return null; 82 return segments[idx++]; 83 } 84 85 unittest { 86 string[] a = ["a", "b", "c"]; 87 uint idx = 0; 88 assert(popSegment(a, idx) == "a"); 89 assert(idx == 1); 90 }