Menu

Monday, August 14, 2017

[NodeJS] Optional Field ของ Joi ไม่อนุญาตค่า null และ empty string
[NodeJS] Optional Field of Joi Validator not Allow Null and Empty String

เนื่องจากใช้ HapiJS เป็น framework ของโปรเจค จึงใช้ Joi เป็น validator ซึ่งก็เหมือน Framework ตัวอื่นๆ คือ สามารถระบุได้ว่า field ใดนั้น required หรือ field ใดนั้นไม่ required

Joi มี method สำหรับการระบุข้างต้น คือ required() และ optional() ตามลำดับ ซึ่งถ้าไม่ระบุจะเป็น optional โดยอัตโนมัติ แต่มันไม่ได้จบเพียงเท่านี้ เพราะ Library ตัวนี้มีบางอย่างที่ขัดกับ validator ของ framework อื่นหลบซ่อนอยู่

สำหรับ required() จะเป็นปกติเหมือน framework ทั่วไป คือ field นั้นต้องมีค่า ซึ่งค่านั้นต้องไม่ใช่ null หรือ empty string

สำหรับ optional() จะมีความแตกต่างจาก framework อื่น คือ field นั้นต้องมีค่า (ซึ่งค่านั้นต้องไม่ใช่ null หรือ empty string) หรือ field นั้นต้องหายไปเลย (undefined) อย่างใดอย่างหนึ่งเท่านั้น

อย่างไรก็ตาม สำหรับ framework ทั่วไป ในกรณีที่ไม่ระบุว่า required ความเป็นไปได้ของ field นั้น คือ มีค่าเป็นอะไรก็ได้รวมถึง null และ empty string ด้วย หรือ field นั้นจะหายไปเลยก็ได้

จะเห็นได้ว่า Joi ต่อให้เป็น optional ก็ไม่สามารถส่งค่า null หรือ empty string มาได้ วิธีแก้ไข คือ เพิ่ม .allow(null) และ .allow('') ต่อท้าย field ที่เป็น optional ด้วย

ปล. มีคนเปิด Issue เกี่ยวกับเรื่องนี้ ให้รวมค่า null กับ empty string เป็น optional ของ Joi ด้วย แต่ก็ได้คำตอบว่า มันโคตรจะไม่ปกติเลยหรือมันไม่ใช่ common use case ซึ่งจากคำตอบก็รู้แล้วแน่ๆว่า จะไม่มีการแก้ไขเกี่ยวกับเรื่องนี้ในอนาคตแน่นอน

สำหรับข้อมูลเพิ่มเติม สามารถอ่านได้ที่
How to allow a string or null?
Joi.string().optional() doesn't treat an empty string as unset

[NodeJS] การแปลงวันที่จาก ค.ศ. เป็น พ.ศ. หรือจาก พ.ศ. เป็น ค.ศ.
[NodeJS] How to Convert Date from A.D. to B.E. or from B.E. to A.D.

ถ้าจะกล่าวถึง Library ที่เกี่ยวกับวันที่และเวลาสำหรับ NodeJS หลายๆคนคงคิดถึง momentjs งานนี้ก็เช่นกัน ด้วยการที่คิดว่าการแปลงปี ค.ศ. เป็น พ.ศ. สลับไปมา มันก็แค่เพิ่มหรือลบปีไป 543 ปีเท่านั้น

สมมติว่า dateStr เป็นวันที่ที่เป็นปี ค.ศ. ที่จะแปลงเป็นปี พ.ศ. ก็จะได้โค้ดประมาณนี้
let date = moment(dateStr, 'DD/MM/YYYY');
if (date.isValid()) {
    date.add(543, 'years');
    return date.format('DD/MM/YYYY');
}
หรือ
let date = moment(dateStr, 'DD/MM/YYYY');
if (date.isValid()) {
    date.subtract(543, 'years');
    return date.format('DD/MM/YYYY');
}
แต่งานนี้ก็ไม่ผ่าน Tester ฝีมือดีของเรา เมื่อ Tester ทำการทดสอบด้วยวันที่ 29 กุมภาพันธ์ของปีที่เป็น Leap year

กรณีที่ต้องการแปลงจาก ค.ศ. เป็น พ.ศ. ยกตัวอย่างเช่น 29 กุมภาพันธ์ ค.ศ. 1968 (29/02/1968) เมื่อทำการแปลงตามโค้ดข้างต้น สิ่งที่คาดหวังคือ 29 กุมภาพันธ์ พ.ศ. 2511 แต่สิ่งที่ได้ไม่เป็นเช่นนั้น

กรณีที่ต้องการแปลงจาก พ.ศ. เป็น ค.ศ. ยกตัวอย่างเช่น 29 กุมภาพันธ์ พ.ศ. 2511 (29/02/2511) เมื่อทำการแปลงตามโค้ดข้างต้น สิ่งที่คาดหวังคือ 29 กุมภาพันธ์ ค.ศ. 1968 แต่สิ่งที่ได้ คือ Invalid date

ทั้งสองกรณีเกิดจาก momentjs จะทำการแปลงวันที่ในรูปแบบปี ค.ศ. เท่านั้น โค้ดข้างต้นจึงเป็นการบวกลบปี ค.ศ. เพียงเท่านั้น ไม่ใช่การแปลงเป็นปี พ.ศ. แต่อย่างใด

หลังจากนั้น ก็ต้องเปลี่ยนวิธีการแปลงปีเป็นแบบข้อความแทน โดยใช้ Regular Expression ก็จะได้โค้ดตามด้านล่าง
const DATE_REGEXP: RegExp = new RegExp('^(0?[1-9]|[1-2][0-9]|3[0-1])/(0?[1-9]|1[0-2])/([0-9]{4})$', 'gi');
dateStr = dateStr.replace(DATE_REGEXP,
    (str: string, day: string, month: string, year: string) => {
        return `${day}/${month}/${parseInt(year, 10) + 543}`;
});
หรือ
const DATE_REGEXP: RegExp = new RegExp('^(0?[1-9]|[1-2][0-9]|3[0-1])/(0?[1-9]|1[0-2])/([0-9]{4})$', 'gi');
dateStr = dateStr.replace(DATE_REGEXP,
    (str: string, day: string, month: string, year: string) => {
        return `${day}/${month}/${parseInt(year, 10) - 543}`;
});
ปล. งานนี้ต้องขอบคุณ Tester เป็นอย่างมากที่คิดเคสได้รอบครอบขนาดนี้ ถ้าทดสอบด้วยวันที่อื่นคงไม่มีทางเจอบั๊กตัวนี้แน่นอน (โอกาสมีเพียงหนึ่งวันในสี่ปีเท่านั้น)