URI ディープリンクを照合する

階層 URI をパターンと照合して引数を抽出するには、UriDeepLinkMatcher を使用します。kotlinx.serialization を使用して、一致した引数をキークラスにデシリアライズします。

UriDeepLinkMatcher を作成するには、パターン DeepLinkUri と、対応するキーのシリアライザを指定します。

階層型でない URI やカスタム スキーム(tel: など)については、カスタム ディープリンク マッチャーを作成するをご覧ください。

サポートされているマッチング パターン

UriDeepLinkMatcher は、スキーム、オーソリティ、パス、クエリ、フラグメントの 5 つのコンポーネントに基づいて URI を照合します。以降のセクションでは、サポートされているパターン構文、引数プレースホルダ、各コンポーネントの一致ルールについて説明します。

スキームの一致

URI パターンにスキームが存在しない場合、http と https の両方が一致します。特定のスキームを照合するには、パターンに含めます。例外として、パターンの http スキームは http リクエスト URI と https リクエスト URI の両方に一致しますが、パターンの https は https リクエストにのみ一致します。

パターン URI リクエスト URI 一致
www.example.com https://www.example.com ✅
www.example.com http://www.example.com ✅
http://www.example.com http://www.example.com ✅
http://www.example.com https://www.example.com ✅
https://www.example.com http://www.example.com ❌
myapp://www.example.com myapp://www.example.com ✅

オーソリティ マッチング

UriDeepLinkMatcher は、URI オーソリティ(ホストとオプションのポート)で大文字と小文字を区別しない完全一致を実行します。オーソリティではプレースホルダやワイルドカードはサポートされておらず、引数は抽出されません。

パターン URI リクエスト URI 一致
example.com https://example.com ✅
example.com https://EXAMPLE.COM ✅
example.com https://sub.example.com ❌
example.com https://www.example.com ❌
example.com https://example.com:8080 ❌
example.com:8080 https://example.com:8080 ✅
example.com:8080 https://example.com ❌

パスマッチング

次のパスパターンがサポートされています。

パターン URI リクエスト URI 一致 抽出された引数
www.example.com/users https://www.example.com/users ✅ なし
www.example.com/users/{id} https://www.example.com/users/123 ✅ id: "123"
www.example.com/users/{first}-{last} https://www.example.com/users/john-doe ✅ first: "john"、last: "doe"
www.example.com/users/{id}/profile https://www.example.com/users//profile ✅ id: ""(空の文字列)
www.example.com/users/user_{id} https://www.example.com/users/user_123 ✅ id: "123"
www.example.com/users/{userId}/posts/{postId} https://www.example.com/users/123/posts/456 ✅ userId: "123"、postId: "456"
www.example.com/users/.* https://www.example.com/users/john-doe ✅ なし
www.example.com/users https://www.example.com/users/ ❌(末尾のスラッシュにより、余分なセグメントが作成される) なし

検索語句の照合プロセス

リクエスト URI のクエリ パラメータの順序は、パターン URI の順序と一致する必要はありません。また、リクエスト URI には存在するがパターン URI には存在しないパラメータは無視されます。

次のクエリ パラメータ パターンがサポートされています。

パターン URI リクエスト URI 抽出された引数
www.example.com/users?name={name} https://www.example.com/users?name=john name: "john"
www.example.com/users?name={name} https://www.example.com/users?name= name: ""(空の文字列)
www.example.com/users?{rawQuery} https://www.example.com/users?anything&else rawQuery: ["anything", "else"]
www.example.com/users?type=user_{id} https://www.example.com/users?type=user_123 id: "123"
www.example.com/users?name={first}_{last} https://www.example.com/users?name=john_doe first: "john"、last: "doe"
www.example.com/users?list={list} https://www.example.com/users?list=10&list=20 list: ["10", "20"]
www.example.com/users?name={name}&{other} https://www.example.com/users?name=john&tab=info name: "john"、other: ["tab=info"]
www.example.com/users?type=user_.* https://www.example.com/users?type=user_admin type: "admin"

フラグメント マッチング

次のフラグメント パターン タイプがサポートされています。

パターン URI リクエスト URI 抽出された引数
www.example.com/#section1 https://www.example.com/#section1 なし
www.example.com/#section_{id} https://www.example.com/#section_123 id: "123"
www.example.com/#section_.* https://www.example.com/#section_123 なし

サポートされるデータタイプ

UriDeepLinkMatcher は、URI 引数のプリミティブ型、列挙型、コレクション、カスタム オブジェクトへの逆シリアル化をサポートしています。シリアル化は次の 2 つのカテゴリに分類されます。

  • 標準のシリアル化: kotlinx.serialization を使用して、次の形式にデシリアル化します。
    • プリミティブ(Boolean、Int、Long、Float、Double、Char、Byte、Short)と String
    • 列挙型
    • Set、List、Array(プリミティブ、文字列、列挙型)
    • ネストされた @Serializable クラス(プロパティは個々の URI プレースホルダにフラット化されます)
  • DeepLinkSerializer を使用したカスタム シリアル化: 単一の String とカスタム オブジェクト、外部タイプ(java.time.LocalDate など)、カスタム区切りのコレクションの間で変換します。

標準のシリアル化

UriDeepLinkMatcher は、カスタム シリアライザの実装を必要とせずに、標準タイプとフラット化された構造ですぐに使用できます。

プリミティブと文字列

UriDeepLinkMatcher は、プリミティブ型(Boolean、Int、Long、Float、Double、Char、Byte、Short)と String を自動的にデコードします。

列挙型

列挙値は、列挙要素名と大文字と小文字を区別して照合されます。

繰り返されるクエリのコレクション

キーが繰り返されるクエリ パラメータ(?id=10&id=20 など)は、自動的に List<T>、Set<T>、Array<T> にデシリアライズされます。ここで、T はプリミティブ型、String、または列挙型です。

ネストされた @Serializable クラス

NavKey に、別の @Serializable クラスを型とするプロパティが含まれている場合、UriDeepLinkMatcher はそのプロパティをフラット化して、ネストされたクラスの各プロパティが同じ名前の個々の URI パラメータに直接マッピングされるようにします。

カスタム オブジェクト(Filter(key = "brand", value = "pixel") など)、外部タイプ(java.time.LocalDate など)、カスタム区切り文字列(カンマ区切り値など)をデシリアライズするには、DeepLinkSerializer<T> を拡張します。

DeepLinkSerializer<T> は、String と T の間で変換を行う抽象 KSerializer<T> です。

abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
    abstract val serialName: String
    abstract fun deserialize(value: String): T
    abstract fun serialize(value: T): String
}

たとえば、次のスニペットで使用されている Filter と FilterSerializer の定義について考えてみましょう。

単一のカスタム オブジェクト

単一の URI パラメータ文字列(?filter=brand:google など)からオブジェクトをデコードするには、プロパティに @Serializable(with = ...) アノテーションを付けます。

繰り返しクエリ パラメータのカスタム オブジェクト

繰り返されるクエリ パラメータをカスタム オブジェクト(List<T>、Set<T>、Array<T>)のコレクションにデシリアライズするには、要素型 T に DeepLinkSerializer<T> を実装し、プロパティの型引数に @Serializable(with = ...) アノテーションを付けます。

単一パラメータ内の区切り文字で区切られたコレクション

カンマ区切りまたはカスタム区切りの値(?ids=1,2,3 など)を解析してコレクションに変換するには、コレクション タイプ全体に DeepLinkSerializer を実装し、プロパティに @Serializable(with = ...) アノテーションを付けます。

引数の検証とマッチングの結果

UriDeepLinkMatcher は、不一致(null を返すため、他のマッチャーを試すことができます)と、サポートされていない構成(例外をスローします)を区別します。

不一致

不一致は、受信リクエスト URI がパターンまたはタイプの要件を満たしていない場合に発生します。

  • 必須パラメータがない: デフォルト値のない null 非許容のキー プロパティで、対応する URI パラメータがリクエスト URI に存在しない。
  • 型解析エラー: 抽出された引数値が、想定されるプロパティ型に解析できない(たとえば、Int プロパティの "abc")。

不一致が発生すると、UriDeepLinkMatcher.match は null を返し、後続のマッチャーを評価できるようにします。

デフォルト値、ネストされたオブジェクト、列挙型で構成されたキークラスとマッチャーについて考えてみましょう。

次の表に、さまざまなリクエスト URI の一致結果を示します。

リクエスト URI デコード結果 試合結果
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE 成功(すべてのパラメータが指定されている) UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE)))
https://www.example.com/map/paris?style=dark 成功(zoom のデフォルトは 12、layer のデフォルトは STANDARD) UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD)))
https://www.example.com/map/paris?zoom=&style=dark 成功(空のオプションのクエリ パラメータはデフォルトの 12 を使用します) UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD)))
https://www.example.com/map?style=dark 不一致(必須の location パラメータがない) null
https://www.example.com/map/paris?zoom=close&style=dark 不一致("close" は Int ではない) null
https://www.example.com/map/paris?style=dark&layer=HYBRID 不一致("HYBRID" が列挙型にない) null

サポートされていない設定

キークラスにサポートされていないデータ型が含まれている場合、UriDeepLinkMatcher は null を返す代わりに、マッチング中に例外をスローします。

  • マップと多次元コレクション: UriDeepLinkMatcher は、プリミティブ、文字列、列挙型、または DeepLinkSerializer でアノテーションが付けられたカスタムタイプの 1 次元コレクションのみをサポートします。Map 型は IllegalArgumentException をスローし、ネストされたコレクション(List<List<String>> など)は SerializationException をスローします。
  • アノテーションのないカスタム オブジェクト コレクション: カスタムタイプ(List<Filter> など)のコレクションは、要素タイプに DeepLinkSerializer のアノテーションが付けられていない限り、SerializationException をスローします。
  • フラット化されていないネストされたクラス: ネストされた @Serializable クラスは、DeepLinkSerializer なしで単一のプレースホルダ(?user={user} など)にマッピングできません。

UriMatchResultの比較

UriMatchResult インスタンスは、次の基準を使用してランク付けされます。

  1. MatchResult タイプ: UriMatchResult は他の MatchResult タイプよりも上位にランクされます。
  2. 正確なパス: リテラルパスの一致は、プレースホルダまたはワイルドカードの一致よりも上位にランク付けされます。
  3. パス引数の数: パス引数が多いほど、一致のランクが高くなります。
  4. 引数の有無: 引数をキャプチャする一致は、キャプチャしない一致よりも上位にランク付けされます。
  5. 引数の総数: 引数(パス、クエリ、フラグメント)の総数が最終的なタイブレーカーになります。

UriDeepLinkMatcher をカスタマイズ

UriDeepLinkMatcher は open クラスです。このクラスをサブクラス化して、URI の照合と引数の抽出動作をカスタマイズできます。

  • matchRequest: 受信 DeepLinkRequest の最上位の一致するエントリ ポイント。これをオーバーライドして、URI の照合前にリクエストの追加情報を検査したり、カスタムの前提条件を適用したりします。
  • matchUri: DeepLinkUri を構成済みのパターンと照合します。super.matchUri を呼び出す前に、受信 URI をインターセプトして正規化する(動的サブドメインや以前のパス形式を書き換えるなど)には、これをオーバーライドします。
  • matchArguments: 指定された serializer を使用して、抽出されたパス、クエリ、フラグメント引数マップをナビゲーション キー インスタンスにデシリアライズします。これをオーバーライドして、動的な値を挿入するか、キーのインスタンス化の前に引数を変換します。

次の例は、UriDeepLinkMatcher をサブクラス化して、照合前に以前の URL パス接頭辞を正規化する方法を示しています。