将长生不老药苦艾酒 input_object 映射到单个数据结构

Mapping elixir absinthe input_object to single data structure

有什么方法可以将 input_object 映射到单个数据结构中吗?

  input_object(:numrange) do
    field :start, non_null(:integer) do
      resolve(fn field, _, _ ->
        ...
      end)
    end
    field :end, non_null(:integer) do
      resolve(fn field, _, _ ->
        ...
      end)
    end
  end

然后将其解析为 [start, end] ?

不幸的是,无法在 input_object 级别定义它。但是,有几种不同的方法来处理参数转换。

1。更改您的业务逻辑

您可以只更改服务层中的契约来处理对象而不是数组。

# Inside of some module

def my_functon(attrs, other, params) do
  formatted_attrs = case attrs do
    %{range: %{start: startrange, end: endrange}} ->
      %{attrs | range: [startrange, endrange]}
    _ -> 
      attrs
  end
  # ...
end

2。处理架构中 args 传递的输入对象

您可以在提交参数时转换参数

# Some graphql definition

field(:my_field, :my_object) do
  arg(:range, non_null(:numrange))
  resolve(fn parent, %{range: %{start: rstart, end: rend}} = args, ctx ->
    new_args = %{args | range: [rstart, rend]}
    SomeModule.my_function(new_attrs, parent, ctx)
  end)
end

3。创建一个中间件

您可以创建一个 Absinthe.Middleware 来在提交参数时转换参数

defmodule MyApp.NumrangeTransform do
  @behaviour Absinthe.Middleware

  @impl Absinthe.Middleware
  def call(%Absinthe.Resolution{arguments: args} = res, opts) do
    field = Keyword.fetch!(opts, :field)
    new_args = case Map.get(args, field) do
      %{start: rstart, end: rend} ->
         Map.put(args, field, [rstart, rend])
      _ ->
        args
    end

    %{res | arguments: new_args}
  end
end

然后在您的架构定义中:

field(:my_field, :type) do
  middleware(MyApp.NumrangeTransform, field: :range)
  arg(:range, :numrange)
  # ...
end

中间件会为您转换参数,而无需到处编写转换逻辑

4。创建自定义标量类型

custom scalar 类型可以在 Absinthe 中定义:

# In some schema definition

scalar :numrange, name: "NumRange" do
  description("A number range of integers m..n")
  serialize([rstart, rend]) when is_integer(rstart) and is_integer(rend) do
    rstart <> ".." <> rend
  end

  parse(&do_parse/1)

  defp do_parse(%Absinthe.Blueprint.Input.String{value: range_str}) do
    with [s_str, e_str] <- String.split(range_str, ".."),
       {rstart, _} <- Integer.parse(s_str),
       {rend, _} <- Integer.parse(e_str) do
      {:ok, [rstart, rend]}
    else
      _ -> :error
    end
  end

  def do_parse(%Absinthe.Blueprint.Input.Null{}), do: {:ok, nil}
  def do_parse(_), do: :error
end

然后将其添加到架构中的某处

field(:my_field, :type) do
  arg(:range, non_null(:numrange))
  # ...
end

GraphQL 看起来像这样:

query SomeQuery {
  myField(range:"1..3")
}

这可能是最没有吸引力的选项,因为它创建了一种非标准的方式来为任何前端应用程序呈现和接受数字范围。但是,如果这不是第三方应用程序访问的public API,那么这样做应该没有问题。

结论

有很多方法可以定义和处理输入参数中的参数转换。可能还有其他我没有提到的解决方案。您可能会通过编写自定义 Absinthe.Phase 来做一些非常疯狂的事情,但这是一项复杂的工作,对于这么简单的事情来说可能过于笨重。