mirror of
https://github.com/ash-project/ash_json_api_wrapper.git
synced 2024-09-20 05:12:51 +12:00
WIP
This commit is contained in:
parent
b541a64482
commit
de000a859c
5 changed files with 8903 additions and 2 deletions
|
@ -193,6 +193,8 @@ defmodule AshJsonApiWrapper.DataLayer do
|
||||||
),
|
),
|
||||||
do: true
|
do: true
|
||||||
|
|
||||||
|
def can?(_, :nested_expressions), do: true
|
||||||
|
|
||||||
def can?(_, :sort), do: true
|
def can?(_, :sort), do: true
|
||||||
def can?(_, {:sort, _}), do: true
|
def can?(_, {:sort, _}), do: true
|
||||||
def can?(_, _), do: false
|
def can?(_, _), do: false
|
||||||
|
@ -361,9 +363,11 @@ defmodule AshJsonApiWrapper.DataLayer do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp filter_instructions(filter, _resource, endpoint) do
|
defp filter_instructions(filter, resource, endpoint) do
|
||||||
fields =
|
fields =
|
||||||
endpoint.fields
|
endpoint.fields
|
||||||
|
|> Enum.concat(AshJsonApiWrapper.DataLayer.Info.fields(resource))
|
||||||
|
|> Enum.uniq_by(& &1.name)
|
||||||
|> List.wrap()
|
|> List.wrap()
|
||||||
|> Enum.filter(& &1.filter_handler)
|
|> Enum.filter(& &1.filter_handler)
|
||||||
|
|
||||||
|
@ -691,6 +695,8 @@ defmodule AshJsonApiWrapper.DataLayer do
|
||||||
|
|
||||||
defp make_request(resource, query) do
|
defp make_request(resource, query) do
|
||||||
# log_send(path, query)
|
# log_send(path, query)
|
||||||
|
IO.inspect(query.query_params)
|
||||||
|
|
||||||
AshJsonApiWrapper.DataLayer.Info.tesla(resource).get(query.path,
|
AshJsonApiWrapper.DataLayer.Info.tesla(resource).get(query.path,
|
||||||
body: query.body,
|
body: query.body,
|
||||||
query: query.query_params
|
query: query.query_params
|
||||||
|
@ -794,6 +800,7 @@ defmodule AshJsonApiWrapper.DataLayer do
|
||||||
else
|
else
|
||||||
case endpoint.entity_path do
|
case endpoint.entity_path do
|
||||||
nil ->
|
nil ->
|
||||||
|
IO.inspect(endpoint)
|
||||||
{:ok, List.wrap(body)}
|
{:ok, List.wrap(body)}
|
||||||
|
|
||||||
path ->
|
path ->
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule AshJsonApiWrapper.DataLayer.Transformers.SetEndpointDefaults do
|
||||||
def transform(dsl) do
|
def transform(dsl) do
|
||||||
base_entity_path = AshJsonApiWrapper.DataLayer.Info.base_entity_path(dsl)
|
base_entity_path = AshJsonApiWrapper.DataLayer.Info.base_entity_path(dsl)
|
||||||
base_paginator = AshJsonApiWrapper.DataLayer.Info.base_paginator(dsl)
|
base_paginator = AshJsonApiWrapper.DataLayer.Info.base_paginator(dsl)
|
||||||
base_fields = AshJsonApiWrapper.DataLayer.Info.fields(dsl)
|
base_fields = AshJsonApiWrapper.DataLayer.Info.fields(dsl) |> IO.inspect()
|
||||||
|
|
||||||
dsl
|
dsl
|
||||||
|> AshJsonApiWrapper.DataLayer.Info.endpoints()
|
|> AshJsonApiWrapper.DataLayer.Info.endpoints()
|
||||||
|
|
206
lib/open_api/resource_generator.ex
Normal file
206
lib/open_api/resource_generator.ex
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
defmodule AshJsonApiWrapper.OpenApi.ResourceGenerator do
|
||||||
|
def generate(json, main_config) do
|
||||||
|
main_config[:resources]
|
||||||
|
|> Enum.map(fn {resource, config} ->
|
||||||
|
endpoints =
|
||||||
|
json
|
||||||
|
|> operations(config)
|
||||||
|
|> Enum.take(1)
|
||||||
|
|> Enum.map_join("\n\n", fn {path, method, operation} ->
|
||||||
|
entity_path =
|
||||||
|
if config[:entity_path] do
|
||||||
|
"entity_path \"#{config[:entity_path]}\""
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
endpoint :#{operation_id(operation)} do
|
||||||
|
path "#{path}"
|
||||||
|
#{entity_path}
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
end)
|
||||||
|
|
||||||
|
actions =
|
||||||
|
json
|
||||||
|
|> operations(config)
|
||||||
|
|> Enum.take(1)
|
||||||
|
|> Enum.map_join("\n\n", fn
|
||||||
|
{path, "get", config} ->
|
||||||
|
"""
|
||||||
|
read :#{operation_id(config)}
|
||||||
|
"""
|
||||||
|
|
||||||
|
{path, "post", config} ->
|
||||||
|
"""
|
||||||
|
create :#{operation_id(config)}
|
||||||
|
"""
|
||||||
|
end)
|
||||||
|
|
||||||
|
fields =
|
||||||
|
config[:fields]
|
||||||
|
|> Enum.map_join("\n\n", fn {name, field_config} ->
|
||||||
|
filter_handler =
|
||||||
|
if field_config[:filter_handler] do
|
||||||
|
"filter_handler #{inspect(field_config[:filter_handler])}"
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
field #{inspect(name)} do
|
||||||
|
#{filter_handler}
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
end)
|
||||||
|
|> case do
|
||||||
|
"" ->
|
||||||
|
""
|
||||||
|
|
||||||
|
other ->
|
||||||
|
"""
|
||||||
|
fields do
|
||||||
|
#{other}
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, [object]} =
|
||||||
|
json
|
||||||
|
|> ExJSONPath.eval(config[:object_type])
|
||||||
|
|
||||||
|
attributes =
|
||||||
|
object
|
||||||
|
|> Map.get("properties")
|
||||||
|
|> Enum.map(fn {name, config} ->
|
||||||
|
{Macro.underscore(name), config}
|
||||||
|
end)
|
||||||
|
|> Enum.sort_by(fn {name, _} ->
|
||||||
|
name not in List.wrap(config[:primary_key])
|
||||||
|
end)
|
||||||
|
|> Enum.map_join("\n\n", fn {name, property} ->
|
||||||
|
type =
|
||||||
|
case property do
|
||||||
|
%{"enum" => values} ->
|
||||||
|
":atom"
|
||||||
|
|
||||||
|
%{"format" => "date-time"} ->
|
||||||
|
":utc_datetime"
|
||||||
|
|
||||||
|
%{"type" => "string"} ->
|
||||||
|
":string"
|
||||||
|
|
||||||
|
%{"type" => "integer"} ->
|
||||||
|
":integer"
|
||||||
|
|
||||||
|
other ->
|
||||||
|
raise "Unsupported property: #{inspect(other)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
constraints =
|
||||||
|
case property do
|
||||||
|
%{"enum" => values} ->
|
||||||
|
"one_of: #{inspect(Enum.map(values, &String.to_atom/1))}"
|
||||||
|
|
||||||
|
%{"maxLength" => max, "minLength" => min, "type" => "string"} ->
|
||||||
|
"min_length: #{min}, max_length: #{max}"
|
||||||
|
|
||||||
|
%{"maxLength" => max, "type" => "string"} ->
|
||||||
|
"max_length: #{max}"
|
||||||
|
|
||||||
|
%{"minLength" => min, "type" => "string"} ->
|
||||||
|
"min_length: #{min}"
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
primary_key? = name in List.wrap(config[:primary_key])
|
||||||
|
|
||||||
|
if constraints || primary_key? do
|
||||||
|
constraints =
|
||||||
|
if constraints do
|
||||||
|
"constraints #{constraints}"
|
||||||
|
end
|
||||||
|
|
||||||
|
primary_key =
|
||||||
|
if primary_key? do
|
||||||
|
"""
|
||||||
|
primary_key? true
|
||||||
|
allow_nil? false
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
attribute :#{name}, #{type} do
|
||||||
|
#{primary_key}
|
||||||
|
#{constraints}
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
else
|
||||||
|
"""
|
||||||
|
attribute :#{name}, #{type}
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
tesla =
|
||||||
|
if main_config[:tesla] do
|
||||||
|
"tesla #{main_config[:tesla]}"
|
||||||
|
end
|
||||||
|
|
||||||
|
endpoint =
|
||||||
|
if main_config[:endpoint] do
|
||||||
|
"base \"#{main_config[:endpoint]}\""
|
||||||
|
end
|
||||||
|
|
||||||
|
code =
|
||||||
|
"""
|
||||||
|
defmodule #{resource} do
|
||||||
|
use Ash.Resource, data_layer: AshJsonApiWrapper.DataLayer
|
||||||
|
|
||||||
|
json_api_wrapper do
|
||||||
|
#{tesla}
|
||||||
|
|
||||||
|
endpoints do
|
||||||
|
#{endpoint}
|
||||||
|
#{endpoints}
|
||||||
|
end
|
||||||
|
|
||||||
|
#{fields}
|
||||||
|
end
|
||||||
|
|
||||||
|
actions do
|
||||||
|
#{actions}
|
||||||
|
end
|
||||||
|
|
||||||
|
attributes do
|
||||||
|
#{attributes}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
|> Code.format_string!()
|
||||||
|
|> IO.iodata_to_binary()
|
||||||
|
|
||||||
|
{resource, code}
|
||||||
|
end)
|
||||||
|
|> Enum.take(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp operation_id(%{"operationId" => operationId}) do
|
||||||
|
operationId
|
||||||
|
|> Macro.underscore()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp operations(json, config) do
|
||||||
|
json["paths"]
|
||||||
|
|> Enum.filter(fn {path, _value} ->
|
||||||
|
String.starts_with?(path, config[:path])
|
||||||
|
end)
|
||||||
|
|> Enum.flat_map(fn {path, methods} ->
|
||||||
|
Enum.map(methods, fn {method, config} ->
|
||||||
|
{path, method, config}
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|> Enum.filter(fn {_path, method, _config} ->
|
||||||
|
method in ["get", "post"]
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
86
test/open_api_test.exs
Normal file
86
test/open_api_test.exs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
defmodule AshJsonApiWrapper.Hackernews.Test do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
require Ash.Query
|
||||||
|
|
||||||
|
@json "test/support/cybrid.json" |> File.read!() |> Jason.decode!()
|
||||||
|
|
||||||
|
defmodule TestingTesla do
|
||||||
|
use Tesla
|
||||||
|
|
||||||
|
plug(Tesla.Middleware.Headers, [
|
||||||
|
{"authorization",
|
||||||
|
"Bearer eyJraWQiOiJTcWZRWXNjelFQbENOSDhxOXZuR1E2WWcwQk1ENm5UZkZMLWhxeER6eFdFIiwiYWxnIjoiUlM1MTIifQ.eyJpc3MiOiJodHRwczovL2lkLnNhbmRib3guY3licmlkLmFwcCIsImF1ZCI6WyJodHRwczovL2Jhbmsuc2FuZGJveC5jeWJyaWQuYXBwIiwiaHR0cDovL3NhbmRib3gtYXBpLWludGVybmFsLWtleTozMDA1IiwiaHR0cHM6Ly9pZC5zYW5kYm94LmN5YnJpZC5hcHAiLCJodHRwOi8vc2FuZGJveC1hcGktaW50ZXJuYWwtYWNjb3VudHM6MzAwMyIsImh0dHA6Ly9zYW5kYm94LWFwaS1pbnRlcm5hbC1pZGVudGl0eTozMDA0IiwiaHR0cDovL3NhbmRib3gtYXBpLWludGVncmF0aW9uLWV4Y2hhbmdlOjMwMDYiLCJodHRwOi8vc2FuZGJveC1hcGktaW50ZWdyYXRpb24tdHJhbnNmZXJzOjMwMDciXSwic3ViIjoiODVjZTljNDgxYjNiN2MwM2YwMTMxOTQ0MjVmODc5MmEiLCJzdWJfdHlwZSI6ImJhbmsiLCJzY29wZSI6WyJiYW5rczpyZWFkIiwiYmFua3M6d3JpdGUiLCJhY2NvdW50czpyZWFkIiwiYWNjb3VudHM6ZXhlY3V0ZSIsImN1c3RvbWVyczpyZWFkIiwiY3VzdG9tZXJzOndyaXRlIiwiY3VzdG9tZXJzOmV4ZWN1dGUiLCJwcmljZXM6cmVhZCIsInF1b3RlczpleGVjdXRlIiwicXVvdGVzOnJlYWQiLCJ0cmFkZXM6ZXhlY3V0ZSIsInRyYWRlczpyZWFkIiwidHJhbnNmZXJzOmV4ZWN1dGUiLCJ0cmFuc2ZlcnM6cmVhZCIsInJld2FyZHM6ZXhlY3V0ZSIsInJld2FyZHM6cmVhZCIsImV4dGVybmFsX2JhbmtfYWNjb3VudHM6cmVhZCIsImV4dGVybmFsX2JhbmtfYWNjb3VudHM6d3JpdGUiLCJleHRlcm5hbF9iYW5rX2FjY291bnRzOmV4ZWN1dGUiLCJleHRlcm5hbF93YWxsZXRzOnJlYWQiLCJleHRlcm5hbF93YWxsZXRzOmV4ZWN1dGUiLCJ3b3JrZmxvd3M6cmVhZCIsIndvcmtmbG93czpleGVjdXRlIiwiZGVwb3NpdF9hZGRyZXNzZXM6cmVhZCIsImRlcG9zaXRfYWRkcmVzc2VzOmV4ZWN1dGUiXSwiaWF0IjoxNjg4MTU4MTE4LCJleHAiOjE2ODgxODY5MTgsImp0aSI6Ijg0ODNhOTg3LWEzZjQtNGU4Mi1iODc3LTg5MDk4YTIyYWE4ZSIsInRva2VuX3R5cGUiOiJhY2Nlc3MiLCJwcm9wZXJ0aWVzIjp7InR5cGUiOiJzYW5kYm94In19.SVFHoZWIKP-owEYzOSfP53nW9oM068t5-CUkPUIlXWPmV_rPTNGhaqjdy9u7iZQvXZX2BF5_gJHx1QR91DBYoR0ftRHxsQTq4UsJChTPfIEZZPuA_lf2iOSy-ivtEdXgqGGHnuItxzS-NnadffSawNXK8Em2Dhfwq7eLps6KvE6fVGelpinvTbfMD7L9PbCdNLdoonEbkdG6eMDV8FEX0sDJhfEd_GUp_HzAKFZwiK_g7NTT4rgm_0Yp6Paue3_ZviDpEWCLhyQNxd-N2TlP4wQng3zafB9_JPX3Z-xKq2WU5z_VltOTHcCMrvsDhDA2oI1CgFT92LMQmC_3QlpCVyaN70Jpd2E-ON9TehQ6JjcNZXoiKl7YaoGDadrAOXdYacexvsNPRpxZhZYxKX3FWtUxYeg0mIHNSS3nd14kfBXVARIqGuBYzRjepmb49MJERNzdeQ-3YectmBVWsPFWfnuMZfWUW54yHR0EF-oWLJJhBqUaZTysyXWpLeKnTBb2t6Q0y9_GLltxZh4x44qRmaq7k511QkEcBbLOnR40HqwnoteCQs-Yqnc8nwHBZ8H6gkUWtQTDiu4_uOmqoqXDx9WDuX4z5pE4M8HzzC1nu7-KvCcqAaCgVQV_Nut0V_IwE-6vB3JOIbFLGjHGMK_WwOrCefKbLoH6ZN6v7wz0cuY"}
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
@config [
|
||||||
|
tesla: TestingTesla,
|
||||||
|
endpoint: "https://bank.sandbox.cybrid.app",
|
||||||
|
resources: [
|
||||||
|
"Cybrid.Account": [
|
||||||
|
path: "/api/accounts",
|
||||||
|
object_type: "components.schemas.Account",
|
||||||
|
primary_key: "guid",
|
||||||
|
entity_path: "objects",
|
||||||
|
fields: [
|
||||||
|
guid: [
|
||||||
|
filter_handler: {:place_in_list, ["guid"]}
|
||||||
|
# {
|
||||||
|
# "name": "guid",
|
||||||
|
# "in": "query",
|
||||||
|
# "required": false,
|
||||||
|
# "description": "Comma separated account_guids to list accounts for.",
|
||||||
|
# "schema": {
|
||||||
|
# "type": "string"
|
||||||
|
# }
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "name": "bank_guid",
|
||||||
|
# "in": "query",
|
||||||
|
# "required": false,
|
||||||
|
# "description": "Comma separated bank_guids to list accounts for.",
|
||||||
|
# "schema": {
|
||||||
|
# "type": "string"
|
||||||
|
# }
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "name": "customer_guid",
|
||||||
|
# "in": "query",
|
||||||
|
# "required": false,
|
||||||
|
# "description": "Comma separated customer_guids to list accounts for.",
|
||||||
|
# "schema": {
|
||||||
|
# "type": "string"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
defmodule Api do
|
||||||
|
use Ash.Api
|
||||||
|
|
||||||
|
resources do
|
||||||
|
allow_unregistered? true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it does stuff" do
|
||||||
|
@json
|
||||||
|
|> AshJsonApiWrapper.OpenApi.ResourceGenerator.generate(@config)
|
||||||
|
|> Enum.map(fn {resource, code} ->
|
||||||
|
IO.puts(code)
|
||||||
|
Code.eval_string(code)
|
||||||
|
resource
|
||||||
|
end)
|
||||||
|
|
||||||
|
Cybrid.Account
|
||||||
|
|> Ash.Query.for_read(:list_accounts)
|
||||||
|
|> Ash.Query.filter(guid == "1c96166bfa20e434962d6f08a96e69ad")
|
||||||
|
# |> Ash.Query.filter(type == :fee)
|
||||||
|
|> Api.read!()
|
||||||
|
|> IO.inspect()
|
||||||
|
end
|
||||||
|
end
|
8602
test/support/cybrid.json
Normal file
8602
test/support/cybrid.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue