open-api-spex / open_api_spex

Open API Specifications for Elixir Plug applications
Mozilla Public License 2.0
722 stars 185 forks source link

Is it possible top use values for enum came in runtime? #482

Open zoedsoupe opened 2 years ago

zoedsoupe commented 2 years ago

We have some Agents that are internal term dictionaries (maps) and the value of some external properties that will be exposed on our API swagger are those Agents (maps) keys.

I know schemas are a compile-time feature, so how could I avoid repeating these enums across my source code?

mbuhot commented 2 years ago

Yes you can define the schema at runtime and share enum values between schemas.

If it's the same enum used in multiple schemas, you can adapt the example from https://swagger.io/docs/specification/data-models/enums/#reuse and reference a shared enum schema from other schemas.

Any module that exposes a schema/0 function can be used as a schema (https://github.com/open-api-spex/open_api_spex/blob/master/lib/open_api_spex/schema.ex#L144-L148), so you can create a runtime schema like:

defmodule MyApp.Schemas.RuntimeEnumExample do
  @behaviour OpenApiSpex.Schema

  @impl true
  def schema do
    %OpenApiSpex.Schema{
      title: "RuntimeEnumExample",
      description: "A description for this enumeration...",
      type: :string,
      # get the values at runtime...
      enum: Application.get_env(:my_app, :some_runtime_configuration)
    }
  end
end

Then elsewhere in your schema:

defmodule MyApp.Schemas.ApiResponseExample do
  require OpenApiSpex
  alias OpenApiSpex.Schema
  OpenApiSpex.schema(%{
    type: :object,
    title: "ApiResponseExample",
    description: "A description....",
    properties: %{
      id: %Schema{type: :string},
      status: MyApp.Schemas.RuntimeEnumExample,
    }
  })
end
zoedsoupe commented 2 years ago

Oh, thank you! That's is exactly what we need!

zoedsoupe commented 2 years ago

Ok, so I tried some options and now we have this:

defmodule CobrancaWeb.Swagger.Schemas.Financing.Status do
  @moduledoc false

  alias Cobranca.TermDictionary

  @behaviour OpenApiSpex.Schema

  @impl true
  def schema do
    status = TermDictionary.keys(:financing_status)

    %OpenApiSpex.Schema{
      title: "Status",
      description: "Status de Financiamento",
      type: :string,
      enum: status
    }
  end
end

However, it still trying to get values from compile time when Registry and Agent aren't alive 😕

== Compilation error in file lib/cobranca_web/swagger/schemas/financing/financing.ex==
** (ArgumentError) unknown registry: Cobranca.DictionaryRegistry
    (elixir 1.13.4) lib/registry.ex:1347: Registry.key_info!/1
    (elixir 1.13.4) lib/registry.ex:227: Registry.whereis_name/2
    (elixir 1.13.4) lib/gen_server.ex:1209: GenServer.whereis/1
    (elixir 1.13.4) lib/gen_server.ex:1017: GenServer.call/3
    lib/cobranca_web/swagger/schemas/financing/status.ex:10: CobrancaWeb.Swagger.Schemas.Financing.Status.schema/0
    (open_api_spex 3.11.0) lib/open_api_spex/schema.ex:403: OpenApiSpex.Schema.default/1
    (open_api_spex 3.11.0) lib/open_api_spex/schema.ex:327: anonymous fn/2 in OpenApiSpex.Schema.properties/1
    (stdlib 3.17) maps.erl:410: :maps.fold_1/3
    (open_api_spex 3.11.0) lib/open_api_spex/schema.ex:327: OpenApiSpex.Schema.properties/1
    lib/cobranca_web/swagger/schemas/financing/financing.ex:8: (module)
    (elixir 1.13.4) lib/kernel/parallel_compiler.ex:346: anonymous fn/5 in Kernel.ParallelCompiler.spawn_workers/7
zoedsoupe commented 2 years ago

Maybe is because OpenApiSpex is a struct, that are expanded at compile-time too

mbuhot commented 2 years ago

It looks like this line is the problem: https://github.com/open-api-spex/open_api_spex/blob/4f6b91b7bad9b8125e4c9d7e1ce0a7c6518b2011/lib/open_api_spex/schema.ex#L331

It’s computing the default value of each property at compile time, ~but it appears to discard the results~ 🤔

~Maybe try changing that functions to use Map.keys instead.~

Edit: sorry I misread the code. It is returning a default value for each property for use in the struct definition.

We might need to add another optional callback to the schema behaviour to avoid evaluating the schema at compile time.