diff --git a/README.md b/README.md index f7448d5..bf49760 100644 --- a/README.md +++ b/README.md @@ -39,12 +39,12 @@ Imagine to implement Pokédex on iOS. You can access somewhere via URL scheme. ```swift router = DefaultRouter(scheme: "pokedex") router.register([ - ("pokedex://pokemons", { context in + ("/pokemons", { context in let type: Type? = context.parameter(for: "type") presentPokedexListViewController(for: type) return true }), - ("pokedex://pokemons/:pokedexID", { context in + ("/pokemons/:pokedexID", { context in guard let pokedexID: Int? = try? context.argument(for: "pokedexID") else { // pokedexID must be Int return false diff --git a/Sources/Crossroad/Router.swift b/Sources/Crossroad/Router.swift index 87e33d3..ee6543a 100644 --- a/Sources/Crossroad/Router.swift +++ b/Sources/Crossroad/Router.swift @@ -59,21 +59,28 @@ public final class Router { return routes.first { $0.responds(to: url, userInfo: userInfo) } != nil } + private func canonicalizePattern(_ pattern: String) -> String { + if pattern.hasPrefix("/") { + return String(pattern.dropFirst()) + } + return pattern + } + public func register(_ routes: [(String, Route.Handler)]) { for (pattern, handler) in routes { let patternURLString: String switch prefix { case .scheme(let scheme): if pattern.hasPrefix("\(scheme)://") { - patternURLString = pattern + patternURLString = canonicalizePattern(pattern) } else { - patternURLString = "\(scheme)://\(pattern)" + patternURLString = "\(scheme)://\(canonicalizePattern(pattern))" } case .url(let url): if pattern.hasPrefix(url.absoluteString) { - patternURLString = pattern + patternURLString = canonicalizePattern(pattern) } else { - patternURLString = url.appendingPathComponent(pattern).absoluteString + patternURLString = url.appendingPathComponent(canonicalizePattern(pattern)).absoluteString } } guard let patternURL = PatternURL(string: patternURLString) else { diff --git a/Tests/CrossroadTests/RouterTests.swift b/Tests/CrossroadTests/RouterTests.swift index 519e6cc..8c5e2cb 100644 --- a/Tests/CrossroadTests/RouterTests.swift +++ b/Tests/CrossroadTests/RouterTests.swift @@ -76,6 +76,29 @@ final class RouterTest: XCTestCase { XCTAssertFalse(router.responds(to: URL(string: "spam/ham")!)) } + func testCanRespondWithRelativePath() { + let router = SimpleRouter(scheme: scheme) + router.register([ + ("/static", { _ in true }), + ("/foo/bar", { _ in true }), + ("/spam/ham", { _ in false }), + ("/:keyword", { _ in true }), + ("/foo/:keyword", { _ in true }), + ]) + XCTAssertTrue(router.responds(to: URL(string: "foobar://static")!)) + XCTAssertTrue(router.responds(to: URL(string: "foobar://foo")!)) + XCTAssertTrue(router.responds(to: URL(string: "foobar://foo/bar")!)) + XCTAssertTrue(router.responds(to: URL(string: "foobar://foo/10000")!)) + XCTAssertFalse(router.responds(to: URL(string: "notfoobar://aaa/bbb")!)) + XCTAssertTrue(router.responds(to: URL(string: "foobar://spam/ham")!)) + XCTAssertFalse(router.responds(to: URL(string: "static")!)) + XCTAssertFalse(router.responds(to: URL(string: "foo")!)) + XCTAssertFalse(router.responds(to: URL(string: "foo/bar")!)) + XCTAssertFalse(router.responds(to: URL(string: "foo/10000")!)) + XCTAssertFalse(router.responds(to: URL(string: "aaa/bbb")!)) + XCTAssertFalse(router.responds(to: URL(string: "spam/ham")!)) + } + func testCanRespondWithoutPrefixWithURLPrefix() { let router = SimpleRouter(url: URL(string: "https://example.com/")!) router.register([ @@ -231,6 +254,50 @@ final class RouterTest: XCTestCase { wait(for: [expectation], timeout: 2.0) } + func testHandleWithSlashPrefix() { + let router = SimpleRouter(scheme: scheme) + let expectation = self.expectation(description: "Should called handler four times") + expectation.expectedFulfillmentCount = 4 + router.register([ + ("/static", { context in + XCTAssertEqual(context.url, URL(string: "foobar://static")!) + expectation.fulfill() + return true + }), + ("/foo/bar", { context in + XCTAssertEqual(context.parameter(for: "param0"), 123) + XCTAssertEqual(context.url, URL(string: "foobar://foo/bar?param0=123")!) + expectation.fulfill() + return true + }), + ("/:keyword", { context in + XCTAssertEqual(context.url, URL(string: "foobar://hoge")!) + XCTAssertEqual(try? context.argument(for: "keyword"), "hoge") + expectation.fulfill() + return true + }), + ("/foo/:keyword/:keyword2", { context in + XCTAssertEqual(context.url, URL(string: "foobar://foo/hoge/fuga")!) + XCTAssertEqual(try? context.argument(for: "keyword"), "hoge") + XCTAssertEqual(try? context.argument(for: "keyword2"), "fuga") + expectation.fulfill() + return true + }), + ]) + XCTAssertTrue(router.openIfPossible(URL(string: "foobar://static")!)) + XCTAssertTrue(router.openIfPossible(URL(string: "foobar://foo/bar?param0=123")!)) + XCTAssertTrue(router.openIfPossible(URL(string: "foobar://hoge")!)) + XCTAssertTrue(router.openIfPossible(URL(string: "foobar://foo/hoge/fuga")!)) + XCTAssertFalse(router.openIfPossible(URL(string: "foobar://spam/ham")!)) + XCTAssertFalse(router.openIfPossible(URL(string: "notfoobar://static")!)) + XCTAssertFalse(router.openIfPossible(URL(string: "static")!)) + XCTAssertFalse(router.openIfPossible(URL(string: "foo/bar?param0=123")!)) + XCTAssertFalse(router.openIfPossible(URL(string: "hoge")!)) + XCTAssertFalse(router.openIfPossible(URL(string: "foo/hoge/fuga")!)) + XCTAssertFalse(router.openIfPossible(URL(string: "spam/ham")!)) + wait(for: [expectation], timeout: 2.0) + } + func testHandleWithoutPrefixWithURLPrefix() { let router = SimpleRouter(url: URL(string: "https://example.com")!) let expectation = self.expectation(description: "Should called handler four times")