absinthe.schema.json.ex (4214B)
1 defmodule Mix.Tasks.Absinthe.Schema.Json do 2 require Logger 3 use Mix.Task 4 import Mix.Generator 5 6 @shortdoc "Generate a schema.json file for an Absinthe schema" 7 8 @default_filename "./schema.json" 9 10 @moduledoc """ 11 Generate a `schema.json` file 12 13 ## Usage 14 15 mix absinthe.schema.json [OPTIONS] [FILENAME] 16 17 The JSON codec to be used needs to be included in your `mix.exs` dependencies. If using the default codec, 18 see the Jason [installation instructions](https://hexdocs.pm/jason). 19 20 ## Options 21 22 * `--schema` - The name of the `Absinthe.Schema` module defining the schema to be generated. 23 Default: As [configured](https://hexdocs.pm/mix/Mix.Config.html) for `:absinthe` `:schema` 24 * `--json-codec` - Codec to use to generate the JSON file (see [Custom Codecs](#module-custom-codecs)). 25 Default: [`Jason`](https://hexdocs.pm/jason/) 26 * `--pretty` - Whether to pretty-print. 27 Default: `false` 28 29 ## Examples 30 31 Write to default path `#{@default_filename}` using the `:schema` configured for the `:absinthe` application: 32 33 mix absinthe.schema.json 34 35 Write to default path `#{@default_filename}` using the `MySchema` schema: 36 37 mix absinthe.schema.json --schema MySchema 38 39 Write to path `/path/to/schema.json` using the `MySchema` schema, with pretty-printing: 40 41 mix absinthe.schema.json --schema MySchema --pretty /path/to/schema.json 42 43 Write to default path `#{@default_filename}` using the `MySchema` schema and a custom JSON codec, `MyCodec`: 44 45 mix absinthe.schema.json --schema MySchema --json-codec MyCodec 46 47 48 ## Custom Codecs 49 50 Any module that provides `encode!/2` can be used as a custom codec: 51 52 encode!(value, options) 53 54 * `value` will be provided as a Map containing the generated schema. 55 * `options` will be a keyword list with a `:pretty` boolean, indicating whether the user requested pretty-printing. 56 57 The function should return a string to be written to the output file. 58 """ 59 60 defmodule Options do 61 @moduledoc false 62 63 defstruct filename: nil, schema: nil, json_codec: nil, pretty: false 64 65 @type t() :: %__MODULE__{ 66 filename: String.t(), 67 schema: module(), 68 json_codec: module(), 69 pretty: boolean() 70 } 71 end 72 73 @doc "Callback implementation for `Mix.Task.run/1`, which receives a list of command-line args." 74 @spec run(argv :: [binary()]) :: any() 75 def run(argv) do 76 Application.ensure_all_started(:absinthe) 77 78 Mix.Task.run("loadpaths", argv) 79 Mix.Task.run("compile", argv) 80 81 opts = parse_options(argv) 82 83 case generate_schema(opts) do 84 {:ok, content} -> write_schema(content, opts.filename) 85 {:error, error} -> raise error 86 end 87 end 88 89 @doc false 90 @spec generate_schema(Options.t()) :: {:error, binary()} | {:ok, String.t()} 91 def generate_schema(%Options{ 92 pretty: pretty, 93 schema: schema, 94 json_codec: json_codec 95 }) do 96 with {:ok, result} <- Absinthe.Schema.introspect(schema) do 97 content = json_codec.encode!(result, pretty: pretty) 98 {:ok, content} 99 else 100 {:error, reason} -> {:error, reason} 101 error -> {:error, error} 102 end 103 end 104 105 @doc false 106 @spec parse_options([String.t()]) :: Options.t() 107 def parse_options(argv) do 108 parse_options = [strict: [schema: :string, json_codec: :string, pretty: :boolean]] 109 {opts, args, _} = OptionParser.parse(argv, parse_options) 110 111 %Options{ 112 filename: args |> List.first() || @default_filename, 113 schema: find_schema(opts), 114 json_codec: json_codec_as_atom(opts), 115 pretty: Keyword.get(opts, :pretty, false) 116 } 117 end 118 119 defp json_codec_as_atom(opts) do 120 opts 121 |> Keyword.fetch(:json_codec) 122 |> case do 123 {:ok, codec} -> Module.concat([codec]) 124 _ -> Jason 125 end 126 end 127 128 defp find_schema(opts) do 129 case Keyword.get(opts, :schema, Application.get_env(:absinthe, :schema)) do 130 nil -> 131 raise "No --schema given or :schema configured for the :absinthe application" 132 133 value -> 134 [value] |> Module.safe_concat() 135 end 136 end 137 138 defp write_schema(content, filename) do 139 create_directory(Path.dirname(filename)) 140 create_file(filename, content, force: true) 141 end 142 end