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

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

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

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

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

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

การจับคู่แบบแผน

หากไม่มีแบบแผนในรูปแบบ URI ระบบจะจับคู่ทั้ง http และ https หากต้องการจับคู่แบบแผนที่เฉพาะเจาะจง ให้ใส่แบบแผนนั้นในรูปแบบ โดยมีข้อยกเว้นว่าแบบแผน http ในรูปแบบจะจับคู่ทั้ง http และ https URI ของคำขอ ขณะที่ 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

การจับคู่ Authority

UriDeepLinkMatcher จะทำการจับคู่ Authority (โฮสต์และพอร์ตที่ไม่บังคับ) ของ URI แบบตรงทั้งหมดโดยไม่คำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ ระบบไม่รองรับตัวยึดตำแหน่งหรือไวลด์การ์ดใน Authority และจะไม่มีการแยกอาร์กิวเมนต์

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

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

การแยกซีเรียลไลซ์มาตรฐาน

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

ประเภทข้อมูลพื้นฐานและสตริง

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

Enum

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

พิจารณาคีย์คลาสและตัวจับคู่ที่กำหนดค่าด้วยค่าเริ่มต้น ออบเจ็กต์ที่ซ้อนกัน และ 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 ประเภทจะแสดง an 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: แยกซีเรียลไลซ์แผนที่อาร์กิวเมนต์เส้นทาง, การค้นหา และส่วนย่อยเป็นอินสแตนซ์คีย์การนำทางโดยใช้ serializer ที่ระบุ ลบล้างฟังก์ชันนี้เพื่อแทรกค่าแบบไดนามิกหรือแปลงอาร์กิวเมนต์ก่อนการสร้างอินสแตนซ์คีย์

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