pub mod fields;
pub mod options;
mod types;
use crate::__private_types::IvoInputStruct;
use crate::schema::options::types::{IgnoreOptionConfig, IgnoreUpdateOptionConfig};
use crate::schema::{
fields::{
base::{
BuildableFieldConfig, BuildableTimestampConfig, FieldConfig, FieldType,
InternalFieldConfig, TimestampConfigBuilder,
},
TimestampConfig,
},
options::{
base::{SchemaOptions, SchemaOptionsBuilder},
types::{OnSuccessConfig, PostValidationConfig, RequiredOptionConfig},
BuildableSchemaOptions,
},
};
use crate::types::internal::{IvoErrorSanitizer, IvoStruct};
use crate::types::InternalFieldConfigs;
use crate::Model;
use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::marker::PhantomData;
pub use types::FieldValue;
use types::{No, Yes};
const STYLE_COLOR_RED: &str = "\x1b[31m";
const STYLE_FONT_BOLD: &str = "\x1b[1m";
const STYLE_RESET: &str = "\x1b[0m";
impl<
I: IvoInputStruct<CtxOptions, ErrorSanitizer>,
O: IvoStruct,
CtxOptions: Clone + Sync + Send,
Timestamp: Clone + Debug + Send + Sync + 'static,
ErrorSanitizer: IvoErrorSanitizer<CtxOptions>,
> Model<I, O, CtxOptions, Timestamp, ErrorSanitizer>
{
#[track_caller]
pub fn new<FieldMaker, OptionMaker, BuildableOptions, WithTimestamps>(
f: FieldMaker,
o: OptionMaker,
) -> Self
where
FieldMaker: Fn(
FieldBuilder<I, O, CtxOptions, Timestamp, ErrorSanitizer>,
)
-> FieldBuilder<I, O, CtxOptions, Timestamp, ErrorSanitizer, WithTimestamps>,
OptionMaker: Fn(SchemaOptionsBuilder<I, O, CtxOptions, ErrorSanitizer>) -> BuildableOptions,
BuildableOptions: BuildableSchemaOptions<I, O, CtxOptions, ErrorSanitizer>,
{
let fields = f(FieldBuilder::new());
let input_field_names = I::ivo_internal_field_names();
let output_field_names = O::ivo_internal_field_names();
let (field_configs, alias_to_virtual_map) = Self::make_field_configs(
fields.configs,
&fields.timestamp_config,
&input_field_names,
&output_field_names,
);
let options = Self::make_options(
o(SchemaOptions::new()).build(),
&field_configs,
&alias_to_virtual_map,
&input_field_names,
&output_field_names,
);
Self {
field_configs,
options,
timestamp_configs: fields.timestamp_config,
}
}
#[expect(clippy::type_complexity)]
#[track_caller]
fn make_field_configs(
config_tuples: Vec<(
&'static str,
InternalFieldConfig<I, O, CtxOptions, ErrorSanitizer>,
)>,
timestamp_configs: &Option<TimestampConfig<Timestamp>>,
input_field_names: &HashSet<String>,
output_field_names: &HashSet<String>,
) -> (
InternalFieldConfigs<I, O, CtxOptions, ErrorSanitizer>,
HashMap<&'static str, &'static str>,
) {
let input_struct_name = format!(
"{STYLE_FONT_BOLD}{}{STYLE_RESET}{STYLE_COLOR_RED}",
I::ivo_internal_name()
);
let output_struct_name = format!(
"{STYLE_FONT_BOLD}{}{STYLE_RESET}{STYLE_COLOR_RED}",
O::ivo_internal_name()
);
if let Some(TimestampConfig {
created_at: Some(name),
..
}) = timestamp_configs
{
let name_owned = name.to_string();
if !output_field_names.contains(&name_owned) {
panic!(
"\n{STYLE_COLOR_RED}[{name}]: is a purely output field. It must be present on {output_struct_name}{STYLE_RESET}\n"
);
}
if input_field_names.contains(&name_owned) {
panic!(
"\n{STYLE_COLOR_RED}[{name}]: is a purely output field. It should not be present on {input_struct_name}{STYLE_RESET}\n"
);
}
}
if let Some(TimestampConfig {
updated_at: Some(name),
..
}) = timestamp_configs
{
let name_owned = name.to_string();
if !output_field_names.contains(&name_owned) {
panic!(
"\n{STYLE_COLOR_RED}[{name}]: is a purely output field. It must be present on {output_struct_name}{STYLE_RESET}\n"
);
}
if input_field_names.contains(&name_owned) {
panic!(
"\n{STYLE_COLOR_RED}[{name}]: is a purely output field. It should not be present on {input_struct_name}{STYLE_RESET}\n"
);
}
}
let mut constant_field_names = HashSet::new();
let mut dependent_field_to_parent_fields = HashMap::new();
let mut field_names = HashSet::new();
let mut alias_to_virtual = HashMap::new();
let mut dependent_configs = Vec::new();
for (field_name, config) in config_tuples.iter() {
if field_names.contains(field_name) {
panic!("\n{STYLE_COLOR_RED}[{field_name}]: occurs more than once, please remove duplicates{STYLE_RESET}\n");
}
if let Some(TimestampConfig {
created_at: Some(name),
..
}) = timestamp_configs
{
if field_name == name {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: is not a valid field name. It is the creation timestamp on {output_struct_name}{STYLE_RESET}\n"
);
}
}
if let Some(TimestampConfig {
updated_at: Some(name),
..
}) = timestamp_configs
{
if field_name == name {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: is not a valid field name. It is the update timestamp on {output_struct_name}{STYLE_RESET}\n"
);
}
}
field_names.insert(field_name);
match config {
InternalFieldConfig {
field_type: FieldType::Constant,
..
} => {
if !output_field_names.contains(*field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: is a purely output field. It must be present on {output_struct_name}{STYLE_RESET}\n"
);
}
if input_field_names.contains(*field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: is a purely output field. It should not be present on {input_struct_name}{STYLE_RESET}\n"
);
}
constant_field_names.insert(field_name);
continue;
}
InternalFieldConfig {
field_type: FieldType::Virtual,
alias,
..
} => {
if let Some(alias) = alias {
if field_name == alias {
panic!("\n{STYLE_COLOR_RED}[{field_name}]: virtual alias name must be different from field name{STYLE_RESET}\n");
}
if let Some(other_field) = alias_to_virtual.get(alias) {
panic!("\n{STYLE_COLOR_RED}[{field_name}]: \"{alias}\" is already the alias of \"{other_field}\"{STYLE_RESET}\n");
}
if let Some(TimestampConfig {
created_at: Some(name),
..
}) = timestamp_configs
{
if alias == name {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: \"{alias}\" is not a valid alias. It is the creation timestamp on {output_struct_name}{STYLE_RESET}\n"
);
}
}
if let Some(TimestampConfig {
updated_at: Some(name),
..
}) = timestamp_configs
{
if alias == name {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: \"{alias}\" is not a valid alias. It is the update timestamp on {output_struct_name}{STYLE_RESET}\n"
);
}
}
for (name, config) in config_tuples.iter() {
if name != alias {
continue;
}
if let Some(ref depends_on) = config.depends_on {
if !depends_on.iter().any(|parent| parent == field_name) {
panic!("\n{STYLE_COLOR_RED}[{field_name}]: \"{alias}\" is not a valid alias for field because \"{alias}\" does not depend on \"{field_name}\"{STYLE_RESET}\n");
}
continue;
}
panic!("\n{STYLE_COLOR_RED}[{field_name}]: \"{alias}\" is not a valid alias for field because it is not a dependent field{STYLE_RESET}\n");
}
if !input_field_names.contains(&alias.to_string()) {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: is an input field. Hence, \"{alias}\" must be present on {input_struct_name}{STYLE_RESET}\n");
}
if input_field_names.contains(*field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: has an alias. Only its alias must be present on {input_struct_name}{STYLE_RESET}\n");
}
alias_to_virtual.insert(*alias, *field_name);
continue;
}
if !input_field_names.contains(*field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: is an input field. It must be present on {input_struct_name}{STYLE_RESET}\n");
}
let mut has_sufficent_dependencies = false;
for (_, config) in config_tuples.iter() {
if let Some(ref depends_on) = config.depends_on {
if depends_on.iter().any(|parent| parent == field_name) {
has_sufficent_dependencies = true;
break;
}
continue;
}
}
if !has_sufficent_dependencies {
panic!("\n{STYLE_COLOR_RED}[{field_name}]: virtual fields are expected to have at least one dependency, but found none{STYLE_RESET}\n");
}
continue;
}
_ => (),
}
if !output_field_names.contains(*field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: is an output field. It must be present on {output_struct_name}{STYLE_RESET}\n");
}
match config {
InternalFieldConfig {
field_type: FieldType::Dependent,
depends_on,
..
} => {
dependent_configs.push((field_name, config));
dependent_field_to_parent_fields
.insert(*field_name, depends_on.as_ref().unwrap());
}
InternalFieldConfig {
field_type: FieldType::Lax | FieldType::Required,
..
} if !input_field_names.contains(*field_name) => {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: is an input field. It must be present on {input_struct_name}{STYLE_RESET}\n");
}
_ => (),
}
}
for (field_name, InternalFieldConfig { depends_on, .. }) in dependent_configs {
let parent_fields = depends_on.as_ref().unwrap();
if parent_fields.is_empty() {
panic!("\n{STYLE_COLOR_RED}[{field_name}]: must depend on at least one lax, required, virtual or other dependent field on your schema{STYLE_RESET}\n");
}
let mut parent_fields_provided = HashSet::new();
for parent_field in parent_fields {
if let Some(TimestampConfig {
created_at: Some(name),
..
}) = timestamp_configs
{
if parent_field == name {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: cannot depend on \"{parent_field}\" because it is the creation timestamp on {output_struct_name}{STYLE_RESET}\n"
);
}
}
if let Some(TimestampConfig {
updated_at: Some(name),
..
}) = timestamp_configs
{
if parent_field == name {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: cannot depend on \"{parent_field}\" because it is the update timestamp on {output_struct_name}{STYLE_RESET}\n"
);
}
}
if !field_names.contains(&parent_field) {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: cannot depend on \"{parent_field}\" because it is not a field on your schema{STYLE_RESET}\n"
);
}
if parent_field == field_name {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: cannot depend on itself{STYLE_RESET}\n"
);
}
if parent_fields_provided.contains(parent_field) {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: \"{parent_field}\" has been provided as a parent field multiple times. remove all duplicates to proceed{STYLE_RESET}\n"
);
}
if constant_field_names.contains(&parent_field) {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: cannot depend on \"{parent_field}\" because it is a constant{STYLE_RESET}\n"
);
}
parent_fields_provided.insert(parent_field);
}
if let Some((parent_field, redundant_field, depth)) =
Self::get_redundant_dependency(parent_fields, &dependent_field_to_parent_fields)
{
if depth == 0 {
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: should not depend on \"{parent_field}\" and \"{redundant_field}\" because \"{parent_field}\" depends on \"{redundant_field}\"{STYLE_RESET}\n"
);
}
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: should not depend on \"{parent_field}\" and \"{redundant_field}\" because \"{parent_field}\" indirectly depends on \"{redundant_field}\"{STYLE_RESET}\n"
);
}
if let Some(chain) = Self::get_circular_dependency_chain(
field_name,
parent_fields,
&dependent_field_to_parent_fields,
) {
let chain = chain.join(" <-> ");
panic!(
"\n{STYLE_COLOR_RED}[{field_name}]: circular dependency identified between \"{chain}\"{STYLE_RESET}\n"
);
}
}
let mut field_configs = HashMap::new();
for (field_name, config) in config_tuples {
field_configs.insert(field_name, config);
}
(field_configs, alias_to_virtual)
}
#[track_caller]
fn make_options(
options: SchemaOptions<I, O, CtxOptions, ErrorSanitizer>,
field_configs: &InternalFieldConfigs<I, O, CtxOptions, ErrorSanitizer>,
alias_to_virtual_map: &HashMap<&'static str, &'static str>,
input_field_names: &HashSet<String>,
output_field_names: &HashSet<String>,
) -> SchemaOptions<I, O, CtxOptions, ErrorSanitizer> {
if let Some(ref configs) = options.ignore {
let option_name = "options.ignore";
let field_type_not_allowed_error =
"only lax and virtual fields can belong to grouped ignore configs;";
for IgnoreOptionConfig { fields, .. } in configs {
if fields.len() < 2 {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: grouped ignore expects at least 2 fields {STYLE_RESET}\n"
);
}
let mut field_names = HashSet::new();
for field_name in fields {
if field_names.contains(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: remove duplicates of \"{field_name}\" in your grouped ignore config{STYLE_RESET}\n"
);
}
if let Some(virtual_field) = alias_to_virtual_map.get(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: \"{field_name}\" is an alias; use \"{virtual_field}\" instead{STYLE_RESET}\n"
);
};
let owned_field_name = field_name.to_string();
if input_field_names.contains(&owned_field_name) {
if matches!(
field_configs.get(field_name),
Some(FieldConfig {
field_type: FieldType::Required,
..
})
) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: {field_type_not_allowed_error} remove \"{field_name}\"{STYLE_RESET}\n"
);
}
field_names.insert(field_name);
continue;
};
if output_field_names.contains(&owned_field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: {field_type_not_allowed_error} remove \"{field_name}\"{STYLE_RESET}\n"
);
} else if !field_configs.contains_key(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: \"{field_name}\" does not exist on your schema{STYLE_RESET}\n"
);
};
}
}
}
if let Some(ref configs) = options.ignore_update {
let option_name = "options.ignore_update";
let field_type_not_allowed_error =
"only lax, required and virtual fields can belong to grouped ignore update configs;";
for IgnoreUpdateOptionConfig { fields, .. } in configs {
if !fields.is_empty() && fields.len() < 2 {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: grouped ignore update expects either zero (0) fields or at least 2 fields {STYLE_RESET}\n"
);
}
let mut field_names = HashSet::new();
for field_name in fields {
if field_names.contains(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: remove duplicates of \"{field_name}\" in your grouped ignore update config{STYLE_RESET}\n"
);
}
if let Some(virtual_field) = alias_to_virtual_map.get(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: \"{field_name}\" is an alias; use \"{virtual_field}\" instead{STYLE_RESET}\n"
);
};
let owned_field_name = field_name.to_string();
if input_field_names.contains(&owned_field_name) {
field_names.insert(field_name);
continue;
};
if output_field_names.contains(&owned_field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: {field_type_not_allowed_error} remove \"{field_name}\"{STYLE_RESET}\n"
);
} else if !field_configs.contains_key(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: \"{field_name}\" does not exist on your schema{STYLE_RESET}\n"
);
};
}
}
}
if let Some(ref configs) = options.on_success_fns {
let option_name = "options.on_success";
for OnSuccessConfig { fields, .. } in configs {
let mut field_names = HashSet::new();
for field_name in fields {
if field_names.contains(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: remove duplicates of \"{field_name}\" in grouped on_success config{STYLE_RESET}\n"
);
}
if let Some(virtual_field) = alias_to_virtual_map.get(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: \"{field_name}\" is an alias; use \"{virtual_field}\" instead{STYLE_RESET}\n"
);
};
let owned_field_name = field_name.to_string();
if input_field_names.contains(&owned_field_name) {
field_names.insert(field_name);
continue;
};
if field_configs.get(field_name).is_some() {
field_names.insert(field_name);
continue;
};
if output_field_names.contains(&owned_field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: timestamps are not allowed in on_success. remove \"{field_name}\"{STYLE_RESET}\n"
);
}
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: \"{field_name}\" does not exist on your schema{STYLE_RESET}\n"
);
}
}
}
if let Some(ref configs) = options.post_validate {
let option_name = "options.post_validate";
let field_type_not_allowed_error =
"only lax, required and virtual fields can be post-validated;";
for PostValidationConfig { fields, .. } in configs {
if fields.len() < 2 {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: post-validation expects at least 2 fields {STYLE_RESET}\n"
);
}
let mut field_names = HashSet::new();
for field_name in fields {
if field_names.contains(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: remove duplicates of \"{field_name}\" in your post-validation config{STYLE_RESET}\n"
);
}
if let Some(virtual_field) = alias_to_virtual_map.get(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: \"{field_name}\" is an alias; use \"{virtual_field}\" instead{STYLE_RESET}\n"
);
};
let owned_field_name = field_name.to_string();
if input_field_names.contains(&owned_field_name) {
field_names.insert(field_name);
continue;
};
if output_field_names.contains(&owned_field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: {field_type_not_allowed_error} remove \"{field_name}\"{STYLE_RESET}\n"
);
} else if !field_configs.contains_key(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: \"{field_name}\" does not exist on your schema{STYLE_RESET}\n"
);
};
}
}
}
if let Some(ref configs) = options.required {
let option_name = "options.required";
let field_type_not_allowed_error =
"only lax and virtual fields can belong to grouped required configs;";
for RequiredOptionConfig { fields, .. } in configs {
if fields.len() < 2 {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: grouped required expects at least 2 fields {STYLE_RESET}\n"
);
}
let mut field_names = HashSet::new();
for field_name in fields {
if field_names.contains(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: remove duplicates of \"{field_name}\" in your grouped required config{STYLE_RESET}\n"
);
}
if let Some(virtual_field) = alias_to_virtual_map.get(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: \"{field_name}\" is an alias; use \"{virtual_field}\" instead{STYLE_RESET}\n"
);
};
let owned_field_name = field_name.to_string();
if input_field_names.contains(&owned_field_name) {
if matches!(
field_configs.get(field_name),
Some(FieldConfig {
field_type: FieldType::Required,
..
})
) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: {field_type_not_allowed_error} remove \"{field_name}\"{STYLE_RESET}\n"
);
}
field_names.insert(field_name);
continue;
};
if output_field_names.contains(&owned_field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: {field_type_not_allowed_error} remove \"{field_name}\"{STYLE_RESET}\n"
);
} else if !field_configs.contains_key(field_name) {
panic!(
"\n{STYLE_COLOR_RED}[{option_name}]: \"{field_name}\" does not exist on your schema{STYLE_RESET}\n"
);
};
}
}
}
options
}
fn get_redundant_dependency<'r>(
parent_fields: &Vec<&'r str>,
dependent_field_to_parent_fields: &HashMap<&str, &Vec<&'r str>>,
) -> Option<(&'r str, &'r str, i32)> {
for parent_name in parent_fields.iter() {
for field_name in parent_fields.iter() {
if field_name == parent_name {
continue;
}
if let Some((redundant_field, depth)) =
Self::is_field_redundantly_dependent_on_parent(
field_name,
parent_name,
dependent_field_to_parent_fields,
0,
)
{
return Some((field_name, redundant_field, depth));
}
}
}
None
}
fn is_field_redundantly_dependent_on_parent<'r>(
field_name: &'r str,
parent_name: &'r str,
dependent_field_to_parent_fields: &HashMap<&str, &Vec<&'r str>>,
depth: i32,
) -> Option<(&'r str, i32)> {
if let Some(parent_deps) = dependent_field_to_parent_fields.get(field_name) {
if parent_deps.contains(&parent_name) {
return Some((parent_name, depth));
}
for field_name in parent_deps.iter() {
let r = Self::is_field_redundantly_dependent_on_parent(
field_name,
parent_name,
dependent_field_to_parent_fields,
depth + 1,
);
if r.is_some() {
return r;
}
}
return None;
}
None
}
fn get_circular_dependency_chain<'c>(
dependent_field_name: &'c str,
parent_fields: &Vec<&'c str>,
dependent_field_to_parent_fields: &HashMap<&str, &Vec<&'c str>>,
) -> Option<Vec<&'c str>> {
for parent_name in parent_fields.iter() {
if let Some(chain) = Self::is_field_circularly_dependent_on_parent(
dependent_field_name,
parent_name,
dependent_field_to_parent_fields,
vec![dependent_field_name],
) {
return Some(chain);
}
}
None
}
fn is_field_circularly_dependent_on_parent<'c>(
dependent_field_name: &str,
parent_name: &'c str,
dependent_field_to_parent_fields: &HashMap<&str, &Vec<&'c str>>,
mut visited_nodes: Vec<&'c str>,
) -> Option<Vec<&'c str>> {
if let Some(parent_deps) = dependent_field_to_parent_fields.get(parent_name) {
visited_nodes.push(parent_name);
if parent_deps.contains(&dependent_field_name) {
return Some(visited_nodes);
}
for field_name in parent_deps.iter() {
let r = Self::is_field_circularly_dependent_on_parent(
dependent_field_name,
field_name,
dependent_field_to_parent_fields,
visited_nodes.clone(),
);
if r.is_some() {
return r;
}
}
return None;
}
None
}
}
pub struct FieldBuilder<
I: IvoStruct,
O: IvoStruct,
CtxOptions,
T: FieldValue,
ErrorSanitizer: IvoErrorSanitizer<CtxOptions>,
WithTimestamps = No,
> {
#[expect(clippy::type_complexity)]
configs: Vec<(
&'static str,
InternalFieldConfig<I, O, CtxOptions, ErrorSanitizer>,
)>,
timestamp_config: Option<TimestampConfig<T>>,
_t: PhantomData<WithTimestamps>,
}
impl<
I: IvoStruct,
O: IvoStruct,
CtxOptions,
Timestamp: Clone + Debug + Send + Sync + 'static,
ErrorSanitizer: IvoErrorSanitizer<CtxOptions>,
> FieldBuilder<I, O, CtxOptions, Timestamp, ErrorSanitizer>
{
fn new() -> Self {
Self {
configs: Vec::new(),
timestamp_config: None,
_t: PhantomData,
}
}
pub fn field<Config>(mut self, name: &'static str, config: Config) -> Self
where
Config: BuildableFieldConfig<I, O, CtxOptions, ErrorSanitizer>,
{
self.configs.push((name, config.build()));
self
}
}
impl<
I: IvoStruct,
O: IvoStruct,
CtxOptions,
Timestamp: Clone + Debug + Send + Sync + 'static,
ErrorSanitizer: IvoErrorSanitizer<CtxOptions>,
> FieldBuilder<I, O, CtxOptions, Timestamp, ErrorSanitizer>
{
pub fn timestamps<BuildableConfig, R>(
self,
t: R,
) -> FieldBuilder<I, O, CtxOptions, Timestamp, ErrorSanitizer, Yes>
where
BuildableConfig: BuildableTimestampConfig<Timestamp>,
R: Fn(TimestampConfigBuilder<Timestamp>) -> BuildableConfig,
{
FieldBuilder {
configs: self.configs,
timestamp_config: Some(t(TimestampConfigBuilder::new()).build()),
_t: PhantomData,
}
}
}