จับคู่ Deep Link ของ URI

หากต้องการจับคู่ URI แบบลำดับชั้นกับรูปแบบและดึงอาร์กิวเมนต์ ให้ใช้ UriDeepLinkMatcher โดยจะใช้ kotlinx.serialization เพื่อดีซีเรียลไลซ์อาร์กิวเมนต์ที่ตรงกันเป็นคลาสคีย์

หากต้องการสร้าง UriDeepLinkMatcher ให้ระบุรูปแบบ DeepLinkUri และ Serializer สำหรับคีย์ที่เกี่ยวข้อง ดังนี้

สำหรับ URI ที่ไม่ใช่แบบลำดับชั้นหรือรูปแบบที่กำหนดเอง (เช่น tel:) โปรดดูหัวข้อสร้างตัวจับคู่ Deep Link ที่กำหนดเอง

รูปแบบการจับคู่ที่รองรับ

UriDeepLinkMatcher จับคู่ URI ตามคอมโพเนนต์ 5 รายการ ได้แก่ รูปแบบ, หน่วยงาน, เส้นทาง, การค้นหา และส่วนย่อย ส่วนต่อไปนี้จะอธิบายไวยากรณ์รูปแบบที่รองรับ ตัวยึดตำแหน่งอาร์กิวเมนต์ และกฎการจับคู่สำหรับแต่ละคอมโพเนนต์

การจับคู่รูปแบบ

หากไม่มีรูปแบบในรูปแบบ 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"

การจับคู่แฟรกเมนต์

ระบบรองรับรูปแบบ Fragment ประเภทต่อไปนี้

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 เป็นประเภทพื้นฐาน, enum, คอลเล็กชัน และออบเจ็กต์ที่กำหนดเอง การเรียงอันดับแบ่งออกเป็น 2 หมวดหมู่ ได้แก่

  • การแปลงเป็นอนุกรมมาตรฐาน: ใช้ kotlinx.serialization เพื่อแปลงกลับเป็นอนุกรมเป็น
    • Primitives (Boolean, Int, Long, Float, Double, Char, Byte, Short) และ String
    • Enums
    • Set, List หรือ Array ของพรีมิติฟ สตริง หรือ Enum
    • คลาส @Serializable ที่ซ้อนกัน (ซึ่งพร็อพเพอร์ตี้จะถูกรวมเป็นตัวยึดตำแหน่ง URI แต่ละรายการ)
  • การแปลงเป็นอนุกรมที่กำหนดเองด้วย DeepLinkSerializer: แปลงระหว่าง String รายการเดียวกับออบเจ็กต์ที่กำหนดเอง ประเภทภายนอก (เช่น java.time.LocalDate) หรือคอลเล็กชันที่คั่นด้วยตัวคั่นที่กำหนดเอง

การเรียงอันดับแบบมาตรฐาน

UriDeepLinkMatcher ทำงานได้ทันทีสำหรับประเภทมาตรฐานและโครงสร้างแบบแบนราบโดยไม่ต้องใช้การติดตั้งใช้งาน Serializer ที่กำหนดเอง

แบบพื้นฐานและสตริง

UriDeepLinkMatcher จะถอดรหัสประเภทพื้นฐาน (Boolean, Int, Long, Float, Double, Char, Byte, Short) และ String โดยอัตโนมัติ ดังนี้

Enums

ค่า Enum จะจับคู่กับชื่อองค์ประกอบ Enum โดยคำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ ดังนี้

คอลเล็กชันคำค้นหาที่ซ้ำกัน

พารามิเตอร์การค้นหาที่มีคีย์ซ้ำ (เช่น ?id=10&id=20) จะดีซีเรียลไลซ์เป็น List<T>, Set<T> หรือ Array<T> โดยอัตโนมัติ โดยที่ T เป็นประเภทพื้นฐาน, String หรือ enum

คลาส @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 ซึ่งช่วยให้สามารถประเมินตัวจับคู่ที่ตามมาได้

พิจารณาคลาสคีย์และ Matcher ที่กำหนดค่าด้วยค่าเริ่มต้น ออบเจ็กต์ที่ฝัง และ Enum ดังนี้

ตารางต่อไปนี้แสดงผลลัพธ์การจับคู่สำหรับ 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 สำเร็จ (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" ไม่อยู่ใน enum) null

การกำหนดค่าที่ไม่รองรับ

หากคลาสคีย์มีประเภทข้อมูลที่ไม่รองรับ UriDeepLinkMatcher จะแสดงข้อยกเว้นระหว่างการจับคู่แทนที่จะแสดงผล null

  • แผนที่และคอลเล็กชันหลายมิติ: UriDeepLinkMatcher รองรับเฉพาะคอลเล็กชันมิติเดียวของประเภทพื้นฐาน, สตริง, enum หรือประเภทที่กำหนดเองซึ่งมีคำอธิบายประกอบเป็น DeepLinkSerializer Map ประเภทจะแสดง IllegalArgumentException ขณะที่คอลเล็กชันที่ฝัง (เช่น List<List<String>>) จะแสดง 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: ดีซีเรียลไลซ์เส้นทาง การค้นหา และอาร์กิวเมนต์ Fragment ที่แยกออกมาเป็นอินสแตนซ์คีย์การนำทางโดยใช้ serializer ที่ระบุ ลบล้างค่านี้เพื่อแทรกค่าแบบไดนามิกหรือเปลี่ยนรูปแบบอาร์กิวเมนต์ก่อนการสร้างอินสแตนซ์คีย์

ตัวอย่างต่อไปนี้แสดงการสร้างคลาสย่อย UriDeepLinkMatcher เพื่อปรับคำนำหน้าเส้นทาง URL เดิมให้เป็นมาตรฐานก่อนการจับคู่