Skip to content

New rule: Do you reject enum values your code does not define? - #13208

Open
bradystroud wants to merge 2 commits into
mainfrom
reject-undefined-enum-values
Open

New rule: Do you reject enum values your code does not define?#13208
bradystroud wants to merge 2 commits into
mainfrom
reject-undefined-enum-values

Conversation

@bradystroud

Copy link
Copy Markdown
Member
  1. What triggered this change? (PBI link, Email Subject, conversation + reason, etc)

✏️ A lesson learned on a client project, generalised. A status parser accepted any numeric value, because Enum.TryParse("99") returns true with an undefined result. The unrecognised status silently rendered as an ordinary state, which also made the record eligible for a bulk action it should never have entered.

  1. What was changed?

✏️ Added a new rule: Do you reject enum values your code does not define?

  • New rule at public/uploads/rules/reject-undefined-enum-values/rule.mdx
  • Added to categories/software-engineering/rules-to-better-code.mdx, beside the existing enum rules

It covers:

  • A successful parse is not a valid value — Enum.TryParse and a plain cast both produce an undefined member that matches no switch branch
  • Reserve an explicit Unknown = 0 member so an unrecognised value never lands on a real state
  • Log the rejected value, since it usually means an upstream contract moved
  • [Flags] enums need a bit mask, because Enum.IsDefined rejects valid combinations
  • Apply the check at every entry point — JSON, model binding, and database reads have the same hole

Validators run and passing: frontmatter, check-mdx, markdownlint, category-sync, and find-rules-missing-endintro.

  1. I paired or mob programmed with:

✏️ Written with Claude Code.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XLJSUhLVomcXxysdC8b7XQ

Enum.TryParse returns true for any number the underlying type can hold, so an undefined value parses cleanly and falls through to the default member. The rule covers checking Enum.IsDefined, reserving an explicit Unknown member, logging the rejected value, using a mask for [Flags] enums, and validating at every entry point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLJSUhLVomcXxysdC8b7XQ
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Preview PR Changes

View updated pages in edit mode:

Branch: reject-undefined-enum-values

@github-actions github-actions Bot added the Age: 🥚 - New About 2 hours old label Aug 20, 2026
@bradystroud bradystroud added the client work learnings Changes based on things we learnt from client work label Aug 20, 2026
@github-actions github-actions Bot added Age: 🐣 - Young About 4 hours old Age: 🐥 - Adolescent About 8 hours old Age: 🐤 - Mature About 16 hours old Age: 🐓 - Old About 32 hours old Age: 🍗 - Ancient About 64 hours old 🔥 Merge Debt This PR contains merge debt, see https://www.ssw.com.au/rules/merge-debt/ and removed Age: 🥚 - New About 2 hours old Age: 🐣 - Young About 4 hours old Age: 🐥 - Adolescent About 8 hours old Age: 🐤 - Mature About 16 hours old Age: 🐓 - Old About 32 hours old labels Aug 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Howzit @bradystroud,

This PR has been here a while.

Did you know you should avoid merge debt?

  1. Please action (e.g. get a review) and merge or close

Thanks!

@tiagov8 tiagov8 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@bradystroud

Few suggestions for readability

@@ -0,0 +1,88 @@
---
type: rule
title: Do you reject enum values your code does not define?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
title: Do you reject enum values your code does not define?
title: Do you reject undefined enum values?


`Enum.TryParse` tells you the text converted to the underlying integer type. It does not tell you the result is one of your members. A cast such as `(Status)99` does not throw either. Both produce a value that matches no `switch` branch and falls through to whatever your default is.

That default is where the damage happens. Enum members usually start at zero with the most ordinary state, so an unrecognized value quietly renders as the safest looking option. The screen shows a state that is wrong but plausible, and nobody questions it — until the record is picked up by a bulk action that should never have touched it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
That default is where the damage happens. Enum members usually start at zero with the most ordinary state, so an unrecognized value quietly renders as the safest looking option. The screen shows a state that is wrong but plausible, and nobody questions it until the record is picked up by a bulk action that should never have touched it.
That default is where the damage happens. Enum members usually start at zero with the most ordinary state, so an unrecognized value quietly renders as the safest looking option. The screen shows a state that is wrong but plausible, and nobody questions it, until the record is picked up by a bulk action that should never have touched it.


An unrecognized value usually means an upstream system added a member and did not tell you. If you map it to `Unknown` in silence, you lose the only notice you will get that the contract has moved. Log the raw value and its source, so the gap becomes a work item instead of a mystery.

## Flags enums need a mask

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
## Flags enums need a mask
## `Flags` enums need a mask

@github-actions github-actions Bot added Age: 🦖 - Extinct About 128 hours old and removed Age: 🍗 - Ancient About 64 hours old labels Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Age: 🦖 - Extinct About 128 hours old client work learnings Changes based on things we learnt from client work 🔥 Merge Debt This PR contains merge debt, see https://www.ssw.com.au/rules/merge-debt/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants