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 ตามลำดับ errorMessageTherrorMessage → ข้อความ 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 นี้