Created
September 8, 2024 22:06
-
-
Save msutkowski/4ee1c9709dc5676d5ee4a0fa84ee4c05 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyAppWeb.ValidateOpenAPIOperation do | |
@moduledoc """ | |
Provides a compile-time mechanism for validating controller actions against the OpenAPI specification. Utilizes a macro `validate_operation/2` to link Phoenix controller actions with OpenAPI operation IDs, enforcing validation automatically. | |
Supports an option `raise_on_error?` to determine behavior upon validation failure: when `true`, it raises an error, closely mirroring the validation enforcement seen in OpenApiSpex's `operation` macro. | |
## Example Usage | |
defmodule MyAppWeb.SomeController do | |
use MyAppWeb, :controller | |
use FacilityActivityServiceWeb.ValidateOpenAPIOperation | |
plug MyAppWeb.ValidateWithOpenAPISpec, module: __MODULE__, raise_on_error?: true | |
validate_operation "public_api_list_outbounds", raise_on_error?: false | |
def list_outbounds(conn, _params) do | |
# Action code... | |
end | |
# This inherits from the plug, and will potentially raise an error when casting and validating the request. | |
validate_operation "public_api_list_outbounds" | |
def list_inbounds(conn, _params) do | |
# Action code... | |
end | |
end | |
This setup ensures the `list_outbounds` action is validated against the OpenAPI operation "public_api_list_outbounds", with strict adherence enforced by `raise_on_error: true`. | |
## Dependencies | |
Ensure the `MyAppWeb.ValidateWithOpenAPISpec` plug is configured for accessing and validating against the OpenAPI specification. | |
## Notes | |
- Operation IDs must match those in your OpenAPI specification exactly. | |
- Validation is applied at the controller action level to confirm request parameters match the specified operation. | |
- Use `OpenApiSpex.TestAssertions.assert_operation_response/2` for validating responses in tests. | |
""" | |
defmacro __using__(_opts \\ []) do | |
quote do | |
import FacilityActivityServiceWeb.ValidateOpenAPIOperation | |
Module.register_attribute(__MODULE__, :validate_operations, accumulate: true) | |
Module.register_attribute(__MODULE__, :current_operation, accumulate: false) | |
@before_compile FacilityActivityServiceWeb.ValidateOpenAPIOperation | |
@on_definition FacilityActivityServiceWeb.ValidateOpenAPIOperation | |
end | |
end | |
@doc """ | |
Validates a Phoenix controller action against a specified OpenAPI operation at compile time. | |
This macro associates a controller action with an OpenAPI operation ID and optional configuration. It primarily supports the `raise_on_error?` option, which dictates the error handling behavior when the incoming request does not comply with the OpenAPI specification. | |
## Parameters | |
- `operation_id` : String.t() - The OpenAPI operation ID to validate against. This ID must exactly match one defined in your OpenAPI specification. | |
- `opts` : Keyword.t() - Optional configuration for validation. Supported options include: | |
- `:raise_on_error` : boolean() (default: false) - When set to `true`, raises an error if the request does not match the OpenAPI specification. When `false`, the request processing continues without raising an error, allowing for custom handling or logging. | |
## Examples | |
validate_operation "listPets", raise_on_error?: true | |
Associates the controller action with the "listPets" OpenAPI operation. If a request does not conform to the operation's specification, an error will be raised. | |
## Usage | |
This macro is intended to be used within a Phoenix controller AND requires the `ValidateWithOpenAPISpec` plug, where each action can be associated with an OpenAPI operation for automatic request validation. | |
""" | |
defmacro validate_operation(operation_id, opts \\ []) do | |
quote do | |
@current_operation %{operation_id: unquote(operation_id), opts: unquote(opts)} | |
end | |
end | |
def __on_definition__(%{module: module}, _kind, name, _args, _guards, _body) do | |
operation = Module.get_attribute(module, :current_operation) | |
if operation do | |
Module.put_attribute(module, :validate_operations, {name, operation}) | |
end | |
Module.delete_attribute(module, :current_operation) | |
end | |
defmacro __before_compile__(env) do | |
validate_operations = Module.get_attribute(env.module, :validate_operations) || [] | |
validate_operations = | |
validate_operations | |
|> Map.new() | |
|> Macro.escape() | |
quote generated: true do | |
def validate_operations, do: unquote(validate_operations) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment