Ders 18 / 22
Doğrulama Şemaları
Kabul kurallarının tek bir bildirimde toplanması, ham girdinin çözülmüş değere dönüştürülmesi, aynı şemanın istemcide ve sunucuda aynı sonucu vermesi ve hata kodunun iletiden ayrı tutulması.
İçindekiler
Önceki ders formun değerlerini tuttu ama değerlerin kabul edilebilir olup olmadığına bakmadı. Ölçüm değeri sayı olmalı, birimine göre makul bir aralıkta durmalı, tarih gelecekte olmamalı, istasyon kodu tanımlı biçime uymalı.
Bu kuralların iki yerde bilinmesi gerekir. İstemcide, kullanıcıya sunucuya gitmeden geri bildirim vermek için. Sunucuda, çünkü Form Olayları dersinde söylendiği gibi istek sayfa hiç kullanılmadan da kurulabilir; istemci tarafı doğrulama bir kolaylıktır, güvence değildir. Aynı kuralı iki yere ayrı ayrı yazmak, ikisinin zamanla ayrışması demektir: istemci kabul eder, sunucu reddeder — ya da tersi, ki bu daha kötüdür.
Şema Bir Bildirimdir
Doğrulamayı iç içe koşullarla yazmak çalışır ama üç şeyi imkânsızlaştırır. Kuralları listelemek, kuralları başka bir ortamda çalıştırmak ve kuralları arayüze bildirmek. Bir alanın zorunlu olup olmadığını öğrenmek için kod okumak gerekiyorsa, o bilgi ekrandaki “zorunlu” işaretine bağlanamaz.
Doğrulama şeması, kuralların veri olarak yazılmasıdır: hangi alanın hangi kurallara tabi olduğu bir yapıda durur, kuralları uygulayan işlev ayrıdır. Bunun üç doğrudan sonucu vardır. Şema taşınabilir — aynı yapı başka bir çalışma ortamında da yorumlanabilir. Şema sorgulanabilir — arayüz hangi alanın zorunlu olduğunu şemadan okur. Şema birleştirilebilir — ortak alan kuralları tek yerde tanımlanıp birden çok formda kullanılır.
Bu, Web’in Temelleri ve HTML kursundaki kısıt doğrulamasının yerini almaz; onun üstüne biner. İşaretlemede bildirilen kısıtlar tarayıcının kendi düzeneğini çalıştırır ve sayfa betiği hiç yüklenmese bile iş görür. Şemanın kapsadığı alan daha geniştir: işaretlemede bildirilemeyen çapraz alan kuralları, birime göre değişen aralıklar, sunucuyla paylaşılan kod tabanı.
Doğrulamak Değil, Çözümlemek
Doğrulama işlevi çoğunlukla “geçerli mi” sorusuna doğru–yanlış döndürür. Bu, çağıran kodu aynı dönüşümü ikinci kez yapmaya zorlar: değer geçerliyse metni sayıya çevir, tarihi ayrıştır, boşlukları kırp. İki çevrim arasında ufak bir fark, doğrulanan değerle kullanılan değerin ayrışmasına yol açar.
Daha sağlam yol, doğrulamanın çözülmüş değeri döndürmesidir. Kural zinciri ham girdiyi
alır, her adımda dönüştürür ve sonunda kullanıma hazır tiplenmiş bir değer üretir. Alan
geçersizse değer üretilmez; geçerliyse üretilen değer artık kuralların garantisini taşır.
Bu, iki tarafın aynı sonuca varmasını da mümkün kılar: istemci "-4,2" dizgisini,
sunucu -4.2 sayısını verse bile zincirin çıkışı aynıdır.
Aynı Şema İki Tarafta
// dogrulama-semasi.mjs — ayni sema hem istemcide hem sunucuda const SIMDI = Date.parse("2026-01-14T12:00:00Z"); // sinama icin sabitlendi // --- Kural kitapligi: her kural ham degeri alir, cozulmus deger ya da hata dondurur const KURALLAR = { zorunlu: (ham) => ham === undefined || ham === null || String(ham).trim() === "" ? { hata: "zorunlu" } : { deger: ham }, kod: (ham, desen) => new RegExp(desen).test(String(ham)) ? { deger: String(ham) } : { hata: "biçim" }, sayi: (ham) => { const s = typeof ham === "number" ? ham : Number(String(ham).replace(",", ".")); return Number.isFinite(s) ? { deger: s } : { hata: "sayı_değil" }; }, secenek: (ham, izinli) => izinli.includes(String(ham)) ? { deger: String(ham) } : { hata: "seçenek_dışı" }, tarih: (ham) => { const t = Date.parse(String(ham)); return Number.isNaN(t) ? { hata: "tarih_değil" } : { deger: t }; }, enCokUzunluk: (ham, n) => String(ham ?? "").length <= n ? { deger: String(ham ?? "") } : { hata: "çok_uzun" }, }; const SEMA = { alanlar: { istasyon: [["zorunlu"], ["kod", "^KY-\\d{2}$"]], tarih: [["zorunlu"], ["tarih"]], deger: [["zorunlu"], ["sayi"]], birim: [["zorunlu"], ["secenek", ["C", "F"]]], not: [["enCokUzunluk", 120]], }, caprazlar: [ { ad: "aralik", gerekli: ["deger", "birim"], sina: (v) => { const [enAz, enCok] = v.birim === "F" ? [-76, 140] : [-60, 60]; return v.deger < enAz || v.deger > enCok ? { yol: "deger", kod: "aralık_dışı", param: { enAz, enCok } } : null; }, }, { ad: "gelecekDegil", gerekli: ["tarih"], sina: (v) => (v.tarih > SIMDI ? { yol: "tarih", kod: "gelecekte" } : null), }, ], }; function dogrula(sema, girdi) { const deger = {}, hatalar = []; for (const [alan, kurallar] of Object.entries(sema.alanlar)) { let ham = girdi[alan]; let bozuk = false; for (const [ad, param] of kurallar) { const sonuc = KURALLAR[ad](ham, param); if (sonuc.hata) { hatalar.push({ yol: alan, kod: sonuc.hata, param }); bozuk = true; break; } ham = sonuc.deger; // cozulen deger sonraki kurala girer } if (!bozuk) deger[alan] = ham; } for (const capraz of sema.caprazlar) { if (!capraz.gerekli.every((a) => a in deger)) continue; // girdi eksikse atlanir const hata = capraz.sina(deger); if (hata) hatalar.push(hata); } return { gecerli: hatalar.length === 0, deger, hatalar }; } const yaz = (ad, s) => { console.log(`${ad}`); console.log(` geçerli: ${s.gecerli}`); console.log(` değer : ${JSON.stringify(s.deger)}`); console.log(` hatalar: ${JSON.stringify(s.hatalar)}`); }; // --- Ayni kayit, iki farkli bicimde ---------------------------------------- // Istemci: butun degerler alanlardan gelen dizgilerdir. const istemciGirdisi = { istasyon: "KY-01", tarih: "2026-01-14", deger: "-4,2", birim: "C", not: "sabah okuması", }; // Sunucu: JSON govdesinde sayi sayi, dizgi dizgi gelir. const sunucuGovdesi = { istasyon: "KY-01", tarih: "2026-01-14", deger: -4.2, birim: "C", not: "sabah okuması", }; yaz("istemci girdisi (dizgiler)", dogrula(SEMA, istemciGirdisi)); yaz("sunucu gövdesi (JSON)", dogrula(SEMA, sunucuGovdesi)); const a = JSON.stringify(dogrula(SEMA, istemciGirdisi)); const b = JSON.stringify(dogrula(SEMA, sunucuGovdesi)); console.log("iki taraf aynı sonucu verdi mi:", a === b); // --- Formu hic kullanmayan bir istek --------------------------------------- yaz("\nsayfa atlanarak kurulan istek", dogrula(SEMA, { istasyon: "XX-9", tarih: "2027-03-01", deger: 900, birim: "K", not: "x".repeat(200), })); // --- Hata listesinden 422 govdesi ------------------------------------------ const s = dogrula(SEMA, { istasyon: "KY-01", tarih: "2026-01-14", deger: "88", birim: "C" }); const govde422 = { kod: "dogrulama", alanlar: s.hatalar.reduce((t, h) => ({ ...t, [h.yol]: { kod: h.kod, param: h.param } }), {}), }; console.log("\n422 gövdesi:", JSON.stringify(govde422));
istemci girdisi (dizgiler)
geçerli: true
değer : {"istasyon":"KY-01","tarih":1768348800000,"deger":-4.2,"birim":"C","not":"sabah okuması"}
hatalar: []
sunucu gövdesi (JSON)
geçerli: true
değer : {"istasyon":"KY-01","tarih":1768348800000,"deger":-4.2,"birim":"C","not":"sabah okuması"}
hatalar: []
iki taraf aynı sonucu verdi mi: true
sayfa atlanarak kurulan istek
geçerli: false
değer : {"tarih":1803859200000,"deger":900}
hatalar: [{"yol":"istasyon","kod":"biçim","param":"^KY-\\d{2}$"},{"yol":"birim","kod":"seçenek_dışı","param":["C","F"]},{"yol":"not","kod":"çok_uzun","param":120},{"yol":"tarih","kod":"gelecekte"}]
422 gövdesi: {"kod":"dogrulama","alanlar":{"deger":{"kod":"aralık_dışı","param":{"enAz":-60,"enCok":60}}}}
Çıktının Okunması
İlk iki blok aynı çözülmüş değeri üretti ve karşılaştırma doğru döndü. Girdiler farklıydı: istemci ondalık ayırıcı olarak virgül kullanan bir dizgi verdi, sunucu bir sayı. Zincir ikisini de aynı sayıya çevirdi. Tarih de her iki tarafta aynı zaman damgasına çözüldü. Bu eşitlik, şemanın tek kaynak olmasının pratik karşılığıdır: sunucunun reddettiği bir değeri istemcinin kabul etmesi ancak şema ayrışırsa mümkün olur.
Üçüncü blok formu atlayan bir isteği gösteriyor ve dört hata birden bildiriliyor. Kural
zincirinin ilk hatada durduğuna dikkat edin: istasyon alanı biçim kuralında
takıldığı için sonraki kurallar çalıştırılmadı ve alan çözülmüş değer üretmedi. Bunun
nedeni, sonraki kuralın önceki kuralın çıktısına güvenmesidir; ham bir değeri sayı sanan
bir aralık kuralı anlamsız hata üretir.
Aynı blokta bir eksik var ve bilinçli: deger 900 olmasına rağmen aralık hatası
bildirilmedi. Çünkü çapraz kural birim alanının çözülmüş olmasını gerektiriyor, birim
ise “K” değeriyle geçersiz kaldı. Çapraz alan kuralları girdileri eksikse atlanır.
Alternatif — eksik girdiyle varsayılan bir değer kullanmak — yanıltıcı hatalar üretir:
kullanıcı birimi düzelttiğinde bildirilen aralık değişebilir. İstek zaten reddediliyor;
kullanıcı birimi düzeltip yeniden gönderdiğinde aralık hatası o zaman bildirilir.
Son satır döngüyü kapatıyor. Sunucu tarafında aynı şemadan çıkan hata listesi, doğrudan
422 yanıtının gövdesine dönüşüyor. REST İstemcisi dersinde bu gövde ayrinti alanında
taşınıyordu; Form Durum Yönetimi dersinde alan adlarıyla eşleştirilip forma bağlanıyordu.
Alan adlarının iki tarafta aynı olması bu zincirin ön koşuludur.
Her Kural İki Tarafta Çalışamaz
Şemanın paylaşılması, bütün kuralların paylaşılabildiği anlamına gelmez. Üç sınıf ayırmak gerekir.
İki tarafta da çalışan kurallar girdinin kendisine bakar: biçim, tür, uzunluk, aralık, alanlar arası tutarlılık. Şemanın gövdesini bunlar oluşturur.
Yalnız sunucuda çalışabilen kurallar sistemin durumuna bakar: istasyon kodunun kayıtlı olması, aynı tarih ve istasyon için ölçümün zaten girilmiş olması, kullanıcının o istasyona yazma yetkisi. İstemci bunları bilmez ve bilmemelidir. Bu kuralların istemcideki karşılığı, sunucudan gelen alan hatasını göstermektir.
Yalnız istemcide anlamlı kurallar girdinin sunuma ilişkin yanına bakar: alanın uzunluk sayacı, henüz tamamlanmamış bir girdinin uyarısı. Bunlar gönderimi engellemez.
Bir de sunucuya soru soran ara bir sınıf vardır: kullanıcı yazarken istasyon kodunun varlığını sorgulamak gibi. Bu, doğrulamanın eşzamansız hâle gelmesidir ve üç kural gerektirir. Sorgu gecikmeli tetiklemeyle seyreltilir. Geç gelen yanıt, Yükleme ve Hata Durumları dersindeki damga kuralıyla elenir. Gönderim, cevabı bekleyen bir sorgu varken beklemek yerine gönderilir ve sunucunun kararına güvenilir — eşzamansız doğrulama bir kolaylıktır, kapı değildir.
Hata Kodu, İleti Değil
Şemanın ürettiği hatalar kod ve parametre taşıyor, metin taşımıyor. Bu ayrım üç nedenden zorunludur.
Metin dile bağlıdır. Aynı hata kodunun Türkçe ve İngilizce karşılığı farklıdır; kodu üreten yerin dili bilmesi gerekmez. Uluslararasılaştırma dersi bu eşlemeyi kurar.
Metin bağlama bağlıdır. Aynı zorunlu kodu, alanın yanında “Bu alan gerekli”, hata
özetinde “Ölçüm değeri girilmedi” biçiminde yazılır. Tek bir metni iki yerde kullanmak
ikisinden birini bozar.
Metin parametreye bağlıdır. Aralık hatası, sınırları parametre olarak taşır; ileti bunları yerine koyarak yazılır. Sınır değiştiğinde metin değil şema değişir.
Kodların adlandırılması da bir sözleşmedir. İstemci ile sunucu aynı kod kümesini kullanmalıdır; sunucunun tanımadığı bir kod gönderdiği durumda istemci genel bir ileti göstermeli, kodu ekrana yazmamalıdır.
Özet
- Doğrulama şeması kuralları veri olarak yazar; bu sayede kurallar listelenebilir, sorgulanabilir ve başka bir ortamda çalıştırılabilir hâle gelir.
- Şema, işaretlemede bildirilen kısıt doğrulamasının yerini almaz, kapsamadığı çapraz alan ve duruma bağlı kuralları ekler.
- Kural zinciri doğrulamakla kalmaz, ham girdiyi çözülmüş değere dönüştürür; böylece istemcinin dizgisi ile sunucunun sayısı aynı sonuca varır.
- Zincir ilk hatada durur, çünkü sonraki kural önceki kuralın çıktısına güvenir; çapraz alan kuralları gerekli girdileri çözülmemişse atlanır.
- Kurallar üç sınıftır: iki tarafta çalışanlar, yalnız sunucuda çalışabilenler ve yalnız istemcide anlamlı olanlar. Eşzamansız doğrulama bir kolaylıktır, gönderimi durdurmaz.
- Hata kodu ve parametre taşınır, ileti taşınmaz; metin dile, bağlama ve parametreye bağlıdır ve sunum katmanında üretilir.
Sonraki Adım
Elde artık her alan için bir hata kodu ve parametresi var. Sıradaki soru bunların ekranda nasıl görüneceği değil — nasıl duyulacağı. Ekrana bakmayan bir kullanıcı, kırmızı çerçeveyi görmez; hata iletisi alanın altına yazıldığında ekran okuyucunun onu alanla birlikte okuması kendiliğinden olmaz. Gönderim reddedildiğinde odağın nereye gideceği, kaç hatanın olduğunun nasıl bildirileceği ve iletinin alanla nasıl ilişkilendirileceği ayrı kararlar ister. Sonraki ders bu ilişkilendirmeyi kurar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.