oapi_generator

repository·main·Indexed 41 days ago

https://github.com/aj-foster/open-api-generator

An Elixir library for creating ergonomic, developer-friendly client libraries from OpenAPI descriptions. It provides a fully automated workflow via the `mix api.gen` task, supporting configuration profiles, custom plugins for processing and rendering, and flexible output options for modules, schemas, and error types.

Tokens
4.7K
Snippets
15
Records
27
Agent score
100%

What's inside oapi_generator

  1. How to handle undocumented API endpoints using supplemental specs

    main

    OpenAPI descriptions often lack coverage for certain endpoints (e.g., ID-based endpoints that are not publicly documented).

    To include these in your generated client, do not modify the official specification. Instead, maintain a separate, supplemental OpenAPI description document containing only the information required to generate the missing endpoints. You can then commit this small supplemental description alongside your client code.

  2. Extend the generator with plugins

    main
    If the standard configuration options do not meet your needs, you can extend the generator's behavior using plugins. The generator's core processing and code rendering logic are implemented as default callbacks for a behaviour. By implementing your own version of this behaviour, you can override these callbacks to customize how code is generated.
  3. How the OpenAPI Generator phases work

    main

    The code generation process is divided into three distinct phases, each using an OpenAPI.State struct to track changes and its own intermediate state struct:

    1. Read: Reads the API description (JSON or YAML), dereferences it, and creates an in-memory Elixir representation of the specification.
    2. Process: Converts the dereferenced description into specialized structures optimized for code generation.
    3. Render: Uses an AST-based renderer to transform the processed API description into formatted Elixir code written to the filesystem.
  4. Implement the client request function

    main

    By default, all generated operation functions call client.request/1.

    To make the generated library functional, you must provide a module that implements this function. You can specify which module to use via the output.default_client configuration option. Inside request/1, you can use any HTTP client and implement your preferred error handling logic.

  5. Establish a release cadence for generated client libraries

    main

    When using this generator to build a library, you are creating a client for an OpenAPI specification. Because specifications change, you must decide how frequently to regenerate and release your code.

    Recommended strategies include:

    • Rapid/As-needed updates: Use this if the specification adds new endpoints that users want immediately. You can automate this with a scheduled CI job that re-generates code and opens a pull request.
    • Periodic updates: Use this if the specification frequently includes fixes or corrections. This reduces maintenance burden while keeping the library stable.
    • Version-following: If the specification is well-versioned, align your library's release cadence with the spec's version. It is recommended to include an .api-version file in your release to explicitly communicate which API version your client supports.
  6. Install oapi_generator via Hex

    main

    Add oapi_generator to your mix.exs dependencies. For most use cases, it is recommended to include it only in the development environment as a non-runtime dependency, as you typically only need the mix api.gen task during development.

    If you need to run the generator in production or during testing, adjust the only: :dev and runtime: false options accordingly.

    def deps do
      [
        {:oapi_generator, "~> 0.4.0", only: :dev, runtime: false}
      ]
    end
  7. Guidelines for contributing updates to generated code

    main

    If you are managing a library that uses this generator, you can provide contribution guidelines to make it easier for others to submit pull requests that update the generated code.

    Effective guidelines should include instructions to:

    1. Use the latest commit from the official specification repository.
    2. Specify the exact path to the target specification file.
    3. Use the mix api.gen default <path_to_spec> command to regenerate code.
    4. Update the .api-version file if the specification version has changed.
    5. Include the commit SHA of the official specification repository in the PR description.
    6. Keep the PR focused strictly on the generated code changes (avoid changing the library version in the same PR).
    mix api.gen default path/to/descriptions-next/api.github.com/api.github.com.yaml
  8. Migrate configuration from 0.0.x to 0.1.0

    main

    Version 0.1.0 introduced a major rewrite that reorganized configuration keys into logical groups (output and naming). If you are upgrading from 0.0.x, you must move your existing keys into these new nested structures.

    Key Mappings:

    | Old Key | New Key | | :--- | :| | base_location | output.location | | base_module | output.base_module | | default_client | output.default_client | | extra_fields | output.extra_fields | | operation_location | output.operation_subdirectory | | schema_location | output.schema_subdirectory | | schema_use | output.schema_use | | types | output.types | | group | naming.group | | merge | naming.merge | | operation_default_module | naming.default_operation_module | | operation_use_tags | naming.operation_use_tags | | rename | naming.rename |

    # Old 0.0.x configuration
    config :oapi_generator, default: [
      base_module: MyClient
    ]
    
    # New 0.1.0 configuration
    config :oapi_generator, default: [
      output: [
        base_module: MyClient
      ]
    ]
  9. Adopt new type representations in 0.1.0

    main
    In version 0.1.0, the internal representations of types returned from field/1 schema functions were changed or added. If your implementation relies on these types (for example, within a typed decoder), you must update your code to use the new structures defined in OpenAPI.Processor.Type.
  10. Use configuration profiles to manage multiple settings

    main

    The library uses configuration profiles to allow multiple sets of configuration to coexist. All configuration for a specific profile is nested under a top-level key in your config/config.exs.

    To use a profile, specify its name when running the generator via the CLI or the OpenAPI.run/2 function.

    CLI usage: mix api.gen <profile_name> <path_to_description.json>

    Programmatic usage: `OpenAPI.run("<profile_name>", ["path/to/description.json"])

    If you are unsure what to name your profile, use default.

    # config/config.exs
    
    import Config
    
    config :oapi_generator,
      my_profile: [
        output: [
          base_module: MyClientLibrary
        ]
      ]
  11. Configure OpenAPI Generator via config/config.exs

    main

    Configuration for the OpenAPI Generator is defined using nested keyword lists within a profile in your config/config.exs file. All configuration values must be contained within a profile (e.g., the default profile).

    Example structure:

    config :oapi_generator, default: [
      output [
        base_module: MyLibrary
      ],
      naming [
        default_operation_module: MyLibrary.Operations
      ]
    ]
    config :oapi_generator, default: [
      output [
        base_module: ...
      ]
    ]
  12. Create a custom plugin by overriding Processor or Renderer behaviours

    main

    To implement custom workflows, you can override the default processor or renderer modules in your configuration profile.

    By using use OpenAPI.Processor or use OpenAPI.Renderer in your module, you automatically receive default implementations for all callbacks. This allows you to implement only the specific callbacks you need to override.

    Implementation Steps

    1. Define your module and use the appropriate behaviour:

      defmodule MyLibrary.Renderer do
        use OpenAPI.Renderer
      
        # Override a specific callback
        def write(state, file) do
          # custom implementation
        end
      end
    2. Register your module in your application configuration under a profile key:

    # config/config.exs
    
    config :oapi_generator,
      my_profile: [
        renderer: MyLibrary.Renderer
      ]