美文网首页
Absinthe 1.5 动态定义枚举值

Absinthe 1.5 动态定义枚举值

作者: BlindingDark | 来源:发表于2020-04-24 03:49 被阅读0次

absinthe graphql enum elixir macro


Absinthe 是 Elixir 语言的一个 GraphQL 工具库。即将发布的 1.5 版本经过了大量重构,该版本将不再支持动态定义 enum 类型的值。详情可以前往以下链接进一步了解。

https://github.com/absinthe-graphql/absinthe/issues/843
https://github.com/absinthe-graphql/absinthe/pull/859

hydrate

为了替代原来动态定义的枚举值,1.5 版本中引入了 hydrate/2 ,请求时将会回调 hydrate/2 以处理枚举值的映射关系。
举个例子,假如邮箱的激活状态是由 MyApp.Email 中的两个函数进行定义

defmodule MyApp.Email do
  def inactivated, do: 1
  def activated, do: 2
end

那么 1.5 版本之前你可以这样定义这个枚举

alias MyApp.Email

enum :email_status do
  value(:inactivated, as: Email.inactivated())
  value(:activated, as: Email.activated())
end

但是在 1.5 版本中这么定义就会报错了,因为 as 之后不再支持函数调用,想要实现类似的效果就得用 hydrate/2

alias MyApp.Email

enum :email_status do
  value(:inactivated)
  value(:activated)
end

def hydrate(
  %Absinthe.Blueprint.Schema.EnumValueDefinition{identifier: identifier},
  [%Absinthe.Blueprint.Schema.EnumTypeDefinition{identifier: :email_status}]
) do
  value =
    case identifier do
      :inactivated -> Email.inactivated()
      :activated -> Email.activated()
    end

  {:as, value}
end

🤨 实在是有点难受。

编写 enum_dynamic 宏

不过不使用 hydrate/2 ,直接硬编码还是可以的

enum :email_status do
  value(:inactivated, as: 1)
  value(:activated, as: 2)
end

但是这样会导致硬编码出现在两个地方,更让人难受了。
这个时候,就轮到强大的 Macro 登场!在编译期将函数调用替换为硬编码,以实现用最小的改动来兼容新版本。
先来设想这个宏的用法,设计的核心思路是尽量减少需要修改的代码

+ import MyApp.Schema.Helper.Enum
  alias MyApp.Email

- enum :email_status do
+ enum_dynamic :email_status do
    value(:inactivated, as: Email.inactivated())
    value(:activated, as: Email.activated())
  end

这样的话只须把所有的 enum 改名即可,很方便进行升级,接下来分析怎么去实现这个宏。
借助 Elixir 提供的 Macro.prewalk/2Macro.postwalk/2 函数,就可以可以前序或者后续遍历 AST 并对 AST 中的节点进行修改,还是非常方便的。
在动手实现之前,先来用 quote 看看修改前的 AST 与修改后的 AST 有什么区别

这是修改前的

quote do
  enum_dynamic :email_status do
    value(:inactivated, as: Email.inactivated())
    value(:activated, as: Email.activated())
  end
end
{:enum_dynamic, [],
 [
   :email_status,
   [
     do: {:__block__, [],
      [
        {:value, [],
         [
           :inactivated,
           [
             as: {{:., [],
               [{:__aliases__, [alias: false], [:Email]}, :inactivated]}, [],
              []}
           ]
         ]},
        {:value, [],
         [
           :activated,
           [
             as: {{:., [],
               [{:__aliases__, [alias: false], [:Email]}, :activated]}, [], []}
           ]
         ]}
      ]}
   ]
 ]}

我们希望修改后是这样一个结果

quote do
  enum :email_status do
    value(:inactivated, as: 1)
    value(:activated, as: 2)
  end
end
{:enum, [],
 [
   :email_status,
   [
     do: {:__block__, [],
      [
        {:value, [], [:inactivated, [as: 1]]},
        {:value, [], [:activated, [as: 2]]}
      ]}
   ]
 ]}

对比可以看出来就是把 :as 后的内容给它执行了,并且把 :enum_dynamic 再换回 :enum
那么具体实现就可以这么写

# :email_status 会传给 name
# do block 会传给 values
defmacro enum_dynamic(name, do: values) do
  values = Macro.postwalk(values, fn
    {:as, value} ->
      # 找到 :as 开头的 AST 节点,把这个节点修改为执行后的值
      {actual_value, _} = Code.eval_quoted(value)
      {:as, actual_value}
    node ->
      # 其余 AST node 保持不变
      node
  end)

  {:enum, [], [name, [do: values]]}
end

但是仅仅这样写你会发现,这个宏无法处理 alias 模块名的调用,所以这里需要指定运行环境为 __CALLER__ 以展开 alias
整理过后就是这样

defmacro enum_dynamic(name, do: values) do
  eval_value = fn
    {:as, value} ->
      {actual_value, _} = Code.eval_quoted(value, [], __CALLER__)
      {:as, actual_value}

    node ->
      node
  end

  values = Macro.postwalk(values, eval_value)

  {:enum, [], [name, [do: values]]}
end

注意事项

宏是在编译期执行的,如果 as 后的函数在编译期无法执行,或者编译期的执行结果与运行时不同,使用 enum_dynamic 就会出现问题。

相关文章

  • Absinthe 1.5 动态定义枚举值

    absinthe graphql enum elixir macro Absinthe 是 Elixir 语言的一...

  • 枚举类

    1.枚举类型的定义: 枚举类型定义的一般形式为 enum 枚举名{//枚举值表枚举值1;枚举值2;...} 在枚举...

  • go 枚举类型

    这里需要用到enum库 定义一个枚举类型 操作枚举enum 查看枚举值 修改自定义枚举值 添加和移除枚举值

  • C语言基础 之 枚举类型

    枚举类型 枚举类型: 列出所有可能的值 枚举类型的定义 枚举类型定义的一般格式:enum 枚举类型名 {枚举值表}...

  • 枚举--java24(02/17/2016)

    如何自定义枚举类如何使用enum定义枚举类、枚举类的主要方法实现接口的枚举类 JDK1.5之前需要自定义枚举类JD...

  • V语言学习笔记-05枚举

    定义枚举 枚举默认是模块内访问,通过pub关键字来定义公共枚举 也可以指定枚举值的值,枚举值也可以是负数 也可以指...

  • 参数规则校验-限定枚举值的校验

    首先,定义一个基础的枚举接口,只要实现了此接口的枚举方可校验枚举值。 接下来定义我们自己的枚举值 定义枚举校验注解...

  • Java-枚举

    一、枚举类的理解 当需要定义一组常量时,强烈建议使用枚举类JDK1.5之前需要自定义枚举类,JDK1.5新增的en...

  • java枚举笔记

    定义枚举 枚举使用关键字enum定义,枚举值一般使用大写字母,值之间用逗号隔开。例如定义一个颜色的枚举。 枚举使用...

  • C-枚举

    一.定义枚举类型 1.枚举语法定义格式: 2.枚举成员的值: 第一个枚举成员的默认值为整型的 0,后续枚举成员的值...

网友评论

      本文标题:Absinthe 1.5 动态定义枚举值

      本文链接:https://www.haomeiwen.com/subject/fcwtwhtx.html