Filter Enum API Publik: Tiga Lapis Validasi untuk Satu Nilai
Nilai filter display_mode dijaga tiga lapis: handler 400, error sentinel ErrValidation, dan SQL berparameter. SQL WHERE yang lama ikut dirapikan.
Ringkasan
Klausa SQL lama bermasalah. Diperbarui dengan routing ulang dan validasi. Selesai dengan tiga lapis keamanan.
Klausa yang menumpang logika editor
Repository API publik portal kota fiktif KotaPortal punya satu cabang WHERE yang lama mengganjal. Arus tampilan visual dan text tidak difilter dari kolom, melainkan direkonstruksi: visual berarti mode video, atau mode default yang punya gambar; text berarti noimage, atau default tanpa gambar. Deteksi "punya gambar" itu sendiri berbentuk ekspresi SQL dengan OR dan NOT EXISTS ke tabel media. Logika editor hidup di query, sedangkan kolom display_mode yang sudah menyimpan keputusan itu malah dipilah ulang.
Bedanya tipis tapi menentukan. Selama ekspresi has-image dan kebiasaan editor senada, semua arus terisi benar. Begitu arti "punya gambar" bergeser, arus visual bisa menampung entity yang seharusnya tabel, atau sebaliknya, tanpa satu pun error. Query terlihat sehat, count cocok dengan list, dan datanya salah. Titik ubahnya sederhana: arus dirouting ulang menjadi klausa murni di atas kolom tersebut, arus visual menjadi pencarian di dalam pasangan mode video dan default, dan arus text menjadi mode noimage saja. Ekspresi has-image dihapus seluruhnya.
Tebakan awal: cukup validasi di pintu HTTP
Sambil routing diubah, satu parameter query baru ditambahkan untuk filter eksak per mode. Tebakan pertama: satu switch di handler cukup. Nilai di luar tiga mode resmi dibalas respons 400 dengan kode VALIDATION, selesai. Untuk pemanggil HTTP, memang itu satu-satunya lapis yang terlihat.
Yang membuat lapis itu tidak cukup: module yang sama dipakai jalur lain. Pemanggil non-HTTP, misalnya job atau script internal, melewati handler sepenuhnya dan menyapa service serta repository langsung. Nilai liar yang lolos dari switch tidak akan pernah ketemu 400; ia jalan sampai ke query.
Dua lapis berikutnya: sentinel error dan SQL berparameter
Lapis kedua tinggal di repository. Nilai arus di luar dua kata kunci resmi tidak ditebak dan tidak diam; ia membalas error sentinel yang dibungkus dengan format verb khusus:
default:
return nil, 0, fmt.Errorf("%w: presentation harus visual atau text", ErrValidation)
Bentuk pembungkusan ini penting karena fmt.Errorf dengan format biasa membuang isi error asli dan menyisakan teksnya saja; dengan %w, error asli ikut terbawa dan membentuk pohon error yang bisa diperiksa, persis mekanisme yang diperkenalkan di pengumuman error wrapping Go 1.13 [6]. Pemanggil memeriksa lewat fungsi errors.Is [5] dari pustaka standar, cara pemeriksaan yang direkomendasikan dokumentasi menggantikan perbandingan kesamaan biasa. Service dan repo jadi fail-closed: panggilan tanpa HTTP tetap kena kontrak yang sama, cukup dengan satu pemeriksaan sentinel di awal penanganan error.
Lapis ketiga menjaga struktur query itu sendiri. Nilai filter tidak pernah dirangkai ke string SQL; klaasanya berparameter dan literal enum hanya berasal dari switch whitelist. Panduan pencegahan SQL injection OWASP [4] memisahkan dua hal ini: nilai data lewat parameter binding, sedangkan struktur query seperti nama kolom dan kata kunci harus datang dari pemetaan tetap milik aplikasi, bukan dari input. Satu catatan tempat yang gampang terlewat: klausa filter ditulis sebelum perhitungan count supaya count dan list memakai string WHERE yang sama; filter setelah count adalah resep angka total yang tidak cocok dengan isinya.
Tiga lapis itu tidak saling menggantikan. Handler memberi jawaban paling cepat untuk klien; sentinel error menjaga pemanggil internal; SQL berparameter memastikan nilai terburuk pun tetap terperlakukan sebagai data. Setelah ekspresi has-image hilang dan routing jadi klausa murni, korek kecil di masa depan cukup menyentuh satu tempat: arti tiga mode milik editor, bukan lagi cabang query yang menirukannya.
Urutan pemeriksaannya juga mengikuti alur yang sama. Nilai asing dari query string berhenti di handler tanpa menyentuh service. Nilai asing dari pemanggil internal berhenti di gerbang repository dengan error yang bisa diuji. Kombinasi nilai yang sah di satu lapis tapi janggal di lapis lain tetap tertangkap, karena ketiga lapis membaca kamus enum yang sama: tiga string milik editor, didefinisikan sekali, dan dipakai konsisten di seluruh lapisan.
Sumber
- OWASP, lembar contekan pencegahan SQL injection, diakses 26 Oktober 2026.
- Go Docs, package errors, diakses 26 Oktober 2026.
- The Go Blog, Working with Errors in Go 1.13, diakses 26 Oktober 2026.