AshOaskit.SpecModifier (AshOasKit v0.2.0)

View Source

Provides support for modifying OpenAPI specifications after generation.

This module enables users to customize the generated OpenAPI spec through callback functions, similar to AshJsonApi's modify_open_api option. This is useful for adding custom extensions, modifying schemas, or integrating with external documentation systems.

Modification Patterns

Function Callback

Pass a function that receives the spec and returns a modified spec:

AshOaskit.spec_31(
  domains: [MyApp.Blog],
  modify_open_api: fn spec ->
    put_in(spec, ["info", "x-custom"], "value")
  end
)

MFA Tuple

Pass a module, function, args tuple for more complex modifications:

AshOaskit.spec_31(
  domains: [MyApp.Blog],
  modify_open_api: {MyApp.OpenApiCustomizer, :customize, [extra_arg]}
)

Multiple Modifiers

Chain multiple modifications:

AshOaskit.spec_31(
  domains: [MyApp.Blog],
  modify_open_api: [
    &add_custom_headers/1,
    &add_rate_limiting_info/1,
    {MyApp.Docs, :add_examples, []}
  ]
)

Common Modifications

  • Adding custom headers to all operations
  • Adding x-* extension fields
  • Modifying security schemes
  • Adding webhook definitions
  • Customizing server URLs per environment
  • Adding examples to schemas

Summary

Functions

Adds a custom extension field to the spec at the specified path.

Adds external documentation link to the spec.

Adds custom headers to all operations in the spec.

Adds a parameter definition to the components section.

Adds a response definition to the components section.

Adds or updates a schema in the components section.

Adds examples to a schema in the components section.

Adds a server to the spec's servers list.

Adds a tag to the spec's tags list.

Adds a webhook definition to the spec.

Applies modifications to an OpenAPI specification.

Creates a modifier function that adds deprecation notices to operations.

Creates a modifier function that adds rate limiting information.

Replaces the servers list in the spec.

Modifies the info section of the spec.

Types

modifier()

@type modifier() :: (map() -> map()) | {module(), atom(), list()} | list() | nil

Functions

add_extension(spec, path, extension_name, value)

@spec add_extension(map(), [String.t()], String.t(), any()) :: map()

Adds a custom extension field to the spec at the specified path.

Extension fields in OpenAPI start with "x-" and can contain any value.

Examples

iex> spec = %{"info" => %{"title" => "API"}}
...> AshOaskit.SpecModifier.add_extension(spec, ["info"], "x-logo", %{"url" => "logo.png"})
%{"info" => %{"title" => "API", "x-logo" => %{"url" => "logo.png"}}}

add_external_docs(spec, url, opts \\ [])

@spec add_external_docs(map(), String.t(), keyword()) :: map()

Adds external documentation link to the spec.

Examples

iex> spec = %{"info" => %{"title" => "API"}}
...>
...> AshOaskit.SpecModifier.add_external_docs(spec, "https://docs.example.com",
...>   description: "Full documentation"
...> )
%{
  "info" => %{"title" => "API"},
  "externalDocs" => %{
    "url" => "https://docs.example.com",
    "description" => "Full documentation"
  }
}

add_header_to_operations(spec, header_name, schema, opts \\ [])

@spec add_header_to_operations(map(), String.t(), map(), keyword()) :: map()

Adds custom headers to all operations in the spec.

This is useful for documenting common headers like correlation IDs, API versions, or custom authentication headers.

Options

  • :operations - List of operation IDs to modify. If nil, modifies all operations.
  • :required - Whether the header is required. Defaults to false.

Examples

iex> spec = %{"paths" => %{"/posts" => %{"get" => %{"operationId" => "listPosts"}}}}
...>
...> AshOaskit.SpecModifier.add_header_to_operations(spec, "X-Request-ID", %{
...>   "type" => "string",
...>   "format" => "uuid"
...> })

add_operation_example(spec, operation_id, media_type, example)

@spec add_operation_example(map(), String.t(), String.t(), map()) :: map()

Adds an example to an operation.

Examples

iex> spec = %{"paths" => %{"/posts" => %{"get" => %{"operationId" => "listPosts"}}}}
...> example = %{"summary" => "List posts", "value" => %{"data" => []}}
...>
...> AshOaskit.SpecModifier.add_operation_example(
...>   spec,
...>   "listPosts",
...>   "application/json",
...>   example
...> )

add_parameter(spec, name, parameter)

@spec add_parameter(map(), String.t(), map()) :: map()

Adds a parameter definition to the components section.

Examples

iex> spec = %{"components" => %{}}
...> param = %{"name" => "page", "in" => "query", "schema" => %{"type" => "integer"}}
...> AshOaskit.SpecModifier.add_parameter(spec, "PageParam", param)

add_response(spec, name, response)

@spec add_response(map(), String.t(), map()) :: map()

Adds a response definition to the components section.

Examples

iex> spec = %{"components" => %{}}
...> response = %{"description" => "Rate limit exceeded"}
...> AshOaskit.SpecModifier.add_response(spec, "RateLimitError", response)

add_schema(spec, name, schema)

@spec add_schema(map(), String.t(), map()) :: map()

Adds or updates a schema in the components section.

Examples

iex> spec = %{"components" => %{"schemas" => %{}}}
...> schema = %{"type" => "object", "properties" => %{"id" => %{"type" => "string"}}}
...> AshOaskit.SpecModifier.add_schema(spec, "CustomResource", schema)
%{"components" => %{"schemas" => %{"CustomResource" => %{...}}}}

add_schema_examples(spec, schema_name, examples)

@spec add_schema_examples(map(), String.t(), [map()]) :: map()

Adds examples to a schema in the components section.

Examples

iex> spec = %{"components" => %{"schemas" => %{"Post" => %{"type" => "object"}}}}
...> examples = [%{"id" => "1", "title" => "Hello World"}]
...> AshOaskit.SpecModifier.add_schema_examples(spec, "Post", examples)

add_server(spec, url, opts \\ [])

@spec add_server(map(), String.t(), keyword()) :: map()

Adds a server to the spec's servers list.

Examples

iex> spec = %{"servers" => [%{"url" => "https://api.example.com"}]}
...>
...> AshOaskit.SpecModifier.add_server(spec, "https://staging.example.com",
...>   description: "Staging"
...> )
%{
  "servers" => [
    %{"url" => "https://api.example.com"},
    %{"url" => "https://staging.example.com", "description" => "Staging"}
  ]
}

add_tag(spec, name, opts \\ [])

@spec add_tag(map(), String.t(), keyword()) :: map()

Adds a tag to the spec's tags list.

Tags are used to group operations in documentation tools.

Examples

iex> spec = %{"tags" => [%{"name" => "Posts"}]}
...> AshOaskit.SpecModifier.add_tag(spec, "Comments", description: "Comment operations")
%{
  "tags" => [
    %{"name" => "Posts"},
    %{"name" => "Comments", "description" => "Comment operations"}
  ]
}

add_webhook(spec, name, webhook)

@spec add_webhook(map(), String.t(), map()) :: map()

Adds a webhook definition to the spec.

Webhooks are callbacks that the API can send to client-specified URLs.

Examples

iex> spec = %{}
...>
...> webhook = %{
...>   "post" => %{
...>     "summary" => "New post created",
...>     "requestBody" => %{...}
...>   }
...> }
...>
...> AshOaskit.SpecModifier.add_webhook(spec, "newPost", webhook)

apply_modifier(spec, fun)

@spec apply_modifier(map(), modifier()) :: map()

Applies modifications to an OpenAPI specification.

The modifier can be:

  • A function that takes the spec and returns the modified spec
  • An MFA tuple {module, function, args} where the spec is prepended to args
  • A list of modifiers to apply in sequence

Examples

iex> spec = %{"info" => %{"title" => "My API"}}
...>
...> AshOaskit.SpecModifier.apply_modifier(spec, fn s ->
...>   put_in(s, ["info", "version"], "2.0")
...> end)
%{"info" => %{"title" => "My API", "version" => "2.0"}}

iex> spec = %{"info" => %{}}
...> AshOaskit.SpecModifier.apply_modifier(spec, {Map, :put, ["info", %{"title" => "New"}]})
%{"info" => %{"title" => "New"}}

deprecation_modifier(opts \\ [])

@spec deprecation_modifier(keyword()) :: (map() -> map())

Creates a modifier function that adds deprecation notices to operations.

Examples

iex> modifier =
...>   AshOaskit.SpecModifier.deprecation_modifier(
...>     operations: ["oldGetPosts"],
...>     message: "Use listPosts instead",
...>     sunset: "2024-12-31"
...>   )

rate_limiting_modifier(opts \\ [])

@spec rate_limiting_modifier(keyword()) :: (map() -> map())

Creates a modifier function that adds rate limiting information.

This is a convenience function that creates a modifier for common rate limiting documentation patterns.

Options

  • :limit - Rate limit value (e.g., 100)
  • :window - Time window (e.g., "1 minute")
  • :headers - Custom header names for rate limit info

Examples

iex> modifier = AshOaskit.SpecModifier.rate_limiting_modifier(limit: 100, window: "1 minute")
...> spec = %{"paths" => %{"/posts" => %{"get" => %{}}}}
...> AshOaskit.SpecModifier.apply_modifier(spec, modifier)

set_servers(spec, servers)

@spec set_servers(map(), [map()]) :: map()

Replaces the servers list in the spec.

Useful for environment-specific server configuration.

Examples

iex> spec = %{"servers" => [%{"url" => "/"}]}
...> servers = [%{"url" => "https://api.prod.example.com"}]
...> AshOaskit.SpecModifier.set_servers(spec, servers)
%{"servers" => [%{"url" => "https://api.prod.example.com"}]}

update_info(spec, info_updates)

@spec update_info(map(), map()) :: map()

Modifies the info section of the spec.

Examples

iex> spec = %{"info" => %{"title" => "API", "version" => "1.0"}}
...>
...> AshOaskit.SpecModifier.update_info(spec, %{
...>   "contact" => %{"email" => "support@example.com"},
...>   "license" => %{"name" => "MIT"}
...> })