Skip to content

Feature Request: Add Precognition Support for Pre-Validating Requests #20

Open

Description

@rluders

Summary

Introduce an optional Precognition capability to httpsuite, allowing clients to validate request data against the server's existing parsing and validation rules before submitting the request for normal processing.

The goal is to enable frontend applications to request early validation feedback without executing the endpoint's business logic, performing mutations, or triggering side effects.

This feature should be inspired by the general concept of Precognition, as popularized by Laravel, but must not aim for Laravel protocol or API compatibility. It should follow httpsuite's existing design, public API conventions, standard-library-first philosophy, and request-processing pipeline.

Motivation

Frontend applications frequently need to validate user input before a user submits a form. Client-side validation is useful for immediate feedback, but it cannot reliably replace server-side validation because:

  • Business rules may change independently of the frontend.
  • Some rules depend on server-side state or configuration.
  • Client-side validation can be bypassed.
  • Duplicating validation rules across frontend and backend creates maintenance overhead.
  • A request may be syntactically valid but violate server-side constraints.

Today, a client that wants authoritative validation feedback may need to submit the request through the normal endpoint. Unless the application has implemented a separate validation endpoint, doing so can also execute business logic.

Precognition should make it possible to reuse the existing endpoint's request parsing and validation behavior while stopping the request before its normal operation begins.

Example use case

Consider an endpoint that creates a user:

func CreateUser(w http.ResponseWriter, r *http.Request) {
    input, err := httpsuite.ParseRequest[CreateUserRequest](
        w,
        r,
        nil,
        &httpsuite.ParseOptions{
            Validator: validator,
        },
    )
    if err != nil {
        return
    }

    // Business logic: must not run during precognitive validation.
    if err := userService.Create(r.Context(), input); err != nil {
        // Handle error.
        return
    }

    // Return the created resource.
}

A frontend may want to check whether the submitted email address is valid and available before the user completes the form.

With Precognition enabled for this endpoint, the client should be able to submit the same request payload and receive the server's validation result without creating a user.

The same principle should apply to other request handlers that use httpsuite's request parsing and validation facilities.


Goals

  1. Allow clients to request validation-only processing for an endpoint that supports Precognition.
  2. Reuse the existing request decoding, path parameter binding, and validation pipeline.
  3. Return successful validation feedback without executing the endpoint's business logic.
  4. Return validation errors using the existing ProblemDetails response conventions.
  5. Keep the feature explicitly opt-in and disabled for ordinary requests unless configured otherwise.
  6. Preserve existing behavior for requests that do not request Precognition.
  7. Avoid introducing new mandatory dependencies.
  8. Provide an API that is explicit, easy to understand, and safe to integrate into existing Go HTTP handlers.
  9. Add comprehensive tests for both the new behavior and backward compatibility.

Non-goals

The initial implementation should not:

  • Reproduce Laravel's Precognition protocol, headers, or client integration requirements.
  • Require compatibility with Laravel, Inertia.js, or any particular frontend framework.
  • Introduce a separate validation endpoint for every existing endpoint.
  • Replace or redesign the existing Validator interface.
  • Introduce a new validation framework or mandatory dependency.
  • Execute business operations, persist data, publish events, send notifications, or trigger other application side effects during Precognition.
  • Implement field-specific or partial validation unless the existing architecture supports it cleanly and the additional complexity is justified.
  • Guarantee that successful pre-validation means the subsequent operation will succeed. State-dependent rules may change between validation and submission.
  • Change the behavior of existing callers that do not enable or request Precognition.

Proposed behavior

1. Explicit opt-in

Precognition must be enabled explicitly by the endpoint or its request-processing configuration.

A request must not enter Precognition mode merely because a client sends a particular header. The endpoint must first opt in to support the feature.

The implementation should provide a clear mechanism for:

  • Enabling Precognition for a particular parsing operation.
  • Identifying a request as a Precognition request.
  • Distinguishing a validation-only request from an ordinary request.
  • Keeping existing endpoints unaffected unless they opt in.

The exact public API and request-identification mechanism should follow existing httpsuite conventions. The implementation should avoid adding several overlapping configuration options when one clear mechanism is sufficient.

If a request asks for Precognition on an endpoint where it is not enabled, the library must follow a documented and predictable behavior. It must not silently treat an ordinary request as a validation-only request or accidentally expose validation behavior that the endpoint did not opt into.

2. Reuse the existing request-processing pipeline

Precognition should reuse the existing parsing and validation infrastructure rather than implementing a parallel pipeline.

Where applicable, the processing sequence should include:

  1. Detect whether the request is a Precognition request.
  2. Confirm that Precognition is enabled for the parsing operation.
  3. Decode the request body using the existing decoding behavior.
  4. Apply the existing body-size limits and parsing options.
  5. Bind path parameters through the existing ParamExtractor mechanism.
  6. Run the configured validator using the existing Validator contract.
  7. Produce the appropriate response.
  8. Stop normal endpoint execution before business logic begins.

Existing behavior for supported content types, decoding errors, path parameter binding, validation configuration, and error serialization should be preserved wherever possible.

Precognition should not introduce a second implementation of request decoding or validation.

3. Successful validation

When a Precognition request is valid, the server should:

  • Return a successful HTTP response indicating that validation completed.
  • Prefer 204 No Content when no response body is needed.
  • Avoid returning a resource representation that would normally be produced by the endpoint.
  • Avoid running any business operation associated with the endpoint.
  • Provide an explicit control-flow signal to the handler or caller so that normal execution stops.

The response should not contain a created resource, updated resource, or other representation that implies the operation was executed.

The exact response contract should be documented and tested.

4. Failed validation

When the request is successfully parsed but fails validation, the server should:

  • Return HTTP 422 Unprocessable Content (commonly referred to as 422 Unprocessable Entity).
  • Use the existing ProblemDetails response format.
  • Preserve the library's existing validation error structure, including field-level errors when supplied by the configured validator.
  • Return enough information for a frontend to associate errors with the submitted fields.
  • Avoid executing normal endpoint logic.

The implementation should not create a new error format specifically for Precognition.

Example response, illustrating the intended structure rather than requiring a new schema:

{
  "type": "https://example.com/problems/validation-error",
  "title": "Validation failed",
  "status": 422,
  "errors": {
    "email": [
      "The email address is invalid."
    ]
  }
}

The actual serialized fields and extension structure must remain consistent with the ProblemDetails conventions already implemented by httpsuite.

5. Parsing and decoding errors

Precognition must also handle requests that cannot be parsed.

Examples include:

  • Malformed JSON.
  • Invalid request body encoding.
  • A request body that exceeds the configured size limit.
  • Invalid path parameter values.
  • Other errors already recognized by the existing parsing pipeline.

These errors should continue to use the existing error-handling and response behavior wherever possible.

Precognition should not incorrectly classify a malformed request as a successful validation or as an ordinary field-validation failure.

The implementation must document which errors are considered parsing errors and which are validation errors, following the existing library semantics.

6. Guarantee that business logic does not execute

This is the most important requirement.

A successful Precognition request must not continue into the endpoint's ordinary business logic.

In Go's net/http, returning from ParseRequest does not automatically stop execution of the handler that called it. Therefore, writing a 204 response inside the parsing function is not sufficient if the caller can continue executing subsequent statements.

The implementation must provide a clear, idiomatic, and backward-compatible way for handlers to recognize that Precognition has been handled and must stop processing.

Potential implementation approaches may include a dedicated sentinel or typed result, an explicit helper, or another design that fits the existing API. The implementation should investigate the current call patterns before selecting the approach.

The chosen API must make the control-flow requirement difficult to overlook and must not cause existing callers to break unexpectedly.

For example, the intended behavior is conceptually:

func CreateUser(w http.ResponseWriter, r *http.Request) {
    input, err := httpsuite.ParseRequest[CreateUserRequest](
        w,
        r,
        nil,
        parseOptions,
    )

    if err != nil {
        // Handle or recognize parsing, validation, and
        // Precognition control-flow outcomes appropriately.
        return
    }

    // This section must never execute for a Precognition request.
    if err := userService.Create(r.Context(), input); err != nil {
        // Handle error.
        return
    }

    // Return the created resource.
}

This snippet is illustrative. The final implementation must define the actual API and show the correct way to handle its control-flow signal.

If the design cannot guarantee that the handler stops before business logic, it does not satisfy this feature request.

API and compatibility considerations

Existing public API

The current ParseRequest[T] function and its related options are central to this feature.

Before implementation, inspect the current source code, public interfaces, existing tests, and usage patterns to determine the smallest compatible extension.

The implementation should:

  • Preserve the existing ParseRequest[T] behavior for ordinary requests.
  • Avoid breaking changes to the Validator interface.
  • Preserve existing ParseOptions behavior.
  • Avoid requiring unrelated changes to every existing endpoint.
  • Follow existing naming and error-handling conventions.
  • Keep new exported APIs documented.
  • Avoid adding an abstraction that duplicates existing capabilities.

A new configuration option may be appropriate, but the exact name and shape should be determined after inspecting the codebase.

Validator interface

Precognition must work with the existing validation abstraction.

The current validator contract should remain usable by existing validators and adapters. In particular, the implementation should not require all validators to implement a new interface just to support

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions