Skip to content

Combine const and enum errors into a single message#155

Merged
jdesrosiers merged 3 commits intohyperjump-io:mainfrom
ShivaGupta-14:132-combine-const-enum-handlers
Feb 15, 2026
Merged

Combine const and enum errors into a single message#155
jdesrosiers merged 3 commits intohyperjump-io:mainfrom
ShivaGupta-14:132-combine-const-enum-handlers

Conversation

@ShivaGupta-14
Copy link
Contributor

Description

When multiple const or enum constraints fail (e.g., via allOf), produce a single message with the most restrictive constraint instead of multiple redundant messages.

  • Compute the intersection of allowed values across all constraints
  • If intersection has 1 value: use const message
  • If intersection has multiple values: use enum message
  • If intersection is empty (contradictory): use boolean schema message
  • Fixes: Combine const and enum handlers #132

Checklist

  • npm test
  • npm run lint
  • npm run type-check

@ShivaGupta-14 ShivaGupta-14 force-pushed the 132-combine-const-enum-handlers branch from 7646a94 to ac73647 Compare January 30, 2026 18:24
Copy link
Collaborator

@jdesrosiers jdesrosiers left a comment

Choose a reason for hiding this comment

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

This approach isn't going to work. Try,

{
  "allOf": [
    { "const": "a" },
    { "const": "b" }
  ]
}

@ShivaGupta-14 ShivaGupta-14 force-pushed the 132-combine-const-enum-handlers branch from ac73647 to 3b4eaf2 Compare January 31, 2026 15:51
@ShivaGupta-14
Copy link
Contributor Author

ShivaGupta-14 commented Jan 31, 2026

This approach isn't going to work. Try,

{
  "allOf": [
    { "const": "a" },
    { "const": "b" }
  ]
}

Hi @jdesrosiers,

thanks for pointing out the issue! I understood the problem:

Issue: with a schema like above one -> allOf: [{ const: "a" }, { const: "b" }], if the instance is "a"

  • const: "a" passes (not in errors)
  • Only const: "b" fails
  • My original code only saw failing constraints -> couldn't detect the contradiction
  • My Solution: I updated the handler to collect ALL constraints (both passing and failing), then compute the intersection:
const passed = normalizedErrors[...][schemaLocation] === true;
if (!passed) hasFailure = true;
// always collect
constraints.push({ allowedValues: [...], schemaLocation });

now:

  • Both ["a"] and ["b"] are collected
  • Intersection = [] (empty)
  • Returns getBooleanSchemaErrorMessage()

Tests Added:

  • contradictory const - empty intersection (instance matches one const)
  • contradictory enum - empty intersection (disjoint enum sets)

All tests pass. Is this approach good, or does it need further improvements?
Thanks!

Copy link
Collaborator

@jdesrosiers jdesrosiers left a comment

Choose a reason for hiding this comment

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

That should work. Just a couple small things I'd like to see.

@ShivaGupta-14 ShivaGupta-14 force-pushed the 132-combine-const-enum-handlers branch 2 times, most recently from e7b9dec to 219a62a Compare February 2, 2026 13:08
@ShivaGupta-14 ShivaGupta-14 force-pushed the 132-combine-const-enum-handlers branch from 219a62a to 5a31867 Compare February 2, 2026 22:35
Copy link
Collaborator

@jdesrosiers jdesrosiers left a comment

Choose a reason for hiding this comment

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

The logic to find the most constraining keyword felt off to me and I figured out a case where it doesn't work. Try,

{
  "allOf": [
    { "enum": ["a", "b", "c"] },
    { "enum": ["a", "b", "d"] },
    { "enum": ["a", "b", "e"] }
  ]
}

with,

"c"

It says, "Expected one of ⁨"a" or "b"⁩" with schema location /allOf/0/enum, but that's a passing location. If we exclude passing locations, we get a schema location of /allOf/1/enum, which is better, but it seems contradictory because the message says only "a" and "b" are allowed, but the schema location says that "d" is also allowed. That means we need to include all the failing schema locations to make it clear why only "a" and "b" are allowed. I think the only time we can collapse to a single most constraining location is when the effective allowed set matches exactly one of the keywords.

This is a tough one.

@jdesrosiers
Copy link
Collaborator

Another edge case to consider,

{
  "allOf": [
    { "enum": ["a", "b"] },
    { "enum": ["a", "b"] }
  ]
}

with,

"c"

Should one location be selected as the most constraining, or should we return both locations? I think either one is fine.

@ShivaGupta-14 ShivaGupta-14 force-pushed the 132-combine-const-enum-handlers branch from 5a31867 to 0a80648 Compare February 4, 2026 17:22
@ShivaGupta-14
Copy link
Contributor Author

It says, "Expected one of ⁨"a" or "b"⁩" with schema location /allOf/0/enum, but that's a passing location. If we exclude passing locations, we get a schema location of /allOf/1/enum, which is better, but it seems contradictory because the message says only "a" and "b" are allowed, but the schema location says that "d" is also allowed. That means we need to include all the failing schema locations to make it clear why only "a" and "b" are allowed. I think the only time we can collapse to a single most constraining location is when the effective allowed set matches exactly one of the keywords.

Thanks for pointing it out, I missed it. for such cases, I’ve updated the logic.

  • If the intersection matches exactly one constraint's values -> use that single location
  • Otherwise -> include all constraint locations

this will handle edge case: when intersection is ["a","b"] but no single constraint has exactly ["a","b"], we show all locations so users understand why only "a" and "b" are valid.

also, test added for this case: "multiple enums with no exact match"

@ShivaGupta-14
Copy link
Contributor Author

Should one location be selected as the most constraining, or should we return both locations? I think either one is fine.

return one since both are exactly the same in such cases. also, duplicate identical enums in real schemas are likely unintentional, so returning one seems sufficient.

@ShivaGupta-14
Copy link
Contributor Author

Hi @jdesrosiers!, PTAL

Copy link
Collaborator

@jdesrosiers jdesrosiers left a comment

Choose a reason for hiding this comment

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

Sorry this one has taken a while. It just hasn't felt right to me and I wanted some time to think about it some more. Mostly, it feels too complicated and I was hoping for something simpler that would be easier to maintain. I was able to take some time this weekend and I think I figured out something that works out well enough with a simple implementation. Here are the rules.

  1. If the intersection is empty, return the boolean-schema-message and all schema locations whether they passed or failed.
  2. If there are const errors, only use those schema locations. Otherwise, use the enum error schema locations.

The main thing that ends up being different from what we had is that it doesn't collapse the schema locations to a single value if there's an exact match. I think that's fine.

Let me know if you can think of any way where these rules can have problems.

@jdesrosiers jdesrosiers force-pushed the 132-combine-const-enum-handlers branch from 63d57e3 to 9b1bd19 Compare February 15, 2026 03:30
@jdesrosiers jdesrosiers merged commit 8b91f1f into hyperjump-io:main Feb 15, 2026
1 check 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.

Combine const and enum handlers

2 participants