لمطابقة معرّفات الموارد المنتظمة الهرمية مع الأنماط واستخراج الوسيطات، استخدِم
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 في نمط مع كل من معرّفات الموارد المنتظمة لطلبات http وhttps، بينما يتطابق https في نمط مع طلبات https فقط.
| معرّف الموارد المنتظم للنمط | عنوان 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 للطلب | المطابقة |
|---|---|---|
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 للطلب | المطابقة | الوسيطات المستخرَجة |
|---|---|---|---|
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 للطلب | الوسيطات المستخرَجة |
|---|---|---|
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 للطلب | الوسيطات المستخرَجة |
|---|---|---|
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 لطلب وارد متطلبات النمط أو النوع:
- المَعلمات المطلوبة غير متوفّرة: خصائص المفاتيح غير القابلة للقيم الفارغة بدون قيم تلقائية والتي لا تتوفّر مَعلمات عنوان URL المقابلة لها في عنوان URL الخاص بالطلب.
- أخطاء تحليل النوع: قيم وسيطة مستخرَجة لا يمكن تحليلها إلى نوع السمة المتوقّع (على سبيل المثال،
"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 للطلب | نتيجة فك التشفير | نتيجة المطابقة |
|---|---|---|
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. تُصدر أنواعMapالخطأIllegalArgumentException، بينما تُصدر المجموعات المتداخلة (مثلList<List<String>>) الخطأSerializationException. - مجموعات العناصر المخصّصة غير المشروحة: تعرض مجموعات الأنواع المخصّصة (مثل
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) } }