Skip to main content

ตรวจความถูกต้องของคำตอบฟอร์ม (Validation Engine)

ภาพรวม​

เครื่องยนต์ validation ที่รันฝั่ง server ก่อนบันทึกคำตอบ ถือเป็นด่านจริงไม่ใช่เพียง UI hint เพราะแม้ฝั่ง client-web จะ validate มาก่อนแล้ว แต่ request ที่ทำมือสามารถข้ามด่านนั้นไปได้ ตัว engine เป็น service ที่ stateless ล้วนและไม่แตะ database

จุดที่ต้องเข้าใจคือ engine นี้ port มาจาก JS จึงต้องเลียนพฤติกรรมเฉพาะตัวของ JS ให้ตรง เช่นการนับความยาวต้องนับเป็น UTF-16 code unit เหมือน String.length ไม่ใช่นับเป็น byte เพื่อให้ข้อความภาษาไทยผ่านหรือไม่ผ่านเหมือนเดิมทุกกรณี

Business Flow​

ValidateAnswers(questions, answers)​

  1. วนเฉพาะ คำตอบที่ส่งมา เท่านั้น ทำให้ field ที่ required แต่ไม่ได้ส่งมาเลยจะไม่ถูกตรวจ ซึ่งเป็น parity กับ source
  2. key ที่ไม่ตรงกับคำถามใดจะให้ error Question with ID {id} not found in form definition
  3. คำถามที่ไม่ required และมีค่าว่าง ("" หรือ null) จะถูกข้าม
  4. ที่เหลือถูกส่งเข้า validateSingleAnswer โดยข้อความ error จะถูก prefix ด้วยชื่อคำถาม ซึ่งไล่ลำดับจาก title ไป placeholder และสุดท้ายเป็น Question {id}
  5. คืนผลลัพธ์เป็น {IsValid, Errors} แล้ว caller จะแปลงเป็น 400 body แบบ object

validateSingleAnswer(q, value)​

  1. coerce ค่าให้เป็น string โดย string จะถูก trim, number ใช้รูปแบบของ JS, boolean กลายเป็น "true" หรือ "false" และชนิดอื่น ๆ ถูกแปลงเป็น JSON
  2. ค่าว่างที่ไม่ required จะผ่าน ส่วน required ที่ว่างจะได้ This field is required
  3. commonRule.pattern มีความสำคัญเหนือกฎตามชนิด field หากมีจะใช้ pattern นั้นพร้อมตรวจ minLength และ maxLength ของ commonRule ทันที และเลือกข้อความ error ตามลำดับ errorMessageTh → errorMessage → ข้อความ default
  4. หากไม่มี commonRule pattern จึงเข้าสู่กฎตามชนิด field
    • email ใช้ default ^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$ และ override ด้วย q.rule ได้
    • phone ใช้ default ^[0-9]{10,15}$ และหากมี q.rule จะตัดเครื่องหมาย " ออกก่อน
    • number ใช้ ^[0-9]+$, url ใช้ ^(https?|ftp)://[^\s/$.?#].[^\s]*$ และ citizen_id ใช้ ^[0-9]{13}$
    • custom ใช้ q.regex แบบดิบ ส่วน custom_input จะตัด / หน้าและท้ายออกก่อน พร้อมรองรับ min/maxLength และข้อความภาษาไทยจาก commonRule โดย default คือ กรุณากรอกข้อมูลให้ถูกต้อง
    • date ใช้ ^\d{4}-\d{2}-\d{2}$
    • date_of_birth ตรวจรูปแบบวันที่แล้วคำนวณอายุจากปีปัจจุบันลบปีเกิด และลบอีก 1 หากยังไม่ถึงวันเกิด หากต่ำกว่า minAge จะได้ Age must be at least N years
    • short_text จำกัด 1 ถึง 255 ตัวอักษร, paragraph จำกัด 1 ถึง 5000 และ custom_text ใช้ minLength/maxLength ของคำถามเอง โดย default คือ 1 ถึง 1000
    • terms ต้องมีค่าเป็น true หรือ "true" มิฉะนั้นจะได้ You must agree to the terms and conditions
    • file_upload ผ่านเสมอ
    • ชนิดตัวเลือกได้แก่ single_choice, multiple_choice, dropdown และ multi_select_dropdown ยังปิดการตรวจไว้ ซึ่งตรงกับ source ที่ comment โค้ดส่วนนี้ไว้เช่นกัน
    • ชนิดที่ไม่รู้จักจะผ่าน
  5. หากมี regex และค่าไม่ว่าง ระบบจะ compile แล้ว match โดย pattern จะถูกแปลงจาก JS escape (\uXXXX, \u{..}) ให้เป็นรูปแบบ RE2 (\x{..}) ก่อนด้วย util.TranslateJSRegex
  6. pattern ที่ RE2 compile ไม่ได้ เช่น lookaround หรือ backreference ซึ่ง Go ไม่รองรับ จะไม่ถือเป็น error ของผู้ใช้ แต่คืนข้อความ Invalid validation pattern configured for this field เพื่อสื่อว่าเป็น config error

ไฟล์และฟังก์ชันหลัก​

feature นี้ไม่มี route ของตัวเอง แต่ถูกเรียกจาก formsubmission.Service.Submit

ไฟล์ฟังก์ชัน
internal/formsubmission/validation.goNewFormValidationService(), (*FormValidationService).ValidateAnswers, validateSingleAnswer, validateTextLength, computeAge, utf16Len, stripSlashes, firstNonEmpty, formatJSNumber, decodeQuestionMap, isEmptyValue
internal/util/jsregex.goTranslateJSRegex
internal/formsubmission/errors.gonewValidationError(errors) ซึ่งสร้าง 400 body แบบ object
internal/formsubmission/entity.goค่าคงที่ชนิดคำถามทั้งหมด เช่น QFirstName, QEmail, QPhone, QCustomInput, QDateOfBirth, QTerms

จุดเชื่อมต่อกับ Service อื่น​

  • อ่านนิยามคำถามจาก form_builder.questions ที่ผ่าน transformPattern มาแล้ว จึงมี key commonRule ติดมาด้วย ดูรายละเอียดที่ โหลดโครงฟอร์ม และ กฎ Validation กลาง
  • ถูกเรียกใน ส่งคำตอบฟอร์ม ที่ขั้นตอนที่ 5 คือก่อน profile mapping และก่อนด่าน OTP
  • ไม่แตะตารางหรือ service ภายนอกใด ๆ
  • ฝั่ง client-web ที่เกี่ยวข้องคือ feature form-fill เนื่องจากข้อความ error ที่แสดงบนฟอร์มมาจาก engine นี้