Contents

shapekim98/schemeroute

SchemeRoute

요구 사항

Requirements

  • Swift 5.10 이상 (매크로 기능 활용)<br>

Swift 5.10 or later (requires macro support)

  • iOS 13 / macOS 10.15 / tvOS 13 / watchOS 6 / macCatalyst 13 이상 타깃<br>

Targets iOS 13 / macOS 10.15 / tvOS 13 / watchOS 6 / macCatalyst 13 or newer

설치 (Swift Package Manager)

Installation (Swift Package Manager)

Package.swiftdependencies 배열에 SchemeRoute 를 최신 버전으로 추가합니다 (현재 0.2.0).<br> Add SchemeRoute to the dependencies array in Package.swift with the latest version (currently 0.2.0).

.package(url: "https://github.com/ShapeKim98/SchemeRoute.git", from: "0.2.0")

사용할 타깃의 dependenciesSchemeRoute 를 명시합니다.<br> Declare SchemeRoute in the target's dependencies where it will be used.

.target(
    name: "YourApp",
    dependencies: [
        .product(name: "SchemeRoute", package: "SchemeRoute")
    ]
)

Xcode에서는 File > Add Packages... 메뉴에서 같은 URL을 입력하면 됩니다.<br> In Xcode, use File > Add Packages... and enter the same URL.

빠른 시작

Quick Start

import SchemeRoute

@SchemeRoutable
enum AppRoute: Equatable {
    static var scheme: String { "myapp" }
    static var host: String { "app" }

    @SchemePattern("")
    case home

    @SchemePattern("user/${id}/profile")
    case userProfile(id: String)

    @SchemePattern("article/${slug}?ref=${ref}")
    case article(slug: String, ref: String)
}

// 문자열 → 라우트 (호스트는 기본값으로 분리)
let route = AppRoute(rawValue: "user/42/profile")
// URL → 라우트
let fromURL = AppRoute(url: URL(string: "myapp://app/article/swift?ref=newsletter"))
// 라우트 → URL
let url = AppRoute.article(slug: "swift", ref: "newsletter").url()

스킴/호스트 없이 선언하기

기본 스킴/호스트를 지정하지 않으면 패턴 문자열 안에 호스트(필요하다면 스킴까지)를 직접 포함할 수 있습니다. 기존 iOS/웹 URL을 그대로 다뤄야 할 때 유용합니다.

@SchemeRoutable
enum InlineRoute: Equatable {
    @SchemePattern("kakaolink?categoryId=${categoryId}")
    case kakaolink(categoryId: String)

    @SchemePattern("inline.app/user/${id}/profile")
    case inlineProfile(id: String)
}

let inline = InlineRoute(rawValue: "inline.app/user/42/profile")
let kakao = InlineRoute(url: URL(string: "kakaoapp://kakaolink?categoryId=424"))
let deepURL = InlineRoute.inlineProfile(id: "42").url(scheme: "myapp")

패턴에 포함된 호스트는 그대로 유지되며, url(scheme:) 을 통해 런타임에서 필요한 스킴을 주입할 수 있습니다.

@SchemeRoutable 매크로는 enum 내 모든 케이스를 스캔하여 SchemeMapper<AppRoute> 를 생성합니다.<br> The @SchemeRoutable macro scans every case in the enum and generates a SchemeMapper<AppRoute>. init?(url:) 은 옵셔널 URL을 그대로 받아 nil 이면 초기화에 실패합니다.<br> init?(url:) accepts an optional URL and returns nil when the argument is nil. SchemeRoute 프로토콜의 기본 구현(rawValue, init?(rawValue:), init?(url:))도 자동으로 동작합니다.<br> The default SchemeRoute implementations (rawValue, init?(rawValue:), init?(url:)) then work automatically.

패턴 작성 규칙

Pattern Rules

  • 패턴 문자열은 기본적으로 path?query 형태입니다. SchemeRoute.hostSchemeRoute.scheme 가 비어 있으면 직접 호스트(및 스킴)을 포함시킬 수 있습니다.<br>

Pattern strings are path?query by default. When SchemeRoute.host or SchemeRoute.scheme are empty, you may inline the host (and scheme) manually.

  • 경로와 쿼리에서 값이 되는 부분은 ${name} 플레이스홀더로 표기합니다.<br>

Use ${name} placeholders wherever the path or query should inject values. - 경로 예: user/${id}/profile<br> Path example: user/${id}/profile - 쿼리 예: pay/complete?order_id=${orderId}<br> Query example: pay/complete?order_id=${orderId}

  • 플레이스홀더 이름은 case 의 연관값 라벨과 1:1 로 매칭되어야 하며, 모든 연관값은 LosslessStringConvertible 프로토콜을 따르는 타입이어야 합니다 (예: String, Int, Double, Bool 등).<br>

Placeholder names must match associated value labels 1:1, and every associated value must conform to LosslessStringConvertible protocol (e.g., String, Int, Double, Bool, etc.).

  • 같은 연관값을 두 번 이상 사용할 수 없고 사용하지 않은 연관값이 있으면 오류가 발생합니다.<br>

The same associated value cannot be used more than once, and unused associated values trigger an error.

  • 외부 라벨이 붙은 연관값(case article(slug slug: String))은 지원하지 않습니다.<br>

Associated values with external labels (e.g. case article(slug slug: String)) are not supported.

  • SchemeRoute.scheme/host 가 지정되어 있다면 url() 호출 시 기본값으로 사용됩니다. 필요하면 url(scheme:host:) 에서 값을 덮어쓸 수 있습니다.<br>

When SchemeRoute.scheme/host are set, url() uses them automatically; override them by passing arguments to url(scheme:host:) when needed.

타입 자동 변환

Automatic Type Conversion

LosslessStringConvertible 프로토콜을 따르는 모든 타입을 연관값으로 사용할 수 있습니다. URL 문자열과 타입 간 자동 변환이 이루어집니다.<br> Any type conforming to LosslessStringConvertible can be used as associated values. Automatic conversion between URL strings and types is performed.

@SchemeRoutable
enum AppRoute: Equatable {
    static var scheme: String { "myapp" }
    static var host: String { "app" }

    // Int, Bool 등의 타입 자동 변환
    @SchemePattern("user/${id}/posts?page=${page}&premium=${premium}")
    case userPosts(id: Int, page: Int, premium: Bool)

    // 옵셔널 타입도 지원
    @SchemePattern("product/${productId}?discount=${discount}&quantity=${quantity}")
    case product(productId: String, discount: Double?, quantity: Int?)
}

// URL → 라우트 변환
AppRoute(url: URL(string: "myapp://app/user/123/posts?page=2&premium=true"))
// => userPosts(id: 123, page: 2, premium: true)

AppRoute(url: URL(string: "myapp://app/product/ABC?discount=0.25&quantity=5"))
// => product(productId: "ABC", discount: 0.25, quantity: 5)

AppRoute(url: URL(string: "myapp://app/product/ABC?discount=0.25"))
// => product(productId: "ABC", discount: 0.25, quantity: nil)

// 라우트 → URL 변환
AppRoute.userPosts(id: 999, page: 5, premium: true).url()
// => myapp://app/user/999/posts?page=5&premium=true

AppRoute.product(productId: "TEST", discount: nil, quantity: nil).url()
// => myapp://app/product/TEST

지원 타입:<br> Supported Types:

  • String, Int, Double, Bool, UInt 등 기본 타입<br>

Built-in types like String, Int, Double, Bool, UInt, etc.

  • 위 타입들의 옵셔널 버전 (Int?, Double? 등)<br>

Optional versions of the above types (Int?, Double?, etc.)

  • LosslessStringConvertible 을 따르는 커스텀 타입<br>

Custom types conforming to LosslessStringConvertible

동작 방식:<br> Behavior:

  • 변환 실패 시 (예: "abc"Int) 라우트 매칭이 실패합니다.<br>

Conversion failures (e.g., "abc"Int) result in route matching failure.

  • 옵셔널 타입: 파라미터가 없거나 빈 값이면 nil 로 처리됩니다.<br>

Optional types: Missing or empty parameters are treated as nil.

  • 비옵셔널 타입: 파라미터가 없거나 빈 값이면 매칭이 실패합니다 (String 제외).<br>

Non-optional types: Missing or empty parameters cause matching to fail (except String).

  • URL 생성 시 nil 값을 가진 파라미터는 URL에서 제외됩니다.<br>

When generating URLs, parameters with nil values are excluded from the URL.

수동 라우터 구성

Manual Router Configuration

매크로 대신 직접 매퍼를 구성하려면 SchemeMapperBuilder 를 사용할 수 있습니다.<br> If you prefer manual control, build the mapper by hand with SchemeMapper's Builder.

let router = SchemeMapper<AppRoute> { builder in
    builder.register("user/${id}/profile", queryKeys: []) { params in
        guard let id = params["id"] else { return nil }
        return .userProfile(id: id)
    } render: { route in
        guard case let .userProfile(id) = route else { return nil }
        return ["id": id]
    }
}

대부분의 경우 매크로를 사용하는 편이 선언적이며 오류를 줄일 수 있습니다.<br> In most cases, macros remain more declarative and help prevent mistakes.

예제 실행

Example Run

리포지토리에는 간단한 실행 예제가 포함되어 있습니다.<br> A simple runnable example ships with the repository.

swift run SchemeRouteClient

출력 로그를 통해 문자열/URL 매칭과 URL 생성을 확인할 수 있습니다.<br> Check the output log to see string/URL matching and URL generation in action.

라이선스

License

이 프로젝트는 MIT License 하에 배포됩니다.<br> This project is distributed under the MIT License.

Package Metadata

Repository: shapekim98/schemeroute

Default branch: main

README: README.md