Соответствие URI ссылок на контент

Для сопоставления иерархических URI с шаблонами и извлечения аргументов используйте UriDeepLinkMatcher. Для десериализации сопоставленных аргументов в классы ключей используется kotlinx.serialization.

Чтобы создать UriDeepLinkMatcher, укажите шаблон DeepLinkUri и сериализатор для соответствующего ключа:

Если вы используете неиерархические URI или специальные схемы (например, tel:), прочитайте статью Как создавать специальные сопоставители ссылок на контент.

Поддерживаемые шаблоны сопоставления

UriDeepLinkMatcher сопоставляет URI на основе пяти компонентов: схемы, источника, пути, запроса и фрагмента. В разделах ниже описаны поддерживаемый синтаксис шаблонов, плейсхолдеры аргументов и правила сопоставления для каждого компонента.

Соответствие схемы

Если в шаблоне URI нет схемы, сопоставляются и http, и https. Чтобы сопоставить определенную схему, включите ее в шаблон. Исключение: схема http в шаблоне соответствует URI запросов http и https, а схема 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 authority (хост и необязательный порт). Плейсхолдеры и подстановочные знаки в 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 в примитивные типы, перечисления, коллекции и пользовательские объекты. Сериализация делится на две категории:

  • Стандартная сериализация. Использует 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> – это абстрактный класс KSerializer<T>, который преобразует String в 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>), реализуйте DeepLinkSerializer<T> для типа элемента T и добавьте аннотацию @Serializable(with = ...) к аргументу типа свойства:

Наборы значений, разделенные запятыми, в отдельных параметрах

Чтобы преобразовать значения, разделенные запятыми или другими символами (например, ?ids=1,2,3), в коллекцию, реализуйте DeepLinkSerializer для всего типа коллекции и добавьте к свойству аннотацию @Serializable(with = ...):

Проверка аргументов и результаты сопоставления

UriDeepLinkMatcher различает несовпадения (возвращает null, чтобы можно было попробовать другие сопоставители) и неподдерживаемые конфигурации (выдает исключение).

Несоответствия

Несоответствие возникает, когда URI входящего запроса не соответствует требованиям к шаблону или типу:

  • Отсутствуют обязательные параметры. Ключевые свойства, которые не могут иметь значение NULL и не имеют значений по умолчанию, а соответствующие им параметры URI отсутствуют в URI запроса.
  • Ошибки синтаксического анализа типов. Извлеченные значения аргументов, которые не могут быть преобразованы в ожидаемый тип свойства (например, "abc" для свойства Int).

Если совпадение не найдено, 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 Success (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. Map types throw an IllegalArgumentException, while nested collections (such as List<List<String>>) throw a SerializationException.
  • Неаннотированные коллекции пользовательских объектов. Коллекции пользовательских типов (например, List<Filter>) вызывают ошибку SerializationException, если тип элемента не аннотирован с помощью DeepLinkSerializer.
  • Неразвернутые вложенные классы. Вложенные классы @Serializable нельзя сопоставить с одним плейсхолдером (например, ?user={user}) без DeepLinkSerializer.

UriMatchResult: сравнение

Экземпляры UriMatchResult ранжируются по следующим критериям:

  1. Тип MatchResult. UriMatchResult имеет более высокий приоритет, чем другие типы MatchResult.
  2. Точный путь. Точные совпадения путей имеют более высокий приоритет, чем совпадения с плейсхолдерами или подстановочными знаками.
  3. Количество аргументов пути. Чем больше аргументов пути, тем выше рейтинг.
  4. Наличие аргументов. Совпадения, содержащие аргументы, имеют более высокий приоритет, чем совпадения без аргументов.
  5. Общее количество аргументов. Общее количество аргументов (путь, запрос, фрагмент) – это последний критерий, который используется для разрешения конфликтов.

UriDeepLinkMatcher: настроить

UriDeepLinkMatcher – это класс open, подкласс которого можно создать, чтобы настроить сопоставление URI и извлечение аргументов.

  • matchRequest – точка входа верхнего уровня для входящего объекта DeepLinkRequest. Переопределите этот метод, чтобы проверять дополнительные параметры запроса или применять пользовательские предварительные условия перед сопоставлением URI.
  • matchUri – сопоставляет DeepLinkUri с настроенным шаблоном. Переопределите этот метод, чтобы перехватывать и нормализовать входящие URI (например, переписывать динамические субдомены или устаревшие форматы путей) перед вызовом super.matchUri.
  • matchArguments десериализует извлеченные карты аргументов пути, запроса и фрагмента в экземпляр ключа навигации с помощью предоставленного serializer. Переопределите этот метод, чтобы вставить динамические значения или преобразовать аргументы перед созданием экземпляра ключа.

В примере ниже показано, как создать подкласс UriDeepLinkMatcher, чтобы нормализовать префиксы путей URL устаревшего формата перед сопоставлением.