Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix path matching logic #1

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Matcha.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Matcha/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -403,6 +404,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Matcha/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -421,6 +423,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = MatchaTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -439,6 +442,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = MatchaTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
61 changes: 41 additions & 20 deletions Matcha/Matcha.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,38 +67,59 @@ public struct Matcha: Equatable {
/// Matcha(url: url, pattern: "/{A}/{B}/{C}/")?.value(at: 1) // is `to`
/// Matcha(url: url, pattern: "/{A}/{B}/{C}/")?.value(of: "C") // is `glory`
public init?(url: URL, pattern: String) {
let isPath = pattern.first == "/"
let patternUrl = isPath ? "dummy\(pattern)" : pattern
guard let url = url.trailingSlashed else { return nil }
let isPatternPath = pattern.first == "/"

guard let schemeRegex = try? NSRegularExpression(pattern: "[^https?:\\/\\/].+") else { return nil }
guard let matched = schemeRegex.firstMatch(in: patternUrl) else { return nil }
guard let patternComponents = URLComponents(string: pattern) else { return nil }

let paths = (patternUrl as NSString).substring(with: matched.range(at: 0)).split(separator: "/").map(String.init)

guard (url.host == paths.first || isPath)
&& (url.pathComponents.count == paths.count || (url.pathComponents.isEmpty && paths.count == 1)) else {
return nil
guard url.host == patternComponents.host || isPatternPath else {
return nil
}

var values: [String: String] = [:]
var list: [String] = []

for (pattern, path) in zip(paths, url.pathComponents) {
if pattern == path || path == "/" {
continue
}
let pathComponents = patternComponents.url?.pathComponents.dropFirst() ?? []

if pattern.first == "{" && pattern.last == "}" {
values[String(pattern.dropFirst().dropLast())] = path
list.append(path)
var regexComponents: [String] = []
var parameterNames: [String] = []
for pathComponent in pathComponents {
if pathComponent.first == "{" && pathComponent.last == "}" {
let parameterName = String(pathComponent.dropFirst().dropLast())
regexComponents.append("(?<\(parameterName)>.+)")
parameterNames.append(parameterName)
}
else {
return nil
regexComponents.append(pathComponent)
}
}

let regexPattern = regexComponents.joined(separator: "/")
let urlPath = url.path

guard let regex = try? NSRegularExpression(pattern: "/\(regexPattern)$"),
let match = regex.firstMatch(in: urlPath) else { return nil }

var values: [String: String] = [:]
var list: [String] = []
for parameterName in parameterNames {
guard let range = match.range(withName: parameterName, in: urlPath) else { continue }
let value = String(urlPath[range])
values[parameterName] = value
list.append(value)
}

self.url = url
self.values = values
self.list = list
}
}

private extension NSTextCheckingResult {
func range(withName name: String, in string: String) -> Range<String.Index>? {
Range(range(withName: name), in: string)
}
}

private extension URL {
var trailingSlashed: URL? {
absoluteString.hasSuffix("/") ? self : URL(string: absoluteString + "/")
}
}
6 changes: 5 additions & 1 deletion MatchaTests/MatchaTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,17 @@ class MatchaTests: XCTestCase {
XCTAssertNil(matcha.matched(""))
XCTAssertNil(Matcha(url: URL(string: "https://example.com/")!).matched("/path"))
XCTAssertNil(Matcha(url: URL(string: "https://example.com/")!).matched(""))

XCTAssertNotNil(matcha.matched("https://example.com/path/to/glory/"))
XCTAssertNotNil(matcha.matched("https://example.com/path/to/glory"))
XCTAssertNotNil(matcha.matched("/path/to/glory/"))
XCTAssertNotNil(matcha.matched("/path/to/glory"))
XCTAssertNotNil(Matcha(url: URL(string: "https://example.com/")!).matched("/"))
XCTAssertNotNil(Matcha(url: URL(string: "https://example.com")!).matched("/"))
XCTAssertNotNil(matcha.matched("/path/{A}"))
XCTAssertEqual(matcha.matched("/path/{A}")?.value(of: "A"), "to/glory")
XCTAssertEqual(matcha.matched("/{A}")?.value(of: "A"), "path/to/glory")
XCTAssertEqual(matcha.matched("/{A}/{B}")?.value(of: "A"), "path/to")
XCTAssertEqual(matcha.matched("/{A}/{B}")?.value(of: "B"), "glory")

let urlPattern = "https://example.com/{A}/{B}/{C}/"
let urlPatternMatched = matcha.matched(urlPattern)
Expand Down