fix: 🐛 allow either or both in price.tiers type#876
fix: 🐛 allow either or both in price.tiers type#876johnyeocx merged 3 commits intouseautumn:mainfrom
Conversation
|
@SirTenzin is attempting to deploy a commit to the Autumn Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 24418dbc42
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| export const UsageTierSchema = z | ||
| .object({ | ||
| to: z.number().or(z.literal(Infinite)), | ||
| amount: z.number().optional().default(0), |
There was a problem hiding this comment.
Enforce tier price presence before applying defaults
amount is now declared as z.number().optional().default(0), so the subsequent refine check (amount !== undefined || flat_amount !== undefined) is effectively always true after parsing. That means a tier like { "to": 100 } passes UsageTierSchema even though the schema intends to require at least one pricing field, weakening validation anywhere this shared schema is used for request parsing.
Useful? React with 👍 / 👎.
| .refine( | ||
| (z) => { | ||
| return z.amount !== undefined || z.flat_amount !== undefined; | ||
| }, | ||
| { | ||
| message: "Either amount or flat_amount, or both must be defined", | ||
| path: ["amount", "flat_amount"], | ||
| }, | ||
| ); |
There was a problem hiding this comment.
The .refine() validation will always pass and never catch anything. Because amount is declared as .optional().default(0), Zod applies the default before the refinement runs — so by the time the callback executes, z.amount is always 0, never undefined.
The condition z.amount !== undefined || z.flat_amount !== undefined evaluates to 0 !== undefined || ..., which is always true.
To make this validation meaningful, the default needs to be applied after the refine, not before. Consider this structure:
| .refine( | |
| (z) => { | |
| return z.amount !== undefined || z.flat_amount !== undefined; | |
| }, | |
| { | |
| message: "Either amount or flat_amount, or both must be defined", | |
| path: ["amount", "flat_amount"], | |
| }, | |
| ); | |
| export const UsageTierSchema = z | |
| .object({ | |
| to: z.number().or(z.literal(Infinite)), | |
| amount: z.number().optional(), | |
| flat_amount: z.number().optional(), | |
| }) | |
| .refine( | |
| (val) => val.amount !== undefined || val.flat_amount !== undefined, | |
| { | |
| message: "Either amount or flat_amount, or both must be defined", | |
| path: ["amount", "flat_amount"], | |
| }, | |
| ) | |
| .transform((val) => ({ | |
| ...val, | |
| amount: val.amount ?? 0, | |
| })); |
This way, the refine callback runs on the raw data (before defaults), and the default is applied afterward via transform.
Prompt To Fix With AI
This is a comment left during a code review.
Path: shared/models/productModels/priceModels/priceConfig/usagePriceConfig.ts
Line: 22-30
Comment:
The `.refine()` validation will always pass and never catch anything. Because `amount` is declared as `.optional().default(0)`, Zod applies the default _before_ the refinement runs — so by the time the callback executes, `z.amount` is always `0`, never `undefined`.
The condition `z.amount !== undefined || z.flat_amount !== undefined` evaluates to `0 !== undefined || ...`, which is always `true`.
To make this validation meaningful, the default needs to be applied *after* the refine, not before. Consider this structure:
```suggestion
export const UsageTierSchema = z
.object({
to: z.number().or(z.literal(Infinite)),
amount: z.number().optional(),
flat_amount: z.number().optional(),
})
.refine(
(val) => val.amount !== undefined || val.flat_amount !== undefined,
{
message: "Either amount or flat_amount, or both must be defined",
path: ["amount", "flat_amount"],
},
)
.transform((val) => ({
...val,
amount: val.amount ?? 0,
}));
```
This way, the refine callback runs on the raw data (before defaults), and the default is applied afterward via transform.
How can I resolve this? If you propose a fix, please make it concise.
Additional Comments (1)
Without the refine, an object like Prompt To Fix With AIThis is a comment left during a code review.
Path: shared/models/productV2Models/productItemModels/productItemModels.ts
Line: 17-30
Comment:
`UsageTierSchema` (in `usagePriceConfig.ts`) includes a `.refine()` guard to enforce that at least one of `amount` or `flat_amount` is provided, but `PriceTierSchema` here is missing that guard. Both schemas have identical field structures (`amount` and `flat_amount` both optional), so they should enforce the same validation rule.
Without the refine, an object like `{ to: 100 }` (with neither field) will pass validation silently, which is inconsistent with `UsageTierSchema`. Consider adding the same refine guard to ensure consistent validation across both tier schemas.
How can I resolve this? If you propose a fix, please make it concise. |
There was a problem hiding this comment.
2 issues found across 4 files
Confidence score: 3/5
- There is some user-impacting risk:
flat_amountinshared/models/productV2Models/productItemModels/productItemModels.tsno longer acceptsnull, which can break parsing for existing/API tier payloads that still sendflat_amount: null. - Validation is weakened in
shared/models/productModels/priceModels/priceConfig/usagePriceConfig.tsbecauseamountdefaults to0, so tiers with neither field explicitly provided may slip through. - Pay close attention to
shared/models/productV2Models/productItemModels/productItemModels.tsandshared/models/productModels/priceModels/priceConfig/usagePriceConfig.ts- parsing/validation changes could affect existing tier payloads.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="shared/models/productV2Models/productItemModels/productItemModels.ts">
<violation number="1" location="shared/models/productV2Models/productItemModels/productItemModels.ts:26">
P1: `flat_amount` no longer accepts `null`, which can break parsing of existing/API tier payloads that still send `flat_amount: null`.</violation>
</file>
<file name="shared/models/productModels/priceModels/priceConfig/usagePriceConfig.ts">
<violation number="1" location="shared/models/productModels/priceModels/priceConfig/usagePriceConfig.ts:19">
P1: `amount` defaulting to `0` makes the "either amount or flat_amount must be defined" validation ineffective, allowing tiers with neither field explicitly provided.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
shared/models/productV2Models/productItemModels/productItemModels.ts
Outdated
Show resolved
Hide resolved
shared/models/productModels/priceModels/priceConfig/usagePriceConfig.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="shared/models/productV2Models/productItemModels/productItemModels.ts">
<violation number="1" location="shared/models/productV2Models/productItemModels/productItemModels.ts:27">
P2: `flat_amount` is now nullable in `PriceTierSchema`, which introduces an inconsistent tier shape (`number | null | undefined`) versus other tier schemas/types that expect only `number | undefined`. This can allow `null` tiers through validation and cause downstream validation/handling mismatches.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
shared/models/productV2Models/productItemModels/productItemModels.ts
Outdated
Show resolved
Hide resolved
…els.ts Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Summary by cubic
Allow price.tiers to define amount, flat_amount, or both, with a guard that requires at least one. Missing amount now defaults to 0 across schemas and conversions, and flat_amount nulls are treated as undefined.
Bug Fixes
Migration
Written for commit 5245c89. Summary will update on new commits.
Greptile Summary
This PR updates
UsageTierSchemaandPriceTierSchemato allow tiered pricing where onlyflat_amount, onlyamount, or both can be provided — makingamountoptional with a default of0instead of required.Key changes:
amountoptional in tier schemas so that flat-fee-only tiers are now expressible without providing a per-unit rate..refine()guard toUsageTierSchemato enforce that at least one ofamountorflat_amountis present; however, the guard is ineffective becauseamountcarries.default(0), which Zod applies before the refinement callback runs. By the time the refine executes,amountis always0(notundefined), making the validation condition always true.PriceTierSchemais missing the validation guard that was added toUsageTierSchema, creating an inconsistency whereUsageTierSchema(ineffectively) attempts to enforce at-least-one-field validation, whilePriceTierSchemadoes not.Both
shared/models/productModels/priceModels/priceConfig/usagePriceConfig.tsandshared/models/productV2Models/productItemModels/productItemModels.tsneed attention for consistent and effective validation.Confidence Score: 2/5
.refine()guard inUsageTierSchemais ineffective becauseamount's default is applied before the refinement callback runs, making the validation always pass; (2)PriceTierSchemalacks the equivalent guard entirely, creating inconsistent validation behavior. Both schemas should enforce the same at-least-one-field rule, but currently neither does effectively.shared/models/productModels/priceModels/priceConfig/usagePriceConfig.ts(refine callback order) andshared/models/productV2Models/productItemModels/productItemModels.ts(missing refine guard)Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Tier Input Object] --> B{Zod Parse} B --> C[Apply field defaults\namount = 0 if absent] C --> D{.refine check\namount !== undefined\nOR flat_amount !== undefined} D -- "Always TRUE\n(amount is 0, not undefined)" --> E[Validation Passes ✓] D -- "Never reached" --> F[Validation Fails ✗] subgraph UsageTierSchema C D end subgraph PriceTierSchema G[Apply field defaults\namount = 0 if absent] --> H[No .refine guard] H --> I[Validation Passes\neven with empty tier ⚠️] end B --> GLast reviewed commit: 24418db