Skip to content

Distinguish request vs response source on JsonSchemaException (4xx vs 5xx) #369

@koriym

Description

@koriym

Follow-up from #364 / #367.

Context

After #367 lands, JsonSchemaException carries structured errors but does not distinguish source: it is thrown from both JsonSchemaInterceptor::validateRequest() (client-bad-input → 4xx) and validateResponse() (server-produced-bad-output → 5xx) as the same type.

Routing today happens only via the handler interface that fires:

  • JsonSchemaRequestExceptionHandlerInterface ← request validation
  • JsonSchemaExceptionHandlerInterface ← response validation

If the same exception is caught outside that handler chain (a generic logger, a unified ErrorObject, a try/catch around a resource call) the consumer cannot tell whether to surface a 4xx or a 5xx — even though the HTTP-level semantic is the opposite.

Proposal

Introduce two concrete subclasses of JsonSchemaException so that instanceof discriminates source:

class JsonSchemaException extends LogicException implements ExceptionInterface { /* unchanged */ }
final class JsonSchemaRequestException extends JsonSchemaException {}    // client input failed validation
final class JsonSchemaResponseException extends JsonSchemaException {}   // own response failed validation
  • JsonSchemaException loses final so subclassing is permitted.
  • Interceptor throws the appropriate concrete subclass.
  • Existing catch (JsonSchemaException $e) keeps working — BC safe.
  • New code can narrow: catch (JsonSchemaRequestException $e) { return new ErrorObject(422, ...); }.

Alternatives considered

  • Discriminator field (public readonly JsonSchemaSource $source enum). Single class, smaller API surface. But narrow-by-instanceof is more idiomatic in PHP and reads better at catch sites.
  • Leave as-is — handler context already routes. But that forces every direct-catch consumer to re-implement the dispatch logic.

Out of scope

  • The actual HTTP status assignment lives in the resource / framework layer, not in this exception change.
  • Handler interface signatures stay as-is for now (they already discriminate source by which one is called).

cc @koriym

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions