لمطابقة معرّفات الموارد المنتظمة (URI) الهرمية مع الأنماط واستخراج الوسيطات، استخدِم
UriDeepLinkMatcher. وتعتمد على kotlinx.serialization لإلغاء تسلسل
الوسيطات المطابقة في فئات المفاتيح.
لإنشاء UriDeepLinkMatcher، قدِّم النمط DeepLinkUri وأداة التسلسل للمفتاح المقابل:
@Serializable data class UserProfileKey(val id: String) : NavKey val userProfilePattern = DeepLinkUri("www.example.com/users/{id}") val userProfileMatcher = UriDeepLinkMatcher(userProfilePattern, serializer<UserProfileKey>()) val request = DeepLinkRequest(uri = "https://www.example.com/users/123") val matchResult = userProfileMatcher.match(request) val key = matchResult?.key // UserProfileKey(id = "123")
بالنسبة إلى معرّفات الموارد المنتظمة (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) (المضيف والمنفذ الاختياري). لا يمكن استخدام العناصر النائبة أو أحرف البدل
في المرجع، ولا يتم استخراج أي وسيطات:
| معرّف الموارد المنتظم (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مدمجة (يتم دمج خصائصها في عناصر نائبة فردية لمعرّفات الموارد المنتظمة)
- الأساسيات (
- التسلسل المخصّص باستخدام
DeepLinkSerializer: يحوّل بينStringواحد وعناصر مخصّصة أو أنواع خارجية (مثلjava.time.LocalDate) أو مجموعات محدّدة بشكل مخصّص.
نشر الحلقات على نحو متسلسِل عادي
تعمل UriDeepLinkMatcher تلقائيًا مع الأنواع العادية والبُنى المبسّطة بدون الحاجة إلى تنفيذ عمليات تسلسل مخصّصة.
الأنواع الأساسية والسلاسل
يفكّ UriDeepLinkMatcher ترميز الأنواع الأساسية تلقائيًا (Boolean وInt وLong وFloat وDouble وChar وByte وShort) وString:
@Serializable data class UserProfileKey(val id: Int) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/users/{id}"), serializer<UserProfileKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/users/123") val key = matcher.match(request)?.key // UserProfileKey(id = 123)
تعدادات
تتم مطابقة قيم التعداد مع أسماء عناصر التعداد مع مراعاة حالة الأحرف:
enum class SortOrder { RELEVANCE, DATE, POPULARITY } @Serializable data class ProductsKey(val sort: SortOrder) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/products?sort={sort}"), serializer<ProductsKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/products?sort=DATE") val key = matcher.match(request)?.key // ProductsKey(sort = SortOrder.DATE)
مجموعات طلبات البحث المتكررة
يتم تلقائيًا إلغاء تسلسل معلمات طلب البحث التي تتضمّن مفاتيح متكررة (مثل ?id=10&id=20) إلى List<T> أو Set<T> أو Array<T>، حيث يكون T نوعًا أساسيًا أو String أو تعدادًا:
@Serializable data class FilteredItemsKey(val ids: List<Int>) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/items?id={ids}"), serializer<FilteredItemsKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/items?id=10&id=20") val key = matcher.match(request)?.key // FilteredItemsKey(ids = listOf(10, 20))
فئات @Serializable مدمجة
عندما يحتوي NavKey على سمة من نوع فئة @Serializable أخرى،
يُبسّط UriDeepLinkMatcher سماته بحيث يتم ربط كل سمة من الفئة المضمّنة
مباشرةً بمعلمة URI فردية تحمل الاسم نفسه:
enum class SortOrder { RELEVANCE, DATE, POPULARITY } @Serializable data class SearchFilters( val category: String, val sortBy: SortOrder = SortOrder.RELEVANCE ) @Serializable data class SearchKey( val query: String, val page: Int = 1, // Flattened into {category} and {sortBy} val filters: SearchFilters ) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/search?q={query}&page={page}&category={category}&sortBy={sortBy}"), serializer<SearchKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/search?q=kotlin&category=books&sortBy=DATE") val key = matcher.match(request)?.key // SearchKey(query = "kotlin", page = 1, filters = SearchFilters(category = "books", sortBy = SortOrder.DATE))
التسلسل المخصّص باستخدام DeepLinkSerializer
لإلغاء تسلسل العناصر المخصّصة (مثل 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 المستخدَمين في المقتطفات التالية:
@Serializable data class Filter(val key: String, val value: String) object FilterSerializer : DeepLinkSerializer<Filter>() { override val serialName: String = "com.example.Filter" override fun deserialize(value: String): Filter { val parts = value.split(":", limit = 2) if (parts.size < 2) { throw SerializationException("Invalid filter: $value. Expected key:value.") } return Filter(key = parts[0], value = parts[1]) } override fun serialize(value: Filter): String = "${value.key}:${value.value}" }
عناصر مخصّصة فردية
لفك ترميز كائن من سلسلة مَعلمات معرّف موارد منتظم (URI) واحدة (مثل
?filter=brand:google)، يمكنك إضافة تعليق توضيحي إلى السمة باستخدام @Serializable(with = ...):
@Serializable data class CatalogKey( @Serializable(with = FilterSerializer::class) val filter: Filter ) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/catalog?filter={filter}"), serializer<CatalogKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/catalog?filter=brand:google") val key = matcher.match(request)?.key // CatalogKey(filter = Filter("brand", "google"))
العناصر المخصّصة في مَعلمات طلب البحث المتكرّرة
لإلغاء تسلسل معلمات طلب البحث المتكررة في مجموعة من العناصر المخصّصة
(List<T> أو Set<T> أو Array<T>)، يمكنك تنفيذ DeepLinkSerializer<T> من أجل
نوع العنصر T وإضافة تعليقات توضيحية إلى وسيطة النوع للسمة باستخدام
@Serializable(with = ...):
@Serializable data class SearchResultsKey( val query: String, val filters: List<@Serializable(with = FilterSerializer::class) Filter> = emptyList() ) : NavKey val searchResultsPattern = DeepLinkUri("www.example.com/search?q={query}&filter={filters}") val searchResultsMatcher = UriDeepLinkMatcher(searchResultsPattern, serializer<SearchResultsKey>()) val request = DeepLinkRequest(uri = "https://www.example.com/search?q=phone&filter=brand:google&filter=color:hazel") val matchResult = searchResultsMatcher.match(request) val key = matchResult?.key // SearchResultsKey(query = "phone", filters = listOf(Filter("brand", "google"), Filter("color", "hazel")))
المجموعات المحدّدة في مَعلمات فردية
لتحليل القيم المفصولة بفواصل أو المحدّدة بشكل مخصّص (مثل ?ids=1,2,3) إلى
مجموعة، نفِّذ DeepLinkSerializer لنوع المجموعة بالكامل
وأضِف تعليقات توضيحية إلى السمة باستخدام @Serializable(with = ...):
object IntListCsvSerializer : DeepLinkSerializer<List<Int>>() { override val serialName: String = "com.example.IntListCsv" override fun deserialize(value: String): List<Int> { if (value.isEmpty()) return emptyList() return value.split(",").map { it.trim().toInt() } } override fun serialize(value: List<Int>): String = value.joinToString(",") } @Serializable data class ItemListKey( @Serializable(with = IntListCsvSerializer::class) val ids: List<Int> ) : NavKey val itemListPattern = DeepLinkUri("www.example.com/items/{ids}") val itemListMatcher = UriDeepLinkMatcher(itemListPattern, serializer<ItemListKey>()) val request = DeepLinkRequest(uri = "https://www.example.com/items/10,20,30") val key = itemListMatcher.match(request)?.key // ItemListKey(ids = listOf(10, 20, 30))
التحقّق من الوسيطات ونتائج المطابقة
يميّز UriDeepLinkMatcher بين حالات عدم التطابق (يعرض null حتى يمكن تجربة أدوات مطابقة أخرى) والإعدادات غير المتوافقة (يُظهر استثناءً).
حالات عدم التطابق
يحدث عدم تطابق عندما لا يستوفي عنوان URI للطلب الوارد متطلبات النمط أو النوع:
- المَعلمات المطلوبة غير متوفّرة: خصائص المفاتيح التي لا يمكن أن تكون فارغة ولا تتضمّن قيمًا تلقائية، ولا تتوفّر مَعلمات معرّف الموارد المنتظم (URI) المقابلة لها في معرّف الموارد المنتظم (URI) للطلب.
- أخطاء تحليل النوع: قيم الوسيطات المستخرَجة التي لا يمكن تحليلها
إلى نوع السمة المتوقّع (على سبيل المثال،
"abc"لسمةInt).
عند حدوث عدم تطابق، تعرض الدالة UriDeepLinkMatcher.match القيمة null، ما يسمح
بتقييم أدوات المطابقة اللاحقة.
لنفترض أنّ لدينا فئة مفتاح ومطابِق تم ضبطهما باستخدام قيم تلقائية وكائنات متداخلة وتعدادات:
enum class MapLayer { STANDARD, SATELLITE, TERRAIN } @Serializable data class LayerOptions( val style: String, val layer: MapLayer = MapLayer.STANDARD ) @Serializable data class MapKey( val location: String, val zoom: Int = 12, val options: LayerOptions ) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/map/{location}?zoom={zoom}&style={style}&layer={layer}"), serializer<MapKey>() )
يوضّح الجدول التالي نتائج المطابقة لعناوين URI مختلفة للطلبات:
| عنوان URI للطلب | نتيجة فك الترميز | نتيجة المباراة |
|---|---|---|
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE |
Success (تم توفير جميع المَعلمات) | 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.Maptypes throw anIllegalArgumentException, while nested collections (such asList<List<String>>) throw aSerializationException. - مجموعات العناصر المخصّصة غير المزوّدة بتعليقات توضيحية: مجموعات الأنواع المخصّصة (مثل
List<Filter>) تعرضSerializationExceptionما لم تتم إضافة تعليق توضيحي إلى نوع العنصر باستخدامDeepLinkSerializer. - الصفوف المُدمَجة غير المسطّحة: لا يمكن ربط صفوف
@Serializableالمُدمَجة بعنصر نائب واحد (مثل?user={user}) بدونDeepLinkSerializer.
// Throws IllegalArgumentException: Map decoding is not supported. @Serializable data class InvalidKey(val tags: Map<String, String>) : NavKey // Throws SerializationException: Only collections of primitives are supported. @Serializable data class InvalidKey(val filters: List<Filter>) : NavKey
مقارنة UriMatchResult
يتم ترتيب مثيلات UriMatchResult باستخدام المعايير التالية بالترتيب:
- نوع MatchResult: يحتلّ النوع
UriMatchResultمرتبة أعلى من أنواعMatchResultالأخرى. - المسار الدقيق: تحصل مطابقات المسار الحرفية على ترتيب أعلى من مطابقات العناصر النائبة أو أحرف البدل.
- عدد وسيطات المسار: يتم ترتيب التطابقات التي تحتوي على عدد أكبر من وسيطات المسار في مرتبة أعلى.
- وجود وسيطات: يتم ترتيب النتائج المطابقة التي تتضمّن وسيطات في مرتبة أعلى من تلك التي لا تتضمّن وسيطات.
- إجمالي عدد الوسيطات: يُعدّ إجمالي عدد الوسيطات (المسار وطلب البحث والمقتطف) هو العامل النهائي لكسر التعادل.
تخصيص UriDeepLinkMatcher
UriDeepLinkMatcher هي فئة open يمكنك إنشاء فئة فرعية منها لتخصيص سلوك مطابقة معرّف الموارد المنتظم (URI) واستخراج الوسيطات:
matchRequest: نقطة دخول مطابقة ذات مستوى أعلى لـDeepLinkRequestواردة. يمكنك إلغاء هذا الإجراء لفحص الإضافات في الطلب أو تطبيق شروط مسبقة مخصّصة قبل مطابقة معرّف الموارد المنتظم (URI).-
matchUri: تطابقDeepLinkUriمع النمط الذي تم ضبطه. يمكنك إلغاء هذه الطريقة لاعتراض معرّفات الموارد المنتظمة (URI) الواردة وتسويتها (على سبيل المثال، إعادة كتابة النطاقات الفرعية الديناميكية أو تنسيقات المسارات القديمة) قبل استدعاءsuper.matchUri. -
matchArguments: تلغي تسلسل المسار المستخرَج وخرائط وسيطات طلب البحث والتقسيم إلى مثيل مفتاح تنقّل باستخدامserializerالمقدَّم. يمكنك إلغاء هذه الطريقة لإدخال قيم ديناميكية أو تحويل الوسيطات قبل إنشاء المفتاح.
يوضّح المثال التالي كيفية إنشاء فئة فرعية من UriDeepLinkMatcher لتسوية بادئات مسارات عناوين URL القديمة قبل المطابقة:
class LegacyPrefixUriDeepLinkMatcher<T : Any>( uriPattern: DeepLinkUri, serializer: KSerializer<T> ) : UriDeepLinkMatcher<T>(uriPattern, serializer) { override fun matchUri(uri: DeepLinkUri): UriMatchResult<T>? { val path = uri.path val normalizedUri = if (path != null && path.startsWith("/legacy/")) { DeepLinkUri(uri.toString().replaceFirst("/legacy", "")) } else { uri } return super.matchUri(normalizedUri) } }