Skip to content

Commit

Permalink
improvement: add installers & extenders
Browse files Browse the repository at this point in the history
improvement: automatically infer the `prefix` instead of relying on configuration

and use it always since its never wrong, preventing a common class of misconfigurations.
  • Loading branch information
zachdaniel committed Jul 19, 2024
1 parent 057b0de commit b46c979
Show file tree
Hide file tree
Showing 43 changed files with 423 additions and 113 deletions.
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
erlang 26.0.2
elixir 1.15.4
erlang 27.0.1
elixir 1.17.2-otp-27
12 changes: 12 additions & 0 deletions documentation/topics/open-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ mix openapi.spec.json --spec MyAppWeb.AshJsonApi
mix openapi.spec.yaml --spec MyAppWeb.AshJsonApi
```

> ### Setting a route prefix for generated files {: .warning}
>
> The route prefix in normal usage is automatically inferred, but when generating files
> we will use the `prefix` option set in the `json_api` section of the relevant `Ash.Domain` module.

To generate the YAML file you need to add the ymlr dependency.

```elixir
Expand All @@ -136,6 +141,13 @@ def deps do
end
```

You can also use the `--check` option to confirm that your checked in spec file(s) match.

```sh
mix openapi.spec.json --spec MyAppWeb.AshJsonApi --check
mix openapi.spec.yaml --spec MyAppWeb.AshJsonApi --check
```

## Known issues/limitations

### Swagger UI
Expand Down
4 changes: 0 additions & 4 deletions documentation/tutorials/getting-started-with-ash-json-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,6 @@ Add the following to your `config/config.exs`.
config :mime, :types, %{
"application/vnd.api+json" => ["json"]
}

config :mime, :extensions, %{
"json" => "application/vnd.api+json"
}
```

This configuration is required to support working with the JSON:API custom mime type.
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/controllers/delete.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule AshJsonApi.Controllers.Delete do
all_domains = options[:all_domains]

conn
|> Request.from(resource, action, domain, all_domains, route)
|> Request.from(resource, action, domain, all_domains, route, options[:prefix])
|> Helpers.destroy_record()
|> Helpers.fetch_includes()
|> Helpers.fetch_metadata()
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/controllers/delete_from_relationship.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule AshJsonApi.Controllers.DeleteFromRelationship do
end

conn
|> Request.from(options[:resource], action, domain, all_domains, route)
|> Request.from(options[:resource], action, domain, all_domains, route, options[:prefix])
|> Helpers.update_record(&Helpers.resource_identifiers(&1, argument))
|> Helpers.fetch_metadata()
|> Helpers.render_or_render_errors(conn, fn conn, request ->
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/controllers/generic_action_route.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule AshJsonApi.Controllers.GenericActionRoute do
all_domains = options[:all_domains]

conn
|> Request.from(resource, action, domain, all_domains, route)
|> Request.from(resource, action, domain, all_domains, route, options[:prefix])
|> Helpers.run_action()
|> Helpers.render_or_render_errors(conn, fn conn, request ->
status =
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/controllers/get.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule AshJsonApi.Controllers.Get do
all_domains = options[:all_domains]

conn
|> Request.from(resource, action, domain, all_domains, route)
|> Request.from(resource, action, domain, all_domains, route, options[:prefix])
|> Helpers.fetch_record_from_path()
|> Helpers.fetch_includes()
|> Helpers.fetch_metadata()
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/controllers/get_related.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule AshJsonApi.Controllers.GetRelated do
all_domains = options[:all_domains]

conn
|> Request.from(resource, action, domain, all_domains, route)
|> Request.from(resource, action, domain, all_domains, route, options[:prefix])
|> Helpers.fetch_related(options[:resource])
|> Helpers.fetch_includes()
|> Helpers.fetch_metadata()
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/controllers/get_relationship.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule AshJsonApi.Controllers.GetRelationship do
resource = relationship.destination

conn
|> Request.from(resource, action, domain, all_domains, route)
|> Request.from(resource, action, domain, all_domains, route, options[:prefix])
|> Helpers.fetch_related(options[:resource])
|> Helpers.fetch_metadata()
|> Helpers.render_or_render_errors(conn, fn conn, request ->
Expand Down
4 changes: 2 additions & 2 deletions lib/ash_json_api/controllers/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule AshJsonApi.Controllers.Index do

if action.type == :read do
conn
|> Request.from(resource, action, domain, all_domains, route)
|> Request.from(resource, action, domain, all_domains, route, options[:prefix])
|> Helpers.fetch_pagination_parameters()
|> Helpers.fetch_records()
|> Helpers.fetch_includes()
Expand All @@ -33,7 +33,7 @@ defmodule AshJsonApi.Controllers.Index do
end)
else
conn
|> Request.from(resource, action, domain, all_domains, route)
|> Request.from(resource, action, domain, all_domains, route, options[:prefix])
|> Helpers.fetch_records()
|> Helpers.fetch_includes()
|> Helpers.fetch_metadata()
Expand Down
13 changes: 12 additions & 1 deletion lib/ash_json_api/controllers/open_api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,20 @@ if Code.ensure_loaded?(OpenApiSpex) do

# sobelow_skip ["XSS.SendResp"]
def call(conn, opts) do
prefix =
conn.request_path
|> Path.split()
|> Enum.reverse()
|> Enum.drop(Enum.count(conn.path_info))
|> Enum.reverse()
|> case do
[] -> "/"
paths -> Path.join(paths)
end

spec =
conn
|> spec(opts)
|> spec(Keyword.put(opts, :prefix, prefix))
|> Jason.encode!(pretty: true)

conn
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/controllers/patch.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule AshJsonApi.Controllers.Patch do
all_domains = options[:all_domains]

conn
|> Request.from(resource, action, domain, all_domains, route)
|> Request.from(resource, action, domain, all_domains, route, options[:prefix])
|> Helpers.update_record()
|> Helpers.fetch_includes()
|> Helpers.fetch_metadata()
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/controllers/patch_relationship.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule AshJsonApi.Controllers.PatchRelationship do
end

conn
|> Request.from(options[:resource], action, domain, all_domains, route)
|> Request.from(options[:resource], action, domain, all_domains, route, options[:prefix])
|> Helpers.update_record(&Helpers.resource_identifiers(&1, argument))
|> Helpers.fetch_metadata()
|> Helpers.render_or_render_errors(conn, fn conn, request ->
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/controllers/post.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule AshJsonApi.Controllers.Post do
all_domains = options[:all_domains]

conn
|> Request.from(resource, action, domain, all_domains, route)
|> Request.from(resource, action, domain, all_domains, route, options[:prefix])
|> Helpers.create_record()
|> Helpers.fetch_includes()
|> Helpers.fetch_metadata()
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/controllers/post_to_relationship.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule AshJsonApi.Controllers.PostToRelationship do
end

conn
|> Request.from(options[:resource], action, domain, all_domains, route)
|> Request.from(options[:resource], action, domain, all_domains, route, options[:prefix])
|> Helpers.update_record(&Helpers.resource_identifiers(&1, argument))
|> Helpers.fetch_metadata()
|> Helpers.render_or_render_errors(conn, fn conn, request ->
Expand Down
16 changes: 14 additions & 2 deletions lib/ash_json_api/controllers/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,28 @@ defmodule AshJsonApi.Controllers.Router do
end

def call(conn, %{domains: domains, open_api: open_api, json_schema: json_schema, original: opts}) do
prefix =
conn.request_path
|> Path.split()
|> Enum.reverse()
|> Enum.drop(Enum.count(conn.path_info))
|> Enum.reverse()
|> case do
[] -> "/"
paths -> Path.join(paths)
end

cond do
conn.method in ["GET", :get] &&
Enum.any?(domains, &AshJsonApi.Domain.Info.serve_schema?/1) &&
conn.path_info in [["schema"], ["schema.json"]] ->
AshJsonApi.Controllers.Schema.call(conn, domains: domains)
AshJsonApi.Controllers.Schema.call(conn, domains: domains, prefix: prefix)

open_api_request?(conn, open_api) ->
apply(AshJsonApi.Controllers.OpenApi, :call, [conn, opts])

conn.method == "GET" && Enum.any?(json_schema, &(&1 == conn.path_info)) ->
AshJsonApi.Controllers.Schema.call(conn, opts)
AshJsonApi.Controllers.Schema.call(conn, Keyword.put(opts, :prefix, prefix))

true ->
Enum.find_value(domains, :error, fn domain ->
Expand Down Expand Up @@ -78,6 +89,7 @@ defmodule AshJsonApi.Controllers.Router do
domain: domain,
resource: resource,
all_domains: domains,
prefix: prefix,
action_type: route.action_type,
route: route,
relationship: route.relationship,
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/controllers/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule AshJsonApi.Controllers.Schema do

schema =
domains
|> AshJsonApi.JsonSchema.generate()
|> AshJsonApi.JsonSchema.generate(prefix: opts[:prefix] || "")
|> Jason.encode!()

conn
Expand Down
52 changes: 52 additions & 0 deletions lib/ash_json_api/domain/domain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,56 @@ defmodule AshJsonApi.Domain do
verifiers: @verifiers,
persisters: @persisters,
transformers: @transformers

def install(igniter, module, Ash.Domain, _path, _argv) do
igniter
|> Spark.Igniter.add_extension(
module,
Ash.Domain,
:extensions,
AshJsonApi.Domain
)
|> add_to_ash_json_api_router(module)
end

defp add_to_ash_json_api_router(igniter, domain) do
case AshJsonApi.Igniter.find_ash_json_api_router(igniter, domain) do
{:ok, igniter, _} ->
igniter

{:error, igniter, []} ->
AshJsonApi.Igniter.setup_ash_json_api_router(igniter)

{:error, igniter, all_ash_json_api_routers} ->
ash_json_api_router =
case all_ash_json_api_routers do
[ash_json_api_router] ->
ash_json_api_router

ash_json_api_routers ->
Owl.IO.select(
ash_json_api_routers,
label: "Multiple AshJsonApi.Router modules found. Please select one to use:",
render_as: &inspect/1
)
end

Igniter.Code.Module.find_and_update_module!(igniter, ash_json_api_router, fn zipper ->
with {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, AshJsonApi.Router),
{:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 1),
{:ok, zipper} <- Igniter.Code.Keyword.get_key(zipper, :domains),
{:ok, zipper} <- Igniter.Code.List.append_to_list(zipper, domain) do
{:ok, zipper}
else
_ ->
{:warning,
"""
Could not add #{inspect(domain)} to the list of domains in #{inspect(ash_json_api_router)}.
Please make that change manually.
"""}
end
end)
end
end
end
Loading

0 comments on commit b46c979

Please sign in to comment.