Getting a defaults record from Schema - alternative for optional #1019
Replies: 2 comments 6 replies
-
Hi Tom, thanks for creating this discussion. Is it possible to provide one of your larger schemas to better see and understand the pain points of our current API design? |
Beta Was this translation helpful? Give feedback.
-
this my "schema.ts" file: interface PricesPerCountryMap {
[key: string]: { [key: string]: number };
}
const productPricesMap: PricesPerCountryMap = {
'': {
default: 99.0,
},
'SAMPLE1': {
default: 5.55,
},
'SAMPLE2': {
default: 7.77,
},
};
const deliveryCostsMap: PricesPerCountryMap = {
'': {
default: 99.0,
},
DIGITAL: {
default: 0.0,
},
DHL: {
DE: 4.44,
default: 8.88,
},
DPD: {
DE: 3.33,
default: 6.66,
},
};
const paymentCostsMap: PricesPerCountryMap = {
'': {
default: 99.0,
},
SEPA: {
default: 0.0,
},
PAYPAL: {
default: 2.22,
CH: 4.44,
},
};
const finalizeOrderData = (values: OrderDataOutput) => {
// product_price based on product_id
try {
values.product_price =
productPricesMap[values.product_id][values.country] ?? productPricesMap[values.product_id].default ?? productPricesMap[''].default;
} catch {
values.product_price = productPricesMap[''].default;
}
// delivery_costs based on delivery_option
try {
values.delivery_costs =
deliveryCostsMap[values.delivery_option][values.country] ?? deliveryCostsMap[values.delivery_option].default ?? deliveryCostsMap[''].default;
} catch {
values.delivery_costs = deliveryCostsMap[''].default;
}
// payment_price based on payment_option
try {
values.payment_costs =
paymentCostsMap[values.payment_option][values.country] ?? paymentCostsMap[values.payment_option].default ?? paymentCostsMap[''].default;
} catch {
values.payment_costs = paymentCostsMap[''].default;
}
// calculate the order_sum if values provided
try {
values.order_sum = roundToPrecision(values.product_price + values.delivery_costs + values.payment_costs, 2);
} catch {
values.order_sum = 0;
}
// the final record
return values;
};
const OrderDataSchema = vbot.pipe(
vbot.object({
product_id: vbot.config(
vbot.pipe(vbot.optional(vbot.string(), ''), vbot.trim(), vbot.maxLength(40), vbot.toUpperCase(), vbot.regex(/^SAMPLE[12]$/)),
{
message: 'Produkt-Id fehlt oder ist ungültig!',
}
),
product_option: vbot.config(
vbot.pipe(vbot.optional(vbot.string(), ''), vbot.trim(), vbot.minLength(4), vbot.toUpperCase(), vbot.regex(/^(S|M|L|XL)$/)),
{
message: 'Produkt-Option fehlt oder ist ungültig!',
}
),
product_price: vbot.config(vbot.pipe(vbot.optional(vbot.number(), 0), vbot.minValue(0), vbot.maxValue(999)), {
message: 'Produkt-Preis fehlt oder ist ungültig!',
}),
delivery_option: vbot.config(
vbot.pipe(vbot.optional(vbot.string(), ''), vbot.trim(), vbot.minLength(3), vbot.toUpperCase(), vbot.regex(/^(DIGITAL|DHL|DPD)$/)),
{
message: 'Versandart fehlt oder ist ungültig!',
}
),
delivery_costs: vbot.config(vbot.pipe(vbot.optional(vbot.number(), 0), vbot.minValue(0), vbot.maxValue(999)), {
message: 'Versandkosten fehlen oder sind ungültig!',
}),
payment_option: vbot.config(
vbot.pipe(vbot.optional(vbot.string(), ''), vbot.trim(), vbot.minLength(4), vbot.toUpperCase(), vbot.regex(/^(SEPA|PAYPAL)$/)),
{
message: 'Zahlungsart fehlt oder ist ungültig!',
}
),
payment_costs: vbot.config(vbot.pipe(vbot.optional(vbot.number(), 0), vbot.minValue(0), vbot.maxValue(999)), {
message: 'Zahlungsgebühren fehlen oder sind ungültig!',
}),
order_sum: vbot.config(vbot.pipe(vbot.optional(vbot.number(), 0), vbot.minValue(0), vbot.maxValue(999)), {
message: 'Bestellsumme fehlt oder ist ungültig!',
}),
lastname: vbot.config(vbot.pipe(vbot.optional(vbot.string(), ''), vbot.trim(), vbot.minLength(3), vbot.maxLength(40)), {
message: 'Nachname fehlt oder ist ungültig!',
}),
address: vbot.config(vbot.pipe(vbot.optional(vbot.string(), ''), vbot.trim(), vbot.minLength(5), vbot.maxLength(40)), {
message: 'Adresse fehlt oder ist ungültig!',
}),
address2: vbot.config(
vbot.pipe(
vbot.optional(vbot.string(), ''),
vbot.trim(),
vbot.maxLength(40),
vbot.check((input) => input === '' || input.length >= 5)
),
{
message: 'Zusatz für Adresse fehlt oder ist ungültig!',
}
),
zipcode: vbot.config(vbot.pipe(vbot.optional(vbot.string(), ''), vbot.trim(), vbot.minLength(3), vbot.maxLength(10)), {
message: 'Postleitzahl fehlt oder ist ungültig!',
}),
city: vbot.config(vbot.pipe(vbot.optional(vbot.string(), ''), vbot.trim(), vbot.minLength(3), vbot.maxLength(40)), {
message: 'Stadt fehlt oder ist ungültig!',
}),
country: vbot.config(
vbot.pipe(vbot.optional(vbot.string(), ''), vbot.trim(), vbot.length(2), vbot.toUpperCase(), vbot.regex(/^(DE|AT|CH|LU)$/)),
{
message: 'Land fehlt oder ist ungültig!',
}
),
email: vbot.config(
vbot.pipe(
vbot.optional(vbot.string(), ''),
vbot.trim(),
vbot.minLength(7),
vbot.maxLength(40),
// test illegal chars
vbot.regex(/^((?!(.*[!"#$%&'()*,/:;<=>?[\\\]^`{|}~])).)*$/),
// test having tld
vbot.regex(/^.+@(.*)[.](.+).*$/),
// test illegal tlds
vbot.regex(/^((?!(.+@.+\.(example|local|localhost|none))).)*$/),
// test valid email format
vbot.email()
),
{
message: 'E-Mail fehlt oder ist ungültig!',
}
),
}),
vbot.forward(
vbot.partialCheck(
[['product_id'], ['product_option']],
(input) => (input.product_id === 'SAMPLE1' ? input.product_option === 'DIGITAL' : input.product_option !== 'DIGITAL'),
'Produkt verfügt nicht über die gewählte Option!'
),
['product_option']
),
vbot.forward(
vbot.partialCheck(
[['product_id'], ['delivery_option']],
(input) => (input.product_id === 'SAMPLE1' ? input.delivery_option === 'DIGITAL' : input.delivery_option !== 'DIGITAL'),
'Produkt wird nicht über die gewählte Versandart geliefert!'
),
['delivery_option']
)
);
const OrderDataDefaults = (values: any = {}) => {
// generate a block of default values enriched by entered values and final fullfillments
const defaultValues = safeDefaultValues(OrderDataSchema, values);
const finalValues = finalizeOrderData(defaultValues);
return finalValues;
};
export type OrderDataInput = vbot.InferInput<typeof OrderDataSchema>;
export type OrderDataOutput = vbot.InferOutput<typeof OrderDataSchema>;
export type OrderDataIssue = vbot.InferIssue<typeof OrderDataSchema>;
export { OrderDataSchema, OrderDataDefaults, finalizeOrderData };
export default null; Using that I can (safe) initilize a forms data by (partially overwrites): const defaultValues = OrderDataDefaults({
country: 'DE',
product_id: 'SAMPLE1',
}) With a Does this make sense to you? Do you have some annotation where I might do better/easier using valibot? By now everything works like I want it. Always I will pass the values before using them in forms (Client) or action hooks (Server) via the safeParse and finalize methods to make sure in any case that the values are sanitized before using or storing them. Thanks for your feedback Tom |
Beta Was this translation helpful? Give feedback.
-
Hi @fabian-hiller
first thanks for that cool piece of library - fun to use.
I came from zod and vest and feel pretty comfortable with your concepts.
I like to generate my default records out from the schema.
Therefor (if correctly understand) you provide optional as pipe to support that
Building bigger schemas with longer pipes, that makes it a little noisy.
I am using config as well to config my messages for the fields. Would be great if you could supprt
default
there as well, like:Much easier to read the code afterwards.
Second, when using "safeDefaults", it would be great if you could parse a not fully filled record and the not provided fields of a schema would be filled with those defined defaults.
Thanks for your work and your reply
Tom
Beta Was this translation helpful? Give feedback.
All reactions