Skip to content

Elixir SDK Generator for OpenAPI V3 Specifications

License

Notifications You must be signed in to change notification settings

mnussbaumer/ex_oapi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ExOAPI

ExOAPI is an Elixir library for generating Client SDK's from OpenAPI v3 json specs.

Note this is a very very early release of the library. It has been used with Docusign's eSign REST spec, Plaid, and Flagsmith - but not extensively. Please check the CAVEATS to be aware of possible shortcomings, non-implemented details, and possible future changes.

ExOAPI.generate(%{
  source: Path.join([File.cwd!(), "priv/docusign_oapi.json"]),
  output_path: "/home/work-base/code/docusign_sdk",
  output_type: :app,
  title: "Docusign"
})

Installation

Add ex_oapi to your list of dependencies in mix.exs:

def deps do
  [
    {:ex_oapi, "~> 0.1.2"}
  ]
end

Usage

Generator

ExOAPI.generate(%{
  source: "full_path_to_an_open_api_v3_json_spec",
  output_path: "full_path_of_where_to_place_the_generated_sdk",
  output_type: :app,
  title: "AppModule.Name.To.Use"
})

This generates a default SDK where optional arguments are passed as option lists. You can instead generated it with either positional arguments instead, or in a map form.

ExOAPI.generate(%{
  source: "full_path_to_an_open_api_v3_json_spec",
  output_path: "full_path_of_where_to_place_the_generated_sdk",
  output_type: :app,
  title: "AppModule.Name.To.Use",
  generator: %{optional_args: :positional}
})

The final modules for the API will contain functions of the given form:

@doc """
  **summary**: Gets the status of a single envelope.

  **description**: Retrieves the overall status for the specified envelope.
  To get the status of a list of envelopes, use
  [Envelope: listStatusChanges ](https://developers.docusign.com/docs/esign-rest-api/reference/envelopes/envelopes/liststatuschanges/).

  ### Related topics

  - [How to get envelope information](https://developers.docusign.com/docs/esign-rest-api/how-to/get-envelope-information/)


  """
  @type get_envelope_opts :: {:include, String.t()} | {:advanced_update, String.t()}
  @spec get_envelope(
          client :: ExOAPI.Client.t(),
          envelope_id :: String.t(),
          account_id :: String.t(),
          list(get_envelope_opts())
        ) :: {:ok, any()} | {:error, any()}
  def get_envelope(%ExOAPI.Client{} = client, envelope_id, account_id, opts \\ []) do
    client
    |> ExOAPI.Client.set_module(Docusign.SDK)
    |> ExOAPI.Client.add_method(:get)
    |> ExOAPI.Client.add_base_url("https://www.docusign.net/restapi", :exoapi_default)
    |> ExOAPI.Client.add_path("/v2.1/accounts/{accountId}/envelopes/{envelopeId}")
    |> ExOAPI.Client.replace_in_path("envelopeId", envelope_id)
    |> ExOAPI.Client.replace_in_path("accountId", account_id)
    |> ExOAPI.Client.add_arg_opts(:keyword, :query, opts, [
      {:include, "include", "form", true},
      {:advanced_update, "advanced_update", "form", true}
    ])
    |> ExOAPI.Client.request()
  end

And schemas of the following form:

defmodule Flagsmith.Schemas.FeatureStateSerializerFull do
  use TypedEctoSchema
  import Ecto.Changeset

  @type params :: map()

  @moduledoc """
  **:enabled** :: *:boolean*


  **:environment** :: *:integer*


  **:feature** :: *Flagsmith.Schemas.Feature*


  **:feature_segment** :: *:integer*


  **:feature_state_value** :: *:string*


  **:id** :: *:integer*


  **:identity** :: *:integer*


  """

  @primary_key false
  typed_embedded_schema do
    field(:enabled, :boolean)

    field(:environment, :integer)

    embeds_one(:feature, Flagsmith.Schemas.Feature)

    field(:feature_segment, :integer)

    field(:feature_state_value, :string)

    field(:id, :integer)

    field(:identity, :integer)
  end

  @spec changeset(params()) :: Ecto.Changeset.t()
  @spec changeset(__MODULE__.t(), params()) :: Ecto.Changeset.t()
  def changeset(struct \\ %__MODULE__{}, params) do
    struct
    |> cast(params, [
      :identity,
      :id,
      :feature_state_value,
      :feature_segment,
      :environment,
      :enabled
    ])
    |> cast_embed(:feature, required: true)
  end
end

With proper embeds_one, embeds_many, Ecto.Enum, etc, and changeset validations appropriate to it.

Client

To use the generated SDK usually you will populate the lib/NAME_OF_LIB.ex file with some functions that wrap the instantiation of the needed bits to your client.

For instance, to use docusign's SDK that requires an Authorization header in most, you could do:

auth_token = go_get_my_auth_token_somehow()

client =
  %ExOAPI.Client{}
  |> ExOAPI.Client.add_header("Authorization", "Bearer #{auth_token}")
  |> ExOAPI.Client.add_middleware(Tesla.Middleware.JSON)
  |> ExOAPI.Client.add_base_url("https://demo.docusign.net/restapi/")

And now you could use this client for all subsequent requests. The client functions are exposed from lib/ex_oapi/client.ex. Underneath ExOAPI uses Tesla so you can configure tesla itself and also set things such as middlewares in the tesla format. Documentation is lacking atm and there's probably some work to polish the interface a bit but basically you can do:

add_options(client, opts) # to add options to be passed to the underlying tesla adapter
add_middleware(client, {_, _} | Middleware) # to add a tesla middleware
set_adapter(client, {_, _}) # to set a runtime adapter for the client
add_base_url(client, url) # to set the base_url the client will use
add_header(client, header_string, header_value) # add an header
add_query(client, key, value) # add a query parameter

You can also set a response handler (besides middlewares) that deals with the request response. This has no interface yet but you can set it manually on the client. It can accept mfa tuples or 2 arity function captures.

client = %ExOAPI.Client{client | response_handler: {Module, :fun, [extra_args]}}
client = %ExOAPI.Client{client | response_handler: {Module, :fun}}
client = %ExOAPI.Client{client | response_handler: &SomeModule.capture/2}

All of them get passed the response as the first argument, the client struct used in the request as the second, and, in case of the 3 sized tuple with anything other than an empty list, any arguments there specified.

Although this is not yet finished it allows you to deal with the tesla responses as you wish and can be set as part of your client instantiation.

If no response function is set the default is to parse them back when in JSON form to a schema if there's is one specified on the path spec, and in case the schema doesn't pass validation the original json body is returned (this behaviour will change and be configurable, the reason is that some specs specify schemas as the response that are either plainly wrong, or have required fields in their definition that are not present when receiving responses that use those schemas, and that makes validation fail)

Caveats

Currently there's no task like interface, so you'll need to wrap this lib or use it from an existing app to which you add the dependency and then call from an iex shell.

Although there's functionality to generate non-app SDK's (i.e. just the modules) it is not in its final form. The best would be to generate the app version and just move the contents of the lib/ directory.

Generating a SDK restricted to a set of paths already works, and ditches all unused schemas, while being able to track deeply nested refs in order to include all needed schemas somehow mentioned in the desired paths. Nonetheless it only supports being given the exact paths to include and is going to be re-written to support, partial paths, regexes and other ways of filtering, such as by tags.

Manipulating the resulting spec schemas prior to dumping them also has hooks but this can be done outside by manipulating the json itself prior to feeding it to ExOAPI and as of now it's the way you should go, because this functionality is not yet stable, nor documented, it should not be relied upon.

While most of the important and common OpenAPI 3 spec is covered, certain things aren't yet:

  • anyOf, noneOf, oneOf, not (allOf is implemented as it was the only one I've ran into yet)
  • discriminator
  • support and validation for payloads other than json
  • xml related functionality
  • callback's related functionality
  • replacement of relative url's in descriptions and external docs by automatic server config ones
  • refs that point to external urls are currently not supported

There's no support for other types of payloads besides json. You can pass whatever as a body to a given API function that takes one but it won't be validated, nor handled specifically, this includes files and such.

Besides that most of the internals will be re-written, but since it's already been useful to generate usable clients I thought it would be ok to share as is for the moment being.

If you give it a try and run into a problem or wish to contribute, feel free to open an issue to discuss it.

About

Cocktail Logo

© rooster image in the cocktail logo

Copyright

Copyright [2021-∞] [Micael Nussbaumer]

Permission is hereby granted, free of charge, to any person obtaining a copy of this 
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, 
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 
permit persons to whom the Software is furnished to do so, subject to the following 
conditions:

The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

About

Elixir SDK Generator for OpenAPI V3 Specifications

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published