View Source OpenAPI.Config (OpenAPI Generator v0.0.8)

Configuration for the code generator

configuration

Configuration

This section is an overview of the available configuration options. For a more in-depth discussion of output module naming, see Naming below. See also type t:t() below for the exact type specification of each option.

  • base_location (string, required): Relative path (from the base of the Mix project where the code generator is run) to output files. When creating a standalone client library, this will often be lib/.

  • base_module (module, required): Base module for all output files. For example, a base module of Example will output schemas like Example.MySchema and operation files like Example.MyOperations. When outputting a standalone client library, this will often be the base module of the library.

  • default_client (module): Module to call when making a web request. This code generator has no opinion on how HTTP requests are made. Instead, you must provide a client module that implements a request/1 function and performs the request and decodes the response. By default, a module [base_module].Client will be used.

  • extra_fields (keyword list): Additional fields to add to each schema. The key is the name of the field, and the value is the type (as defined by type/0. For example, [__info__: :map] will add a field named __info__ with type map to every schema. This can be useful for library authors that would like to store additional information in the structs returned by the client operations. Defaults to [].

  • group (list of modules): Namespaces to use when grouping modules. For example, two schemas SchemaOne and SchemaTwo grouped by the Schema module would become Schema.One and Schema.Two. Defaults to no grouping.

  • ignore (list of modules, strings, or regular expressions): Schemas to ignore when outputting well-defined Elixir structs. Schemas that are ignored will be replaced with a map type when referenced elsewhere in the code. When provided as a module or string, the ignore pattern must exactly match the name of the schema after any merges. Defaults to no schemas ignored.

  • merge (list of two-tuples): Source and destination modules for schemas that should be merged into a single file. See Merging below for examples. Defaults to no schemas merged.

  • operation_default_module (module): Module name that will be appended to the base_module when generating operation modules and the operation has no tags or operation_use_tags is set to false. Defaults to Operation. See Operations below for details on operations generation.

  • operation_location (string): Relative path, after the base_location, to output operation files. This may useful if you want to hide generated operation files in a subdirectory of a larger project. Defaults to outputting operation files to the base_location.

  • operation_use_tags (boolean): Whether to use OpenAPI specification tags when generating the operation modules names. Defaults to true. See Operations below for details on operation generation.

  • rename (list of rename pattern and action tuples): Renaming actions to take on schema names. The two elements of each tuple will be fed as the second and third arguments to String.replace/4 along with the schema name. See Renaming below for examples. Defaults to no schemas renamed.

  • schema_location (string): Relative path, after the base_location, to output schema files. This may useful if you want to hide generated schema files in a subdirectory of a larger project. Defaults to outputting schema files to the base_location.

  • :schema_use (module): Optional module that should be included in a use [Module] statement in each schema. This allows library authors to implement a __using__/1 macro with additional functionality. The __using__/1 macro currently does not receive any options, but it may in the future. Defaults to nil, meaning no use statement is included.

  • types (keyword list): Overrides to the types defined by the generator. Each value should be a tuple {module, type} such as {MyModule, :t}.

    • error: Override the error type for all operations. APIs often define their own error schemas, which may differ between operations. Use this option to define a single, consistent error type for all operations. For example, a value {MyError, :t} would cause operations to return {:ok, ...} | {:error, MyError.t()}.

naming

Naming

Most of the configuration of this project relates to the manipulation of schema names. It is important to understand the order of operations. As an example, imagine an OpenAPI description has the following schemas:

  • #/components/schemas/simple-user
  • #/components/schemas/user
  • #/components/schemas/user-preferences

And the following configuration:

config :oapi_generator, default: [
  base_location: "lib/",
  base_module: Example,
  group: [User],
  ignore: [],
  merge: [{"SimpleUser", "User"}]
  rename: [{~r/Preferences/, "Settings"}]
]

In this case, naming would proceed as follows:

  1. Schemas in the OpenAPI descriptions are turned into Elixir modules:

    #/components/schemas/simple-user       =>  SimpleUser.t()
    #/components/schemas/user              =>  User.t()
    #/components/schemas/user-preferences  =>  UserPreferences.t()
  2. Merge settings are applied based on the original names of the schemas:

    SimpleUser.t()  =>  User.simple()
  3. Ignore settings are applied based on the merged module names (no changes in this example).

  4. Rename settings are applied based on the merged module names:

    UserPreferences.t()  =>  UserSettings.t()
  5. Group settings are applied based on the renamed module names:

    UserSettings.t()  =>  User.Settings.t()
  6. The base module is applied to get the final names:

    User.simple()      =>  Example.User.simple()
    User.t()           =>  Example.User.t()
    User.Settings.t()  =>  Example.User.Settings.t()

All of the schemas are then written to appropriate files based on the base_location and schema_location settings. Note that User.simple() and User.t() will end up in the same file as a result of the merge, sharing the same struct for their responses (with distinct typespecs).

merging

Merging

OpenAPI descriptions may have multiple schemas that are closely related or even duplicated. Merging gives the power to consolidate these schemas into a single struct that is easy to use.

For example, the GitHub API description has schemas repository, full-repository, and nullable-repository. While the "full" repository adds additional properties, the "nullable" variant is just that: all of the same properties, but the schema is nullable. This kind of oddity in the OpenAPI specification is exactly what makes most generated code difficult to use.

The following merge settings would help clean this up:

merge: [
  {"FullRepository", "Repository"},
  {~r/^Nullable/, ""}
]

In the first line, we tell the generator to merge FullRepository into Repository (the original module names based on the names of the schemas). Because the destination module appears at the end of the original module, the word "Repository" will be dropped from the type:

FullRepository => Repository :: Repository.full()

This renaming of the type is automatic for prefixes and suffixes. If no overlap is found, then the full (underscored) schema name will be used for the type:

SimpleUser        => User        :: User.simple()
PullRequestSimple => PullRequest :: PullRequest.simple()
MySchema          => Unrelated   :: Unrelated.my_schema()

If the destination module is later renamed or grouped, the merged schemas will processed in the same way.

collapsing

Collapsing

In the second line of the configuration above, we merge two nearly-identical schemas NullableRepository and Repository. Because these schemas have the same fields, there will not be a Repository.nullable() type generated; instead, references will use Repository.t(). Despite this deduplication, other parts of the code will continue to know that the original schema had nullable: true and respond accordingly.

ignoring

Ignoring

Sometimes, schemas are best treated as plain maps. In these cases, they can be ignored using a regular expression, exact string, or exact module:

ignore: [
  ~r/^Unnecessary/,
  "SomeSchema",
  AnotherSchema
]

Any references to an ignored schema will be replaced with a map() type.

grouping

Grouping

Schemas in an OpenAPI description can have extensively long names. For example, GitHub has a schema called actions-cache-usage-by-repository. Along with all other actions-related schemas, we can cut down the top-level module namespace by grouping on Actions or even further:

group: [
  Actions,
  Actions.CacheUsage
]

Even simple renaming and groups can take a raw OpenAPI description and turn it into a library that feels friendly to users.

operations

Operations

Operations are the API entrypoint, which normally will be called by the user of the library. This generator generates a set of modules with functions in them according to some normalization rules:

  • Operation tags and IDs will be normalized for spaces, slashes, etc.
  • Operation tags will be used to generate modules that group operation functions
  • Operations with slashes will be split, with each component used to generate the module hierarchy of the operation

Examples:

  • Operation foo with tag bar => Bar.foo
  • Operation foo/bar with tag baz => Baz.foo_bar
  • Operation foo/bar without tags => Foo.bar

Further examples can be found in the tests for OpenAPI.Generator.OperationTest.names/1.

To summarize, the generator uses tags to create modules containing the operation functions. If tags are not present, module names will be created from the operation's ID if it contains slashes.

If the operation has no slashes and no tags, the generator cannot infer a proper module name. That's where the operation_default_module config option comes in place. For such operations, the operation_default_module will be used as container for the operation.

Examples:

  • Operation foo without tags => [base_module].Operation.foo

Since OpenAPI tags are not strictly part of the specification, you can also decide to not use them at all with the option operation_use_tags set to false. This will put all operations into a single module specified by operation_default_module. There's no risk of functions conflict since by definition operation IDs are unique on a given OpenAPI specification.

Link to this section Summary

Types

Keyword list of extra fields to add to each schema

List of module namespaces to create when grouping

List of patterns or exact matches of schemas to ignore

Patterns or exact matches of schemas to ignore

Before (pattern or exact match) and after (replacement action) for merging schemas by module

Replacement action for renaming schemas by module

List of replacement searches and actions for renaming schemas by module

Search pattern for renaming schemas by module

t()

Configuration for the code generator

Runtime type annotation

Link to this section Types

@type extra_fields() :: keyword(type())

Keyword list of extra fields to add to each schema

@type group_options() :: [module()]

List of module namespaces to create when grouping

@type ignore_options() :: [ignore_pattern()]

List of patterns or exact matches of schemas to ignore

@type ignore_pattern() :: Regex.t() | String.t() | module()

Patterns or exact matches of schemas to ignore

@type merge_options() :: [{Regex.t() | String.t() | module(), String.t()}]

Before (pattern or exact match) and after (replacement action) for merging schemas by module

@type rename_action() :: String.t() | (String.t() -> String.t())

Replacement action for renaming schemas by module

@type rename_options() :: [{rename_pattern(), rename_action()}]

List of replacement searches and actions for renaming schemas by module

@type rename_pattern() :: String.pattern() | Regex.t()

Search pattern for renaming schemas by module

@type t() :: %OpenAPI.Config{
  base_location: String.t(),
  base_module: module(),
  default_client: module(),
  extra_fields: extra_fields(),
  group: group_options(),
  ignore: ignore_options(),
  merge: merge_options(),
  operation_default_module: module(),
  operation_location: String.t(),
  operation_use_tags: boolean(),
  rename: rename_options(),
  schema_location: String.t(),
  schema_use: module(),
  types: keyword()
}

Configuration for the code generator

@type type() ::
  :binary
  | :boolean
  | :integer
  | :map
  | :number
  | :string
  | :null
  | :unknown
  | {:array, t()}
  | {:union, [t()]}
  | {:nullable, t()}
  | {module(), atom()}

Runtime type annotation