Skip to content

Commit f9d7c43

Browse files
authored
feat: add simple AsFormField impl for ForeignKeys (#335)
* feat: add support for file fields in forms * clippy fixes * address review comment, tiny fixes * feat: add simple AsFormField impl for ForeignKeys * add more tests
1 parent 24156a8 commit f9d7c43

File tree

2 files changed

+106
-4
lines changed

2 files changed

+106
-4
lines changed

cot/src/form/fields.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ pub use files::{FileField, FileFieldOptions, InMemoryUploadedFile};
1212
use crate::auth::PasswordHash;
1313
use crate::common_types::{Email, Password};
1414
#[cfg(feature = "db")]
15-
use crate::db::Auto;
16-
#[cfg(feature = "db")]
17-
use crate::db::LimitedString;
15+
use crate::db::{Auto, ForeignKey, LimitedString, Model};
1816
use crate::form::{
1917
AsFormField, FormField, FormFieldOptions, FormFieldValidationError, FormFieldValue,
2018
FormFieldValueError,
@@ -592,6 +590,40 @@ impl<T: AsFormField> AsFormField for Auto<T> {
592590
}
593591
}
594592

593+
#[cfg(feature = "db")]
594+
impl<T> AsFormField for ForeignKey<T>
595+
where
596+
T: Model,
597+
<T as Model>::PrimaryKey: AsFormField,
598+
{
599+
type Type = <<T as Model>::PrimaryKey as AsFormField>::Type;
600+
601+
fn new_field(
602+
options: FormFieldOptions,
603+
custom_options: <Self::Type as FormField>::CustomOptions,
604+
) -> Self::Type {
605+
Self::Type::with_options(options, custom_options)
606+
}
607+
608+
fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError>
609+
where
610+
Self: Sized,
611+
{
612+
let value = <T as Model>::PrimaryKey::clean_value(field);
613+
match value {
614+
Ok(value) => Ok(ForeignKey::PrimaryKey(value)),
615+
Err(error) => Err(error),
616+
}
617+
}
618+
619+
fn to_field_value(&self) -> String {
620+
match self {
621+
ForeignKey::PrimaryKey(primary_key) => primary_key.to_field_value(),
622+
ForeignKey::Model(model) => model.primary_key().to_field_value(),
623+
}
624+
}
625+
}
626+
595627
fn check_required<T: FormField>(field: &T) -> Result<&str, FormFieldValidationError> {
596628
if let Some(value) = field.value() {
597629
if value.is_empty() {

cot/tests/form.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
use cot::db::{Auto, ForeignKey};
12
use cot::form::{
2-
Form, FormContext, FormErrorTarget, FormField, FormFieldValidationError, FormResult,
3+
AsFormField, Form, FormContext, FormErrorTarget, FormField, FormFieldValidationError,
4+
FormResult,
35
};
46
use cot::test::TestRequestBuilder;
7+
use cot_macros::model;
58

69
#[derive(Debug, Form)]
710
struct MyForm {
@@ -88,3 +91,70 @@ async fn values_persist_on_form_errors() {
8891
_ => panic!("Expected a validation error"),
8992
}
9093
}
94+
95+
#[cot::test]
96+
async fn foreign_key_field() {
97+
#[model]
98+
struct TestModel {
99+
#[model(primary_key)]
100+
name: String,
101+
}
102+
103+
#[derive(Form)]
104+
struct TestModelForm {
105+
test_field: ForeignKey<TestModel>,
106+
}
107+
108+
// test field rendering
109+
let context = TestModelForm::build_context(&mut TestRequestBuilder::get("/").build())
110+
.await
111+
.unwrap();
112+
let form_rendered = context.to_string();
113+
assert!(form_rendered.contains("test_field"));
114+
assert!(form_rendered.contains("type=\"text\""));
115+
116+
// test form data
117+
let mut request = TestRequestBuilder::post("/")
118+
.form_data(&[("test_field", "Alice")])
119+
.build();
120+
let form = TestModelForm::from_request(&mut request).await;
121+
match form {
122+
Ok(FormResult::Ok(instance)) => {
123+
assert_eq!(instance.test_field.primary_key(), "Alice");
124+
}
125+
_ => panic!("Expected a valid form"),
126+
}
127+
128+
// test re-raising validation errors
129+
let mut request = TestRequestBuilder::post("/")
130+
.form_data(&[("test_field", "")])
131+
.build();
132+
let form = TestModelForm::from_request(&mut request).await;
133+
match form {
134+
Ok(FormResult::ValidationError(context)) => {
135+
assert_eq!(
136+
context.errors_for(FormErrorTarget::Field("test_field")),
137+
&[FormFieldValidationError::Required]
138+
);
139+
}
140+
_ => panic!("Expected a validation error"),
141+
}
142+
}
143+
144+
#[cot::test]
145+
async fn foreign_key_field_to_field_value() {
146+
#[model]
147+
struct TestModel {
148+
#[model(primary_key)]
149+
id: Auto<i32>,
150+
}
151+
152+
let field_value = ForeignKey::<TestModel>::Model(Box::new(TestModel {
153+
id: Auto::fixed(123),
154+
}))
155+
.to_field_value();
156+
assert_eq!(field_value, "123");
157+
158+
let field_value = ForeignKey::<TestModel>::PrimaryKey(Auto::fixed(456)).to_field_value();
159+
assert_eq!(field_value, "456");
160+
}

0 commit comments

Comments
 (0)