Open API Spex

repository·master·Indexed 21 days ago

https://github.com/open-api-spex/open_api_spex

A tool for Elixir and Phoenix developers to document, test, and validate Plug and Phoenix APIs using the OpenAPI Specification (Swagger). It enables generating JSON/YAML specs from code, validating request/response schemas via the CastAndValidate plug, and serving interactive documentation through SwaggerUI.

Tokens
11.7K
Snippets
54
Records
59
Agent score
71%

What's inside open_api_spex

  1. Explore the PlugApp implementation structure

    master

    The PlugApp example is organized into three main components to demonstrate how open_api_spex integrates with a Plug-based API:

    • API Specification: lib/plug_app/api_spec.ex defines the outline of the OpenAPI specification.
    • Schemas: lib/plug_app/schemas.ex contains the modules used for defining request and response schemas.
    • Routing: lib/plug_app/router.ex contains the Plug router that handles incoming requests.
  2. Configure Authorization in the Main Spec

    master

    If your API requires authorization, define security schemes within the components key of your main spec. You can then declare the security requirements globally in the main spec or per individual operation.

    # Define the scheme in components
    components: %Components{
      securitySchemes: %{"authorization" => %SecurityScheme{type: "http", scheme: "bearer"}}
    }
    
    # Declare it globally in the spec
    security: [%{"authorization" => []}]
  3. Serve the API Spec and Swagger UI

    master

    To expose your API spec and an interactive Swagger UI, use the following plugs in your Phoenix pipeline:

    1. OpenApiSpex.Plug.PutApiSpec: Add this to your API pipeline to make the spec available to downstream plugs.
    2. OpenApiSpex.Plug.RenderSpec: Use this to render the spec as a JSON endpoint.
    3. OpenApiSpex.Plug.SwaggerUI: Use this to serve the interactive Swagger UI. You must provide the path: option pointing to your JSON spec endpoint.

    Development Tip: Disable caching in development to ensure the spec refreshes automatically:

    config :open_api_spex, :cache_adapter, OpenApiSpex.Plug.NoneCache
    # 1. Setup the pipeline and RenderSpec
    scope "/api" do
      pipe_through :api
      plug OpenApiSpex.Plug.PutApiSpec, module: MyAppWeb.ApiSpec
      
      get "/openapi", OpenApiSpex.Plug.RenderSpec, []
    end
    
    # 2. Setup Swagger UI
    scope "/" do
      pipe_through :browser
      get "/swaggerui", OpenApiSpex.Plug.SwaggerUI, path: "/api/openapi"
    end
  4. Validate examples and responses with TestAssertions

    master

    The OpenApiSpex.TestAssertions module provides tools to ensure your API implementation stays in sync with your documentation.

    Asserting that an example matches a schema

    Use assert_schema/2 to verify that the example data generated by a schema matches the overall API specification.

    Asserting that a response matches a schema

    Use assert_schema/2 to verify that the actual JSON response returned by a controller matches the expected schema in your API spec.

    import OpenApiSpex.TestAssertions
    
    # Validate an example
    assert_schema(schema.example, "UsersResponse", api_spec)
    
    # Validate a controller response
    assert_schema(json_response, "UsersResponse", api_spec)
  5. Import an existing JSON or YAML schema

    master

    You can import existing JSON or YAML encoded OpenAPI schemas and cast them into an %OpenApi{} struct using OpenApiSpex.OpenApi.Decode.decode/1.

    Warning: This functionality converts Strings into Atoms, which may be vulnerable to DoS attacks. Only load schemas from known files during application startup; do not load them dynamically from external sources at runtime.

    # Importing an existing JSON encoded schema
    open_api_spec_from_json = "encoded_schema.json"
      |> File.read!()
      |> Jason.decode!()
      |> OpenApiSpex.OpenApi.Decode.decode()
    
    # Importing an existing YAML encoded schema
    open_api_spec_from_yaml = "encoded_schema.yaml"
      |> YamlElixir.read_all_from_file!()
      |> List.first()
      |> OpenApiSpex.OpenApi.Decode.decode()
  6. Create a Main API Spec

    master

    To generate an OpenAPI specification, create a module that implements the OpenApi behaviour. This module populates an %OpenApiSpex.OpenApi{} struct. You can use OpenApiSpex.Server.from_endpoint/1 to automatically populate server information from a Phoenix endpoint and OpenApiSpex.Paths.from_router/1 to populate paths from a Phoenix router. Finally, call OpenApiSpex.resolve_schema_modules/1 to discover request/response schemas defined in your operations.

    defmodule MyAppWeb.ApiSpec do
      alias OpenApiSpex.{Components, Info, OpenApi, Paths, Server}
      alias MyAppWeb.{Endpoint, Router}
      @behaviour OpenApi
    
      @impl OpenApi
      def spec do
        %OpenApi{
          servers: [
            Server.from_endpoint(Endpoint)
          ],
          info: %Info{
            title: "My App",
            version: "1.0"
          },
          paths: Paths.from_router(Router)
        }
        |> OpenApiSpex.resolve_schema_modules()
      end
    end
  7. Run the PlugApp example application

    master

    To run the PlugApp demonstration, which shows how to use open_api_spex within a Plug API application, execute the following commands in your terminal to install dependencies, set up the database, and start the server:

    1. Fetch dependencies: mix deps.get
    2. Create the database: mix ecto.create
    3. Run migrations: mix ecto.migrate
    4. Start the application: mix run --no-halt

    Once running, you can view the generated Swagger UI at http://localhost:4000/swaggerui.

    mix deps.get
    mix ecto.create
    mix ecto.migrate
    mix run --no-halt
  8. Validate and cast parameters using CastAndValidate plug

    master

    OpenApiSpex can automatically validate request parameters and cast them to Elixir types defined in your operation schema before they reach your controller action.

    To use this, you must first ensure plug OpenApiSpex.Plug.PutApiSpec is called in your Router. Then, add OpenApiSpex.Plug.CastAndValidate to your controller.

    Configuration Options

    • json_render_error_v2: true: A workaround for the default error renderer format (not needed in version 4.0).
    • operation_id: Required for non-Phoenix Plugs to identify the operation. In Phoenix, this is inferred from conn.private.
    • render_error: MyErrorRendererPlug: Allows you to provide a custom plug to shape the error response JSON.

    Data Access in Controller

    Once cast, the following are available in the conn:

    • conn.body_params: The request body cast to your specified schema struct.
    • conn.params: A map combining path, query, and header parameters, with values cast to their defined types.
    # Phoenix
    plug OpenApiSpex.Plug.CastAndValidate, json_render_v2: true
    
    # Plug
    plug OpenApiSpex.Plug.CastAndValidate, json_render_error_v2: true, operation_id: "UserController.create"
  9. The Paths type definition

    master

    In OpenApiSpex.Paths, the t() type represents the OpenAPI Paths Object. It is a map where the keys are the relative URL paths (strings) and the values are PathItem.t() objects containing the operations (GET, POST, etc.) available at that path.

    Note that paths are appended to the URL from the Server Object to construct full URLs. The Paths object may be empty if ACL constraints apply.