اعتبار سنجی فرم قدرتمند با چارچوب رویای OCaml

Summarize this content to 400 words in Persian Lang
توجه: Dream_html.form و query توابع کمکی منتشر نشده است. آنها در نسخه بعدی برای opam منتشر خواهند شد. همه چیز دیگر در حال حاضر در دسترس است!
من مدتها طرفدار قدرت فرمهای HTML و طبیعی بودن آنها برای ساخت صفحات وب بودهام که به کاربران اجازه میدهد دادهها را وارد کنند. اخیراً، من این فرصت را پیدا کردم که یک برنامه داخلی قدیمی را که در محل کار از آن استفاده می کنیم با استفاده از htmx و Scala's Play Framework بازسازی کنم. در این برنامه ما اغلب از فرم های HTML برای ارسال اطلاعات استفاده می کنیم. به عنوان مثال:
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
Play از رمزگشایی دادههای فرم HTML ارسالی به مقادیر انواع سفارشی و گزارش همه خطاهای اعتبارسنجی که ممکن است رخ داده باشد پشتیبانی بسیار خوبی دارد، که به من اجازه میدهد خطاها را مستقیماً در کنار خود فرمها با استفاده از Constraint Validation API ارائه کنم. در پست قبلی در مورد آن نوشتم.
اما در دنیای OCaml، وضعیت آنقدرها پیشرفته نبود. ما چند ابزار اساسی برای تجزیه داده های فرم در لیست جفت کلید-مقدار داشتیم:
اما اگر میخواهیم این فهرست را به یک نوع سفارشی رمزگشایی کنیم، به چیزی پیچیدهتر نیاز داریم، مانند شاید سازگار. با این حال، Conformist این مشکل را دارد که هر بار فقط یک خطا را گزارش می کند. اگر باید فرم ارسالی را رمزگشایی کنیم:
accept-terms: true
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
ما می خواهیم یک را ببینیم فهرست خطاهای اعتبارسنجی، مانند این:
name: required
email: required
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اعتبار سنجی فرم dream-html
خلاصه، من به این نتیجه رسیدم که به تجربه اعتبارسنجی فرم ارگونومیک تری در OCaml نیاز داریم. و از آنجایی که من قبلاً بستهای را نگه میدارم که کمککنندههای ایمن را در بالای چارچوب وب Dream اضافه میکند، فکر میکردم که افزوده خوبی باشد. بیایید آن را برای چرخش در REPL در نظر بگیریم:
$ utop -require dream-html
# type add_user = {
name : string;
email : string;
accept_terms : bool;
};;
# let add_user =
let open Dream_html.Form in
let+ name = required string “name”
and+ email = required string “email”
and+ accept_terms = optional bool “accept-terms” in
{
name;
email;
accept_terms = Option.value accept_terms ~default:false;
};;
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
حالا ما یک ارزش داریم add_user : add_user Dream_html.Form.t، که یک رمزگشا به نوع سفارشی ما است. بیایید آن را امتحان کنیم:
# Dream_html.Form.validate add_user [];;
– : (add_user, (string * string) list) result =
Error [(“email”, “error.required”); (“name”, “error.required”)]
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
ما لیستی از خطاهای اعتبار سنجی فرم را با نام فیلدها و کلیدهای پیام خطا دریافت می کنیم (این امکان بومی سازی برنامه را فراهم می کند).
بیایید یک رمزگشایی موفق را امتحان کنیم:
# Dream_html.Form.validate add_user [“name”, “Bob”; “email”, “bob@example.com”];;
– : (add_user, (string * string) list) result =
Ok {name = “Bob”; email = “bob@example.com”; accept_terms = false}
# Dream_html.Form.validate add_user [“name”, “Bob”; “email”, “bob@example.com”; “accept-terms”, “true”];;
– : (add_user, (string * string) list) result =
Ok {name = “Bob”; email = “bob@example.com”; accept_terms = true}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
بیایید یک خطای نوع را بررسی کنیم:
# Dream_html.Form.validate add_user [“name”, “Bob”; “email”, “bob@example.com”; “accept-terms”, “1”];;
– : (add_user, (string * string) list) result =
Error [(“accept-terms”, “error.expected.bool”)]
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
الف می خواهد bool، یعنی فقط true یا false ارزش ها می توانید مطمئن شوید که چک باکس های شما همیشه ارسال می شوند true در ارسال با تنظیم value=true.
رمزگشاهای ارزش سفارشی
همچنین می توانید داده های سفارشی را رمزگشایی کنید. به عنوان مثال فرض کنید فرم شما دارای ورودی هایی است که قرار است اعداد اعشاری باشند:
می توانید یک رمزگشای داده سفارشی بنویسید که می تواند یک عدد اعشاری را تجزیه کند:
# #require “decimal”;;
# let decimal s =
try Ok (Decimal.of_string s)
with Invalid_argument _ -> Error “error.expected.decimal”;;
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اکنون می توانیم از آن استفاده کنیم:
let+ height_m = required decimal “height-m”
…
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اضافه کردن قیود به مقادیر
می توانید محدودیت های بیشتری را به مقادیری که رمزگشایی می کنید اضافه کنید. به عنوان مثال، در اکثر فرم های ارسالی خالی بودن رشته ها منطقی نیست. بنابراین بیایید یک کمک کننده تعریف کنیم که رشته ها را به غیر خالی بودن محدود می کند:
let nonempty =
ensure “expected.nonempty” (( <> ) “”) required string
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اکنون میتوانیم تعریف فرم قبلی را با محدودیتهای قویتر برای رشتهها بنویسیم:
let add_user =
let open Dream_html.Form in
let+ name = nonempty “name”
and+ email = nonempty “email”
and+ accept_terms = optional bool “accept-terms” in
{
name;
email;
accept_terms = Option.value accept_terms ~default:false;
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اعتبار سنجی فرم ها در Dream handlers
در یک برنامه Dream، مدیریت فرم داخلی چیزی شبیه به این است:
(* POST /users *)
let post_users request =
match%lwt Dream.form request with
| `Ok [“accept-terms”, accept_terms; “email”, email; “name”, name] ->
(* …success… *)
| _ -> Dream.empty `Bad_Request
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اما با تواناییهای اعتبارسنجی فرم، میتوانیم کارهای بیشتری انجام دهیم:
(* POST /users *)
let post_users request =
match%lwt Dream_html.form add_user request with
| `Ok { name; email; accept_terms } ->
(* …success… *)
| `Invalid errors ->
Dream.json ~code:422 ( (* …turn the error list into a JSON object… *) )
| _ -> Dream.empty `Bad_Request
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
رمزگشایی مقادیر نوع متغیر
البته، انواع مختلف بخش بزرگی از برنامه نویسی در OCaml هستند، بنابراین ممکن است بخواهید فرم ارسالی را به مقداری از نوع نوع رمزگشایی کنید. به عنوان مثال،
type user =
| Logged_out
| Logged_in of { admin : bool }
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
شما می توانید یک فرم ارسالی داشته باشید که به شکل زیر باشد:
type: logged-out
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
یا:
type: logged-in
admin: true
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
و غیره
برای رمزگشایی این نوع ارسال، می توانید آن را به رمزگشا برای هر مورد تقسیم کنید، سپس آنها را با Dream_html.Form.( or )، به عنوان مثال:
let logged_out =
let+ _ = ensure “expected.type” (( = ) “logged-out”) required string “type” in
Logged_out
let logged_in =
let+ _ = ensure “expected.type” (( = ) “logged-in”) required string “type”
and+ admin = required bool “admin” in
Logged_in { admin }
let user = logged_out or logged_in
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
بیایید آن را امتحان کنیم:
# validate user [];;
– : (user, (string * string) list) result =
Error [(“admin”, “error.required”); (“type”, “error.required”)]
# validate user [“type”, “logged-out”];;
– : (user, (string * string) list) result = Ok Logged_out
# validate user [“type”, “logged-in”];;
– : (user, (string * string) list) result = Error [(“admin”, “error.required”)]
# validate user [“type”, “logged-in”; “admin”, “”];;
– : (user, (string * string) list) result =
Error [(“admin”, “error.expected.bool”)]
# validate user [“type”, “logged-in”; “admin”, “true”];;
– : (user, (string * string) list) result = Ok (Logged_in { admin = true })
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
همانطور که می بینید، رمزگشا می تواند هر دو مورد و تمام الزامات موجود در آن را انجام دهد.
رمزگشایی پرس و جوها به انواع سفارشی
عملکرد رمزگشایی نه تنها با «فرمها» بلکه با پرسوجوها نیز کار میکند /foo?a=1&b=2. البته، در اینجا ما از «فرمها» بهعنوان خلاصهنویسی استفاده میکنیم application/x-www-form-urlencoded دادههایی که با یک درخواست POST ارسال میشوند، اما در واقع یک فرم HTML است action=get داده های ورودی خود را به صورت a ارسال می کند پرس و جو، بخشی از URL، نه به عنوان داده های فرم. کمی گیج کننده است، اما نکته کلیدی که باید به خاطر بسپارید این است که Dream می تواند با هر دو کار کند، و همچنین dream-html.
در Dream، می توانید داده های پرس و جو را با استفاده از توابعی مانند دریافت کنید let a = Dream.query request “a”. اما اگر داده های پیچیده تری را از طریق پرس و جو ارسال می کنید، می توانید با استفاده از قابلیت رمزگشایی فرم بالا، آنها را به یک نوع سفارشی رمزگشایی کنید. به عنوان مثال فرض کنید می خواهید پارامترهای UTM را به یک نوع سفارشی رمزگشایی کنید:
type utm = {
source : string option;
medium : string option;
campaign : string option;
term : string option;
content : string option;
}
let utm =
let+ source = optional string “utm_source”
and+ medium = optional string “utm_medium”
and+ campaign = optional string “utm_campaign”
and+ term = optional string “utm_term”
and+ content = optional string “utm_content” in
{ source; medium; campaign; term; content }
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اکنون، میتوانید از این بسیار شبیه به ارسال فرم POST استفاده کنید:
let some_page request =
match Dream_html.query utm request with
| `Ok { source; medium; campaign; term; content } ->
(* …success… *)
| `Invalid errors -> (* …handle errors… *)
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
و نکته جالب این است که از آنجایی که آنها به معنای واقعی کلمه همان تعریف فرم هستند، می توانید با تغییرات بسیار اندک، بین انجام درخواست خود به داده های فرم POST یا پارامترهای پرس و جو GET سوئیچ کنید.
پس…
وای این خیلی بود. و من واقعاً به موارد استفاده پیشرفتهتر هم نپرداختهام. اما امیدواریم در این مرحله شما متقاعد شده باشید که فرم ها و پرس و جوها اکنون در Dream به راحتی قابل رسیدگی هستند. البته ممکن است واقعاً به این همه قدرت نیاز نداشته باشید. برای موارد استفاده ساده، احتمالاً می توانید از قابلیت های داخلی Dream خلاص شوید. اما برای برنامههای بزرگتر که شاید نیاز به استفاده از فرمهای زیادی داشته باشند، فکر میکنم میتواند مفید باشد.
توجه: Dream_html.form
و query
توابع کمکی منتشر نشده است. آنها در نسخه بعدی برای opam منتشر خواهند شد. همه چیز دیگر در حال حاضر در دسترس است!
من مدتها طرفدار قدرت فرمهای HTML و طبیعی بودن آنها برای ساخت صفحات وب بودهام که به کاربران اجازه میدهد دادهها را وارد کنند. اخیراً، من این فرصت را پیدا کردم که یک برنامه داخلی قدیمی را که در محل کار از آن استفاده می کنیم با استفاده از htmx و Scala's Play Framework بازسازی کنم. در این برنامه ما اغلب از فرم های HTML برای ارسال اطلاعات استفاده می کنیم. به عنوان مثال:
Play از رمزگشایی دادههای فرم HTML ارسالی به مقادیر انواع سفارشی و گزارش همه خطاهای اعتبارسنجی که ممکن است رخ داده باشد پشتیبانی بسیار خوبی دارد، که به من اجازه میدهد خطاها را مستقیماً در کنار خود فرمها با استفاده از Constraint Validation API ارائه کنم. در پست قبلی در مورد آن نوشتم.
اما در دنیای OCaml، وضعیت آنقدرها پیشرفته نبود. ما چند ابزار اساسی برای تجزیه داده های فرم در لیست جفت کلید-مقدار داشتیم:
اما اگر میخواهیم این فهرست را به یک نوع سفارشی رمزگشایی کنیم، به چیزی پیچیدهتر نیاز داریم، مانند شاید سازگار. با این حال، Conformist این مشکل را دارد که هر بار فقط یک خطا را گزارش می کند. اگر باید فرم ارسالی را رمزگشایی کنیم:
accept-terms: true
ما می خواهیم یک را ببینیم فهرست خطاهای اعتبارسنجی، مانند این:
name: required
email: required
اعتبار سنجی فرم dream-html
خلاصه، من به این نتیجه رسیدم که به تجربه اعتبارسنجی فرم ارگونومیک تری در OCaml نیاز داریم. و از آنجایی که من قبلاً بستهای را نگه میدارم که کمککنندههای ایمن را در بالای چارچوب وب Dream اضافه میکند، فکر میکردم که افزوده خوبی باشد. بیایید آن را برای چرخش در REPL در نظر بگیریم:
$ utop -require dream-html
# type add_user = {
name : string;
email : string;
accept_terms : bool;
};;
# let add_user =
let open Dream_html.Form in
let+ name = required string "name"
and+ email = required string "email"
and+ accept_terms = optional bool "accept-terms" in
{
name;
email;
accept_terms = Option.value accept_terms ~default:false;
};;
حالا ما یک ارزش داریم add_user : add_user Dream_html.Form.t
، که یک رمزگشا به نوع سفارشی ما است. بیایید آن را امتحان کنیم:
# Dream_html.Form.validate add_user [];;
- : (add_user, (string * string) list) result =
Error [("email", "error.required"); ("name", "error.required")]
ما لیستی از خطاهای اعتبار سنجی فرم را با نام فیلدها و کلیدهای پیام خطا دریافت می کنیم (این امکان بومی سازی برنامه را فراهم می کند).
بیایید یک رمزگشایی موفق را امتحان کنیم:
# Dream_html.Form.validate add_user ["name", "Bob"; "email", "bob@example.com"];;
- : (add_user, (string * string) list) result =
Ok {name = "Bob"; email = "bob@example.com"; accept_terms = false}
# Dream_html.Form.validate add_user ["name", "Bob"; "email", "bob@example.com"; "accept-terms", "true"];;
- : (add_user, (string * string) list) result =
Ok {name = "Bob"; email = "bob@example.com"; accept_terms = true}
بیایید یک خطای نوع را بررسی کنیم:
# Dream_html.Form.validate add_user ["name", "Bob"; "email", "bob@example.com"; "accept-terms", "1"];;
- : (add_user, (string * string) list) result =
Error [("accept-terms", "error.expected.bool")]
الف می خواهد bool
، یعنی فقط true
یا false
ارزش ها می توانید مطمئن شوید که چک باکس های شما همیشه ارسال می شوند true
در ارسال با تنظیم value=true
.
رمزگشاهای ارزش سفارشی
همچنین می توانید داده های سفارشی را رمزگشایی کنید. به عنوان مثال فرض کنید فرم شما دارای ورودی هایی است که قرار است اعداد اعشاری باشند:
می توانید یک رمزگشای داده سفارشی بنویسید که می تواند یک عدد اعشاری را تجزیه کند:
# #require "decimal";;
# let decimal s =
try Ok (Decimal.of_string s)
with Invalid_argument _ -> Error "error.expected.decimal";;
اکنون می توانیم از آن استفاده کنیم:
let+ height_m = required decimal "height-m"
...
اضافه کردن قیود به مقادیر
می توانید محدودیت های بیشتری را به مقادیری که رمزگشایی می کنید اضافه کنید. به عنوان مثال، در اکثر فرم های ارسالی خالی بودن رشته ها منطقی نیست. بنابراین بیایید یک کمک کننده تعریف کنیم که رشته ها را به غیر خالی بودن محدود می کند:
let nonempty =
ensure "expected.nonempty" (( <> ) "") required string
اکنون میتوانیم تعریف فرم قبلی را با محدودیتهای قویتر برای رشتهها بنویسیم:
let add_user =
let open Dream_html.Form in
let+ name = nonempty "name"
and+ email = nonempty "email"
and+ accept_terms = optional bool "accept-terms" in
{
name;
email;
accept_terms = Option.value accept_terms ~default:false;
}
اعتبار سنجی فرم ها در Dream handlers
در یک برنامه Dream، مدیریت فرم داخلی چیزی شبیه به این است:
(* POST /users *)
let post_users request =
match%lwt Dream.form request with
| `Ok ["accept-terms", accept_terms; "email", email; "name", name] ->
(* ...success... *)
| _ -> Dream.empty `Bad_Request
اما با تواناییهای اعتبارسنجی فرم، میتوانیم کارهای بیشتری انجام دهیم:
(* POST /users *)
let post_users request =
match%lwt Dream_html.form add_user request with
| `Ok { name; email; accept_terms } ->
(* ...success... *)
| `Invalid errors ->
Dream.json ~code:422 ( (* ...turn the error list into a JSON object... *) )
| _ -> Dream.empty `Bad_Request
رمزگشایی مقادیر نوع متغیر
البته، انواع مختلف بخش بزرگی از برنامه نویسی در OCaml هستند، بنابراین ممکن است بخواهید فرم ارسالی را به مقداری از نوع نوع رمزگشایی کنید. به عنوان مثال،
type user =
| Logged_out
| Logged_in of { admin : bool }
شما می توانید یک فرم ارسالی داشته باشید که به شکل زیر باشد:
type: logged-out
یا:
type: logged-in
admin: true
و غیره
برای رمزگشایی این نوع ارسال، می توانید آن را به رمزگشا برای هر مورد تقسیم کنید، سپس آنها را با Dream_html.Form.( or )
، به عنوان مثال:
let logged_out =
let+ _ = ensure "expected.type" (( = ) "logged-out") required string "type" in
Logged_out
let logged_in =
let+ _ = ensure "expected.type" (( = ) "logged-in") required string "type"
and+ admin = required bool "admin" in
Logged_in { admin }
let user = logged_out or logged_in
بیایید آن را امتحان کنیم:
# validate user [];;
- : (user, (string * string) list) result =
Error [("admin", "error.required"); ("type", "error.required")]
# validate user ["type", "logged-out"];;
- : (user, (string * string) list) result = Ok Logged_out
# validate user ["type", "logged-in"];;
- : (user, (string * string) list) result = Error [("admin", "error.required")]
# validate user ["type", "logged-in"; "admin", ""];;
- : (user, (string * string) list) result =
Error [("admin", "error.expected.bool")]
# validate user ["type", "logged-in"; "admin", "true"];;
- : (user, (string * string) list) result = Ok (Logged_in { admin = true })
همانطور که می بینید، رمزگشا می تواند هر دو مورد و تمام الزامات موجود در آن را انجام دهد.
رمزگشایی پرس و جوها به انواع سفارشی
عملکرد رمزگشایی نه تنها با «فرمها» بلکه با پرسوجوها نیز کار میکند /foo?a=1&b=2
. البته، در اینجا ما از «فرمها» بهعنوان خلاصهنویسی استفاده میکنیم application/x-www-form-urlencoded
دادههایی که با یک درخواست POST ارسال میشوند، اما در واقع یک فرم HTML است action=get
داده های ورودی خود را به صورت a ارسال می کند پرس و جو، بخشی از URL، نه به عنوان داده های فرم. کمی گیج کننده است، اما نکته کلیدی که باید به خاطر بسپارید این است که Dream می تواند با هر دو کار کند، و همچنین dream-html.
در Dream، می توانید داده های پرس و جو را با استفاده از توابعی مانند دریافت کنید let a = Dream.query request "a"
. اما اگر داده های پیچیده تری را از طریق پرس و جو ارسال می کنید، می توانید با استفاده از قابلیت رمزگشایی فرم بالا، آنها را به یک نوع سفارشی رمزگشایی کنید. به عنوان مثال فرض کنید می خواهید پارامترهای UTM را به یک نوع سفارشی رمزگشایی کنید:
type utm = {
source : string option;
medium : string option;
campaign : string option;
term : string option;
content : string option;
}
let utm =
let+ source = optional string "utm_source"
and+ medium = optional string "utm_medium"
and+ campaign = optional string "utm_campaign"
and+ term = optional string "utm_term"
and+ content = optional string "utm_content" in
{ source; medium; campaign; term; content }
اکنون، میتوانید از این بسیار شبیه به ارسال فرم POST استفاده کنید:
let some_page request =
match Dream_html.query utm request with
| `Ok { source; medium; campaign; term; content } ->
(* ...success... *)
| `Invalid errors -> (* ...handle errors... *)
و نکته جالب این است که از آنجایی که آنها به معنای واقعی کلمه همان تعریف فرم هستند، می توانید با تغییرات بسیار اندک، بین انجام درخواست خود به داده های فرم POST یا پارامترهای پرس و جو GET سوئیچ کنید.
پس…
وای این خیلی بود. و من واقعاً به موارد استفاده پیشرفتهتر هم نپرداختهام. اما امیدواریم در این مرحله شما متقاعد شده باشید که فرم ها و پرس و جوها اکنون در Dream به راحتی قابل رسیدگی هستند. البته ممکن است واقعاً به این همه قدرت نیاز نداشته باشید. برای موارد استفاده ساده، احتمالاً می توانید از قابلیت های داخلی Dream خلاص شوید. اما برای برنامههای بزرگتر که شاید نیاز به استفاده از فرمهای زیادی داشته باشند، فکر میکنم میتواند مفید باشد.