This commit is contained in:
Zach Daniel 2023-06-30 17:23:20 -04:00
parent b541a64482
commit de000a859c
5 changed files with 8903 additions and 2 deletions

View file

@ -193,6 +193,8 @@ defmodule AshJsonApiWrapper.DataLayer do
),
do: true
def can?(_, :nested_expressions), do: true
def can?(_, :sort), do: true
def can?(_, {:sort, _}), do: true
def can?(_, _), do: false
@ -361,9 +363,11 @@ defmodule AshJsonApiWrapper.DataLayer do
end
end
defp filter_instructions(filter, _resource, endpoint) do
defp filter_instructions(filter, resource, endpoint) do
fields =
endpoint.fields
|> Enum.concat(AshJsonApiWrapper.DataLayer.Info.fields(resource))
|> Enum.uniq_by(& &1.name)
|> List.wrap()
|> Enum.filter(& &1.filter_handler)
@ -691,6 +695,8 @@ defmodule AshJsonApiWrapper.DataLayer do
defp make_request(resource, query) do
# log_send(path, query)
IO.inspect(query.query_params)
AshJsonApiWrapper.DataLayer.Info.tesla(resource).get(query.path,
body: query.body,
query: query.query_params
@ -794,6 +800,7 @@ defmodule AshJsonApiWrapper.DataLayer do
else
case endpoint.entity_path do
nil ->
IO.inspect(endpoint)
{:ok, List.wrap(body)}
path ->

View file

@ -7,7 +7,7 @@ defmodule AshJsonApiWrapper.DataLayer.Transformers.SetEndpointDefaults do
def transform(dsl) do
base_entity_path = AshJsonApiWrapper.DataLayer.Info.base_entity_path(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
|> AshJsonApiWrapper.DataLayer.Info.endpoints()

View 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
View 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

File diff suppressed because one or more lines are too long