Faire correspondre les liens profonds d'URI

Pour faire correspondre des URI hiérarchiques à 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 de la clé correspondante :

Pour les URI non hiérarchiques ou les schémas personnalisés (tels que tel:), consultez Créer des comparateurs de liens profonds personnalisés.

Modèles de correspondance acceptés

UriDeepLinkMatcher fait correspondre les URI en fonction de leurs cinq composants : schéma, autorité, chemin, requête et fragment. Les sections suivantes décrivent la syntaxe des modèles acceptés, les espaces réservés pour les arguments et les règles de correspondance pour chaque composant.

Correspondance de schéma

Si aucun schéma n'est présent dans le modèle d'URI, http et https sont mis en correspondance. Pour faire correspondre un schéma spécifique, incluez-le dans le modèle. Exceptionnellement, un http schéma 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 requête 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

Correspondance d'autorité

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 requête 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

Correspondance de chemin

Les modèles de chemin suivants sont acceptés :

URI du modèle URI de la requête 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

Correspondance de requête

L'ordre des paramètres de requête dans l'URI de la demande ne doit pas nécessairement 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 requête 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 fragment

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

URI du modèle URI de la requête 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 permet de désérialiser les arguments d'URI en types primitifs, enums, collections et objets personnalisés. La sérialisation se divise en deux catégories :

  • Sérialisation standard : utilise kotlinx.serialization pour désérialiser en :
    • Primitifs (Boolean, Int, Long, Float, Double, Char, Byte, Short) et String
    • Enums
    • Set, List ou Array de primitifs, de chaînes ou d'enums
    • 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 : conversion entre un seul String et des objets personnalisés, des types externes (tels que java.time.LocalDate) ou des collections délimitées personnalisées.

Sérialisation standard

UriDeepLinkMatcher fonctionne immédiatement pour les types standards et les structures aplaties sans nécessiter d'implémentations de sérialiseur personnalisées.

Primitifs et chaînes

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

Enums

Les valeurs Enum sont mises en correspondance de manière sensible à la casse avec les noms des éléments Enum :

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 enum :

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 afin 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 délimitées personnalisées (telles que des valeurs séparées par des virgules), étendez DeepLinkSerializer<T>.

DeepLinkSerializer<T> est un KSerializer<T> abstrait qui effectue une conversion entre un String et un T :

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

Par exemple, considérez 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ètres d'URI (telle que ?filter=brand:google), annotez la propriété avec @Serializable(with = ...) :

Objets personnalisés dans des 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 des virgules ou délimitées personnalisées (telles que ?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 correspondance

UriDeepLinkMatcher fait la distinction entre les incohérences (renvoie null afin que d'autres comparateurs puissent être tentés) et les configurations non compatibles (génère une exception).

Incohérences

Une incohérence se produit lorsqu'un URI de la demande entrant ne répond pas aux exigences de modèle 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'arguments 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 d'incohérence, UriDeepLinkMatcher.match renvoie null, ce qui permet d'évaluer les comparateurs suivants.

Considérez une classe de clé et un comparateur configurés avec des valeurs par défaut, des objets imbriqués et des enums :

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

URI de la requête Résultat du décodage Résultat de la correspondance
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE Opération réussie (tous les paramètres fournis) UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE)))
https://www.example.com/map/paris?style=dark Opération réussie (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 Opération réussie (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" n'est pas dans l'enum) 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 correspondance au lieu de renvoyer null.

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

Comparaison 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 MatchResult types.
  2. Chemin exact : les correspondances de chemin littéral sont mieux classées que les correspondances d'espaces réservés ou de caractères génériques.
  3. Nombre d'arguments de chemin : les correspondances avec plus d'arguments de chemin 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, requête, fragment) est le dernier critère de départage.

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-le pour inspecter les extras de la requête ou appliquer des conditions préalables personnalisées avant la correspondance d'URI.
  • matchUri : fait correspondre le DeepLinkUri au modèle configuré. Remplacez-le pour intercepter et normaliser les URI entrants (par exemple, en réécrivant les sous-domaines dynamiques ou les formats de chemin hérité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 du serializer fourni. Remplacez-le pour injecter des valeurs dynamiques ou transformer des arguments avant l'instanciation de la clé.

L'exemple suivant montre comment sous-classer UriDeepLinkMatcher pour normaliser les préfixes de chemin d'URL hérités avant la correspondance :