Deeplink - A declarative approach to deeplinks handling

Hello! I just open-sourced a library that I've been developing and using for the last few months: it provides a declarative(ish) way of defining deeplink templates using Swift's String interpolation, so that you don't have to manually parse URLs in order to extract the data you need from them.

Deeplink on GitHub

Few examples:

Parsing a single deeplink
struct TestData {
    let id: String?
    let text: String?
}

let myTestDeeplink: Deeplink<TestData> = try! "/test/\(\.id)/\(\.text)"

let url = URL(string: "https://ticketswap.com/test/123/abc")!

// Object to write data into
var result = TestData()

// Use the deeplink to parse the URL, extracting its data into `result`
try myTestDeeplink.parse(url, into: &result)

print(result.id) // Will print `.some(123)`
print(result.text) // Will print `.some(abc)`
Defining a list of deeplink templates to try out in order
// This is the object that holds the list of deeplinks to try.
let center = DeeplinksCenter()

// Data types where to store parsed values
struct Artist: Equatable {
    var id: String?
    var slug: String?
}

struct Location: Equatable {
    var id: String?
    var slug: String?
    var period: String?
}

struct Event: Equatable {
    var id: String?
    var slug: String?
}

// Instances where to put the parsed data
var artist = Artist()
var location = Location()
var event = Event()

// URLs to parse
let artistURL = URL(string: "https://ticketswap.com/artist/metallica/123456")!
let locationURL = URL(string: "https://ticketswap.com/location/amsterdam/1234567/24-06-2019")!
let eventURL = URL(string: "https://ticketswap.com/event/awakenings/123")!

// Deeplink templates
let artistDeeplink = try! "/artist/\(\.slug)/\(\.id)"
    as Deeplink<Artist>
let locationDeeplink = try! "/location/\(\.slug)/\(\.id)/\(\.period)"
    as Deeplink<Location>
let eventDeeplink = try! "/event/\(\.slug)/\(\.id)"
    as Deeplink<Event>

// Registering a deeplink template into the center, using the `artistDeeplink` to parse data into the `artist` var, and run the `ifMatching` closure if the template matches a URL.
center.register(
    deeplink: artistDeeplink,
    assigningTo: artist,
    ifMatching: { url, newArtist in

    // The parsed artist info is available here
    if let id = newArtist.id {
        print(id)
    }
})
.register(
    deeplink: locationDeeplink,
    assigningTo: location,
    ifMatching: { url, newLocation in
    if let id = newLocation.id {
        print(id)
    }
})
.register(
    deeplink: eventDeeplink,
    assigningTo: event,
    ifMatching: { url, newEvent in
    if let id = newEvent.id {
        print(id)
    }
})

try center.parse(url: artistURL) // prints "123456"
try center.parse(url: locationURL) // prints "1234567"
try center.parse(url: eventURL) // prints "123"
Defining a list of templates using a result builder
fileprivate struct TestData: DefaultInitializable {
    var arg1: String?
    var arg2: String?
}

fileprivate struct TestData2 {
    var arg1: String?
    var arg2: String?
}

let link1 = "/test/1" as Deeplink<Void>
let link2 = try "/test/\(\.arg1)/\(\.arg2)" as Deeplink<TestData>
let link3 = try "/test2/\(\.arg1)/\(\.arg2)" as Deeplink<TestData2>

let center = DeeplinksCenter {

    link1 { url in
        XCTAssertEqual(url.absoluteString, "https://apple.com/test/1")
        expectSimpleLink.fulfill()
        return true
    }

    link2 { url, value in
        XCTAssertEqual(url.absoluteString, "https://apple.com/test/a/b")
        XCTAssertEqual(value.arg1, "a")
        XCTAssertEqual(value.arg2, "b")
        expectInitializableDataLink.fulfill()
        return true
    }

    link3(
        assigningTo: .init(arg1: "default", arg2: "default")
    ) { (url, value) -> Bool in
        XCTAssertEqual(url.absoluteString, "https://apple.com/test2/a/b")
        XCTAssertEqual(value.arg1, "a")
        XCTAssertEqual(value.arg2, "b")
        expectDataLink.fulfill()
        return true
    }
}

try center.parse(url: URL(string: "https://apple.com/test/1")!)
try center.parse(url: URL(string: "https://apple.com/test/a/b")!)
try center.parse(url: URL(string: "https://apple.com/test2/a/b")!)

The library is fairly well-tested, but not very flexible (no regex based parsing, no scheme taken into account). I'm looking for ways to improve on it; all feedback is appreciated!

I'm also looking for some well-deserved bike shedding: the library is currently named Deeplink, and this obviously cannot continue, so please help me with a new name for it.

:pray:

6 Likes

Small update: I just finished to add two tutorials to Deeplink using the new DocC documentation compiler. You can open them by double clicking the “Deeplink.doccarchive” file in the root of the package on the main branch.

Give a look if you have Xcode 13! And enjoy WWDC :man_technologist:

4 Likes