Faire correspondre les liens profonds d'URI

Pour mettre en correspondance des URI hiérarchiques avec des modèles et extraire des arguments, utilisez UriDeepLinkMatcher. Il s'appuie sur kotlinx.serialization pour désérialiser les arguments correspondants dans vos classes de clés.

Pour créer un UriDeepLinkMatcher, fournissez un modèle DeepLinkUri et le sérialiseur pour la clé correspondante :

Pour les URI non hiérarchiques ou les schémas personnalisés (comme tel:), consultez Créer des outils de mise en correspondance de liens profonds personnalisés.

Formats de correspondance acceptés

UriDeepLinkMatcher établit une correspondance avec les URI en fonction de leurs cinq composants : schéma, autorité, chemin, requête et fragment. Les sections suivantes décrivent la syntaxe de format, les espaces réservés d'argument et les règles de correspondance compatibles pour chaque composant.

Correspondance de schéma

Si aucun schéma n'est présent dans le format d'URI, http et https sont mis en correspondance. Pour faire correspondre un schéma spécifique, incluez-le dans le modèle. Par exception, un schéma http dans un modèle correspond aux URI de requête http et https, tandis que https dans un modèle ne correspond qu'aux requêtes https.

URI du modèle URI de la demande Correspondance
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 ✅

Mise en correspondance des autorités

UriDeepLinkMatcher effectue une correspondance exacte non sensible à la casse sur l'autorité de l'URI (hôte et port facultatif). Les espaces réservés ou les caractères génériques ne sont pas acceptés dans l'autorité, et aucun argument n'est extrait :

URI du modèle URI de la demande Correspondance
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 ❌

Mise en correspondance des chemins d'accès

Les formats de chemin d'accès suivants sont acceptés :

URI du modèle URI de la demande Correspondance Arguments extraits
www.example.com/users https://www.example.com/users ✅ Aucun
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 : "" (chaîne vide)
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 ✅ Aucun
www.example.com/users https://www.example.com/users/ ❌ (la barre oblique finale crée un segment supplémentaire) N/A

Mise en correspondance des requêtes

L'ordre des paramètres de requête dans l'URI de la demande n'a pas besoin de correspondre à l'ordre dans l'URI du modèle. De plus, les paramètres présents dans l'URI de la demande, mais pas dans l'URI du modèle, sont ignorés.

Les modèles de paramètres de requête suivants sont acceptés :

URI du modèle URI de la demande Arguments extraits
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 : "" (chaîne vide)
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"

Correspondance de fragments

Les types de modèles de fragment suivants sont acceptés :

URI du modèle URI de la demande Arguments extraits
www.example.com/#section1 https://www.example.com/#section1 Aucun
www.example.com/#section_{id} https://www.example.com/#section_123 id : "123"
www.example.com/#section_.* https://www.example.com/#section_123 Aucun

Types de données acceptés

UriDeepLinkMatcher accepte la désérialisation des arguments d'URI en types primitifs, en énumérations, en collections et en objets personnalisés. La sérialisation se divise en deux catégories :

  • Sérialisation standard : utilise kotlinx.serialization pour désérialiser dans :
    • Primitives (Boolean, Int, Long, Float, Double, Char, Byte, Short) et String
    • Enums
    • Set, List ou Array de primitives, de chaînes ou d'énumérations
    • Classes @Serializable imbriquées (dont les propriétés sont aplaties en espaces réservés d'URI individuels)
  • Sérialisation personnalisée avec DeepLinkSerializer : convertit un seul String en objets personnalisés, en types externes (tels que java.time.LocalDate) ou en collections délimitées personnalisées.

Sérialisation standard

UriDeepLinkMatcher fonctionne de manière clé en main pour les types standards et les structures aplaties, sans nécessiter d'implémentations de sérialiseur personnalisées.

Primitives et chaînes

UriDeepLinkMatcher décode automatiquement les types primitifs (Boolean, Int, Long, Float, Double, Char, Byte, Short) et String :

Enums

Les valeurs d'énumération sont mises en correspondance avec les noms des éléments d'énumération en tenant compte de la casse :

Collections de requêtes répétées

Les paramètres de requête avec des clés répétées (tels que ?id=10&id=20) sont automatiquement désérialisés en List<T>, Set<T> ou Array<T>, où T est un type primitif, String ou une énumération :

Classes @Serializable imbriquées

Lorsqu'un NavKey contient une propriété dont le type est une autre classe @Serializable, UriDeepLinkMatcher aplatit ses propriétés de sorte que chaque propriété de la classe imbriquée soit directement mappée à un paramètre d'URI individuel du même nom :

Pour désérialiser des objets personnalisés (tels que Filter(key = "brand", value = "pixel")), des types externes (tels que java.time.LocalDate) ou des chaînes personnalisées délimitées (telles que des valeurs séparées par une virgule), étendez DeepLinkSerializer<T>.

DeepLinkSerializer<T> est un KSerializer<T> abstrait qui convertit un String en T :

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

Prenons par exemple les définitions Filter et FilterSerializer utilisées dans les extraits suivants :

Objets personnalisés uniques

Pour décoder un objet à partir d'une seule chaîne de paramètre d'URI (par exemple, ?filter=brand:google), annotez la propriété avec @Serializable(with = ...) :

Objets personnalisés dans les paramètres de requête répétés

Pour désérialiser des paramètres de requête répétés dans une collection d'objets personnalisés (List<T>, Set<T> ou Array<T>), implémentez DeepLinkSerializer<T> pour le type d'élément T et annotez l'argument de type de la propriété avec @Serializable(with = ...) :

Collections délimitées dans des paramètres uniques

Pour analyser des valeurs séparées par une virgule ou un délimiteur personnalisé (comme ?ids=1,2,3) dans une collection, implémentez DeepLinkSerializer pour le type de collection entier et annotez la propriété avec @Serializable(with = ...) :

Validation des arguments et résultats de la mise en correspondance

UriDeepLinkMatcher fait la distinction entre les non-correspondances (renvoie null pour que d'autres outils de mise en correspondance puissent être tentés) et les configurations non compatibles (génère une exception).

Incohérences

Une non-concordance se produit lorsqu'un URI de la demande entrant ne répond pas aux exigences de motif ou de type :

  • Paramètres obligatoires manquants : propriétés de clé non nullables sans valeurs par défaut dont les paramètres d'URI correspondants sont absents de l'URI de la demande.
  • Échecs d'analyse de type : valeurs d'argument extraites qui ne peuvent pas être analysées dans le type de propriété attendu (par exemple, "abc" pour une propriété Int).

En cas de non-concordance, UriDeepLinkMatcher.match renvoie null, ce qui permet d'évaluer les outils de mise en correspondance suivants.

Prenons l'exemple d'une classe de clé et d'un outil de mise en correspondance configurés avec des valeurs par défaut, des objets imbriqués et des énumérations :

Le tableau suivant présente les résultats de correspondance pour différents URI de requête :

URI de la demande Résultat du décodage Résultat du match
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE Réussite (tous les paramètres sont fournis) UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE)))
https://www.example.com/map/paris?style=dark Réussite (zoom est défini par défaut sur 12, layer sur STANDARD) UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD)))
https://www.example.com/map/paris?zoom=&style=dark Réussite (le paramètre de requête facultatif vide utilise la valeur par défaut 12) UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD)))
https://www.example.com/map?style=dark Incohérence (paramètre location obligatoire manquant) null
https://www.example.com/map/paris?zoom=close&style=dark Incohérence ("close" n'est pas un Int) null
https://www.example.com/map/paris?style=dark&layer=HYBRID Incohérence ("HYBRID" ne figure pas dans l'énumération) null

Configurations non compatibles

Si votre classe de clé contient des types de données non compatibles, UriDeepLinkMatcher génère une exception lors de la mise en correspondance au lieu de renvoyer null.

  • Maps et collections multidimensionnelles : UriDeepLinkMatcher n'accepte que les collections unidimensionnelles de primitives, de chaînes, d'énumérations ou de types personnalisés annotés avec un DeepLinkSerializer. Les types Map génèrent une exception IllegalArgumentException, tandis que les collections imbriquées (telles que List<List<String>>) génèrent une exception SerializationException.
  • Collections d'objets personnalisés non annotées : les collections de types personnalisés (tels que List<Filter>) génèrent une exception SerializationException, sauf si le type d'élément est annoté avec un DeepLinkSerializer.
  • Classes imbriquées non aplaties : les classes @Serializable imbriquées ne peuvent pas être mappées à un seul espace réservé (tel que ?user={user}) sans DeepLinkSerializer.

Comparaison de UriMatchResult

Les instances UriMatchResult sont classées selon les critères suivants, dans l'ordre :

  1. Type MatchResult : UriMatchResult est mieux classé que les autres types MatchResult.
  2. Chemin d'accès exact : les correspondances de chemin d'accès littéral sont mieux classées que les correspondances d'espace réservé ou de caractère générique.
  3. Nombre d'arguments de chemin d'accès : les correspondances avec plus d'arguments de chemin d'accès sont mieux classées.
  4. Présence d'arguments : les correspondances qui capturent des arguments sont mieux classées que celles qui n'en capturent pas.
  5. Nombre total d'arguments : le nombre total d'arguments (chemin d'accès, requête, fragment) est le critère de départage final.

Personnaliser UriDeepLinkMatcher

UriDeepLinkMatcher est une classe open que vous pouvez sous-classer pour personnaliser le comportement de correspondance d'URI et d'extraction d'arguments :

  • matchRequest : point d'entrée de correspondance de premier niveau pour un DeepLinkRequest entrant. Remplacez cette méthode pour inspecter les extras de la requête ou appliquer des conditions préalables personnalisées avant la mise en correspondance des URI.
  • matchUri : compare DeepLinkUri au modèle configuré. Remplacez cette méthode pour intercepter et normaliser les URI entrants (par exemple, en réécrivant les sous-domaines dynamiques ou les anciens formats de chemin d'accès) avant d'appeler super.matchUri.
  • matchArguments : désérialise les mappages d'arguments de chemin, de requête et de fragment extraits dans une instance de clé de navigation à l'aide de l'serializer fourni. Remplacez cette méthode pour injecter des valeurs dynamiques ou transformer des arguments avant l'instanciation de la clé.

L'exemple suivant montre comment créer une sous-classe de UriDeepLinkMatcher pour normaliser les anciens préfixes de chemin d'URL avant la mise en correspondance :