Skip to content

Fix blank string validation#491

Merged
SvenVw merged 3 commits into
release/2026-02from
fix/empty-string-submissions
Mar 4, 2026
Merged

Fix blank string validation#491
SvenVw merged 3 commits into
release/2026-02from
fix/empty-string-submissions

Conversation

@BoraIneviNMI
Copy link
Copy Markdown
Collaborator

@BoraIneviNMI BoraIneviNMI commented Mar 4, 2026

Fixes

  • Various forms on the app used to let the user submit names and ids like " " which got trimmed to an empty string in extractFormValuesFromRequest thus failed min length and similar checks. The form that the user is seeing wouldn't show any errors because the string still had length to it, but once the string is trimmed server-side, the same check would fail there.

Summary by CodeRabbit

Release Notes

Bug Fixes

  • Improved form validation across authentication, field management, cultivation, fertilizer applications, organization, and farm modules to better handle whitespace in user inputs
  • Leading and trailing whitespace is now automatically removed before validation checks
  • Whitespace-only entries are now correctly rejected instead of being accepted

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 4, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 736bec6e-bdc1-40dd-94e2-8e73447e7cf8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR systematically adds .trim() calls to string validation schemas across multiple form components and route handlers, removing leading/trailing whitespace before validation checks. Some instances also replace required_error with error in Zod validation options.

Changes

Cohort / File(s) Summary
Auth & Access
fdm-app/app/components/blocks/auth/auth-formschema.tsx, fdm-app/app/lib/schemas/access.schema.ts
Changed code field from required_error to error and added .trim() to auth form schema. Added .trim() to invitation_id field in access schema.
Domain Block Schemas
fdm-app/app/components/blocks/cultivation/schema.ts, fdm-app/app/components/blocks/field/schema.tsx, fdm-app/app/components/blocks/fertilizer/formschema.tsx, fdm-app/app/components/blocks/fertilizer-applications/formschema.tsx, fdm-app/app/components/blocks/organization/schema.ts
Added .trim() to string validation fields: b_lu_catalogue, b_name, p_name_nl, p_app_method, and name/slug fields to enforce whitespace removal before length/format checks.
Fields-New & Farm Route Schemas
fdm-app/app/components/blocks/fields-new/schema.tsx, fdm-app/app/routes/farm.create._index.tsx, fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx, fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx, fdm-app/app/routes/welcome.tsx
Added .trim() to multiple string fields (b_name, b_area, b_lu_catalogue, b_name_farm, b_postalcode_farm, b_businessid_farm, firstname, surname). Changed required_error to error in three fields within fields-new schema.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~18 minutes

Possibly related PRs

Suggested labels

fdm-app

Poem

🐰 Whiskers twitching with delight,
Trimming spaces left and right,
String by string, we clean the slate,
Validation now is clean and straight!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix blank string validation' directly aligns with the PR's primary objective: fixing validation inconsistencies for whitespace-only inputs across multiple form schemas.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/empty-string-submissions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@BoraIneviNMI
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 4, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@fdm-app/app/components/blocks/fields-new/schema.tsx`:
- Around line 17-19: b_lu_catalogue currently only enforces type but allows
blank/whitespace strings; change its Zod validator to trim input and require
non-empty content (e.g., replace z.string({ error: "Hoofdgewas is verplicht" })
with a trimmed + non-empty check such as using .trim().min(1, { message:
"Hoofdgewas is verplicht" }) or .trim().nonempty({ message: "Hoofdgewas is
verplicht" })) so whitespace-only payloads are rejected; update the validator
for the b_lu_catalogue field in schema.tsx accordingly.
- Around line 12-16: Normalize empty-string inputs for the b_area field by
preprocessing them to undefined before coercion: update the b_area validator
(the z.preprocess / b_area schema that currently uses
z.coerce.number(...).optional()) to first convert "" to undefined and then apply
z.coerce.number({ error: "Oppervlakte van perceel is verplicht" }).optional(),
preserving the existing error message and optional behavior.

In `@fdm-app/app/routes/farm.create._index.tsx`:
- Around line 89-94: The validation for b_businessid_farm fails for
whitespace-only input because Zod's .trim() doesn't apply across union branches;
the .or(z.literal("")) branch sees the original untrimmed value. Update the
b_businessid_farm schema to first normalize the input (trim or transform to ""),
then perform a post-transform check with .refine() that allows an empty string
or enforces the /^\d{8}$/ regex; reference the existing symbol
b_businessid_farm, the .trim()/.or(z.literal("")) union, and replace that logic
with a single-string pipeline that trims/normalizes and then .refine()s the
trimmed value to accept either "" or 8 digits.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 282496e2-6c28-4976-a6d0-30b5123e60e1

📥 Commits

Reviewing files that changed from the base of the PR and between 8fa63ee and e9b79f4.

📒 Files selected for processing (12)
  • fdm-app/app/components/blocks/auth/auth-formschema.tsx
  • fdm-app/app/components/blocks/cultivation/schema.ts
  • fdm-app/app/components/blocks/fertilizer-applications/formschema.tsx
  • fdm-app/app/components/blocks/fertilizer/formschema.tsx
  • fdm-app/app/components/blocks/field/schema.tsx
  • fdm-app/app/components/blocks/fields-new/schema.tsx
  • fdm-app/app/components/blocks/organization/schema.ts
  • fdm-app/app/lib/schemas/access.schema.ts
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.overview.tsx
  • fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx
  • fdm-app/app/routes/farm.create._index.tsx
  • fdm-app/app/routes/welcome.tsx

Comment on lines 12 to 16
b_area: z.coerce
.number({
required_error: "Oppervlakte van perceel is verplicht",
error: "Oppervlakte van perceel is verplicht",
})
.optional(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "schema.tsx" | head -20

Repository: nmi-agro/fdm

Length of output: 209


🏁 Script executed:

# Get the file size first to understand what we're dealing with
wc -l fdm-app/app/components/blocks/fields-new/schema.tsx

Repository: nmi-agro/fdm

Length of output: 110


🏁 Script executed:

# Read the target file around lines 12-16
cat -n fdm-app/app/components/blocks/fields-new/schema.tsx | head -30

Repository: nmi-agro/fdm

Length of output: 734


🏁 Script executed:

# Search for similar preprocessing patterns in schema files to confirm the pattern usage
rg "z.preprocess.*z.coerce.number" --type typescript

Repository: nmi-agro/fdm

Length of output: 90


🏁 Script executed:

# Search for z.preprocess patterns more broadly
rg "z.preprocess" fdm-app/app/components/blocks/

Repository: nmi-agro/fdm

Length of output: 8696


🏁 Script executed:

# Check the field/schema.tsx to see how optional numeric fields are handled there
cat -n fdm-app/app/components/blocks/field/schema.tsx

Repository: nmi-agro/fdm

Length of output: 1316


🏁 Script executed:

# Check harvest/schema.tsx too
cat -n fdm-app/app/components/blocks/harvest/schema.tsx | head -50

Repository: nmi-agro/fdm

Length of output: 2111


Add empty-string normalization to b_area field to match the established pattern for optional numeric fields.

The b_area field should preprocess empty input to undefined before numeric coercion, consistent with how other optional numeric fields are handled throughout the codebase (e.g., b_lu_yield in harvest schema). Without this, empty strings may be coerced improperly.

Proposed fix
-    b_area: z.coerce
-        .number({
-            error: "Oppervlakte van perceel is verplicht",
-        })
-        .optional(),
+    b_area: z.preprocess(
+        (val) => (val === "" ? undefined : val),
+        z.coerce.number({
+            error: "Oppervlakte van perceel is verplicht",
+        }).optional(),
+    ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
b_area: z.coerce
.number({
required_error: "Oppervlakte van perceel is verplicht",
error: "Oppervlakte van perceel is verplicht",
})
.optional(),
b_area: z.preprocess(
(val) => (val === "" ? undefined : val),
z.coerce.number({
error: "Oppervlakte van perceel is verplicht",
}).optional(),
),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/components/blocks/fields-new/schema.tsx` around lines 12 - 16,
Normalize empty-string inputs for the b_area field by preprocessing them to
undefined before coercion: update the b_area validator (the z.preprocess /
b_area schema that currently uses z.coerce.number(...).optional()) to first
convert "" to undefined and then apply z.coerce.number({ error: "Oppervlakte van
perceel is verplicht" }).optional(), preserving the existing error message and
optional behavior.

Comment on lines 17 to 19
b_lu_catalogue: z.string({
required_error: "Hoofdgewas is verplicht",
error: "Hoofdgewas is verplicht",
}),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

b_lu_catalogue is marked required but still allows blank/whitespace payloads.

Line 17–19 currently only enforces string type. Add trim + non-empty validation to fully align with this PR’s blank-string objective.

Proposed fix
-    b_lu_catalogue: z.string({
-        error: "Hoofdgewas is verplicht",
-    }),
+    b_lu_catalogue: z
+        .string({
+            error: "Hoofdgewas is verplicht",
+        })
+        .trim()
+        .min(1, "Hoofdgewas is verplicht"),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
b_lu_catalogue: z.string({
required_error: "Hoofdgewas is verplicht",
error: "Hoofdgewas is verplicht",
}),
b_lu_catalogue: z
.string({
error: "Hoofdgewas is verplicht",
})
.trim()
.min(1, "Hoofdgewas is verplicht"),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/components/blocks/fields-new/schema.tsx` around lines 17 - 19,
b_lu_catalogue currently only enforces type but allows blank/whitespace strings;
change its Zod validator to trim input and require non-empty content (e.g.,
replace z.string({ error: "Hoofdgewas is verplicht" }) with a trimmed +
non-empty check such as using .trim().min(1, { message: "Hoofdgewas is
verplicht" }) or .trim().nonempty({ message: "Hoofdgewas is verplicht" })) so
whitespace-only payloads are rejected; update the validator for the
b_lu_catalogue field in schema.tsx accordingly.

Comment on lines 89 to 94
b_businessid_farm: z
.string()
.trim()
.regex(/^\d{8}$/, "KvK-nummer moet uit 8 cijfers bestaan")
.optional()
.or(z.literal("")),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Zod v4 z.string().trim().regex().optional().or(z.literal("")) transformation behavior union

💡 Result:

In Zod v4:

  • z.string().trim() is a string transformation (the parsed output is trimmed). (zodjs.netlify.app)
  • A union made via .or(...) / z.union([...]) is evaluated option-by-option in order, and Zod returns the first option that successfully parses. (odocs-zod.vercel.app)

So with:

z.string().trim().regex(R).optional().or(z.literal(""))

What actually happens

  1. This is a union with (roughly) these branches:

    • Branch A: z.string().trim().regex(R).optional() (i.e., “string matching R” OR undefined)
    • Branch B: z.literal("")
  2. Transforms/refinements do not “carry over” between union branches. Each branch gets the original input.

    • If the input is "": Branch A fails the regex, Branch B succeeds → output is "" (not undefined).
    • If the input is " ": Branch A trims to "" then fails regex; Branch B checks the original " " (not "") and fails → overall fails (this surprises people).

This explains why “allow empty string” via .or(z.literal("")) makes validation pass, but you then store empty strings in the DB. (stackoverflow.com)

Common fixes (depending on desired output)

A) Treat "" as “missing” (undefined)

const emptyToUndefined = z.literal("").transform(() => undefined);

const s = z.string().trim().regex(R).optional().or(emptyToUndefined);

(So "" parses to undefined.) (gist.github.com)

B) Make trimming happen before the union (so " " becomes "" and can match the empty-string branch)
Use a top-level preprocess/transform that trims first, then union. (Conceptually: “normalize input, then validate”.) (zodjs.netlify.app)

Citations:


Fix validation logic for whitespace-only KvK input.

In Zod v4, transformations like .trim() do not carry over between union branches. Each branch in .or(z.literal("")) receives the original, untrimmed input. For input like " ", the string branch trims to "" and fails the regex, but the z.literal("") branch checks the original " " and also fails—causing validation to reject whitespace-only strings.

Use .refine() to apply validation after trimming has occurred:

Suggested fix
-        b_businessid_farm: z
-            .string()
-            .trim()
-            .regex(/^\d{8}$/, "KvK-nummer moet uit 8 cijfers bestaan")
-            .optional()
-            .or(z.literal("")),
+        b_businessid_farm: z
+            .string()
+            .trim()
+            .refine(
+                (value) => value === "" || /^\d{8}$/.test(value),
+                "KvK-nummer moet uit 8 cijfers bestaan",
+            )
+            .optional(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
b_businessid_farm: z
.string()
.trim()
.regex(/^\d{8}$/, "KvK-nummer moet uit 8 cijfers bestaan")
.optional()
.or(z.literal("")),
b_businessid_farm: z
.string()
.trim()
.refine(
(value) => value === "" || /^\d{8}$/.test(value),
"KvK-nummer moet uit 8 cijfers bestaan",
)
.optional(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/routes/farm.create._index.tsx` around lines 89 - 94, The
validation for b_businessid_farm fails for whitespace-only input because Zod's
.trim() doesn't apply across union branches; the .or(z.literal("")) branch sees
the original untrimmed value. Update the b_businessid_farm schema to first
normalize the input (trim or transform to ""), then perform a post-transform
check with .refine() that allows an empty string or enforces the /^\d{8}$/
regex; reference the existing symbol b_businessid_farm, the
.trim()/.or(z.literal("")) union, and replace that logic with a single-string
pipeline that trims/normalizes and then .refine()s the trimmed value to accept
either "" or 8 digits.

@BoraIneviNMI BoraIneviNMI requested a review from SvenVw March 4, 2026 12:52
@SvenVw SvenVw merged commit a8b7cee into release/2026-02 Mar 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants