Behaviour module generation (#99)

* Behaviour module generation

For the server side, I imagine that the user would specify a handler
module and pass it into the service. Then it would be called with the
appropriate arguments.
I thought it might be nice to have a behaviour for these modules that
users could be implement.

Behaviours here are fully typed with the correct success types.
This commit is contained in:
Steve Cohen 2016-12-29 10:42:28 -08:00 committed by GitHub
parent ceceea9a66
commit 4695aaed34
4 changed files with 136 additions and 1 deletions

View File

@ -7,4 +7,10 @@ defmodule Thrift do
* `Thrift.Parser` - functions for parsing `.thift` schema files
"""
@type i8 :: (-128..127)
@type i16 :: (-32_768..32_767)
@type i32 :: (-2_147_483_648..2_147_483_647)
@type i64 :: (-9_223_372_036_854_775_808..9_223_372_036_854_775_807)
@type double :: float()
end

View File

@ -40,7 +40,8 @@ defmodule Thrift.Generator do
generate_struct_modules(schema),
generate_union_modules(schema),
generate_exception_modules(schema),
generate_services(schema)
generate_services(schema),
generate_behaviours(schema)
])
end
@ -96,4 +97,11 @@ defmodule Thrift.Generator do
Generator.Service.generate(schema, service)
end
end
defp generate_behaviours(schema) do
for {_, service} <- schema.services do
Generator.Behaviour.generate(schema, service)
end
end
end

View File

@ -0,0 +1,88 @@
defmodule Thrift.Generator.Behaviour do
@moduledoc """
A generator for a handler module's behaviour.
Takes a thrift service definition and creates a behavoiur module for users
to implement. Thrift types are converted into Elixir typespecs that are
equivalent to their thrift counterparts.
"""
alias Thrift.Parser.FileGroup
alias Thrift.Parser.Models.{
Field,
Struct,
StructRef,
}
def generate(schema, service) do
file_group = schema.file_group
dest_module = FileGroup.dest_module(file_group, service)
|> Module.concat(Handler)
|> Module.concat(Behaviour)
callbacks = service.functions
|> Map.values
|> Enum.map(&create_callback(file_group, &1))
behaviour_module = quote do
defmodule unquote(dest_module) do
unquote_splicing(callbacks)
end
end
{dest_module, behaviour_module}
end
defp create_callback(file_group, function) do
return_type = typespec(function.return_type, file_group)
params = function.params
|> Enum.map(&FileGroup.resolve(file_group, &1))
|> Enum.map(&to_arg_spec(&1, file_group))
quote do
@callback unquote(function.name)(unquote_splicing(params)) :: unquote(return_type)
end
end
def to_arg_spec(%Field{name: name, type: type}, file_group) do
quote do
unquote(Macro.var(name, nil)) :: unquote(typespec(type, file_group))
end
end
defp typespec(:void, _), do: quote do: no_return()
defp typespec(:bool, _), do: quote do: boolean()
defp typespec(:string, _), do: quote do: String.t
defp typespec(:i8, _), do: quote do: Thrift.i8
defp typespec(:i16, _), do: quote do: Thrift.i16
defp typespec(:i32, _), do: quote do: Thrift.i32
defp typespec(:i64, _), do: quote do: Thrift.i64
defp typespec(:double, _), do: quote do: Thrift.double
defp typespec(%StructRef{} = ref, file_group) do
file_group
|> FileGroup.resolve(ref)
|> typespec(file_group)
end
defp typespec(%Struct{name: name}, file_group) do
dest_module = FileGroup.dest_module(file_group, name)
quote do
%unquote(dest_module){}
end
end
defp typespec({:set, _t}, _) do
quote do
%MapSet{}
end
end
defp typespec({:list, t}, file_group) do
quote do
[unquote(typespec(t, file_group))]
end
end
defp typespec({:map, {k, v}}, file_group) do
key_type = typespec(k, file_group)
val_type = typespec(v, file_group)
quote do
%{unquote(key_type) => unquote(val_type)}
end
end
end

View File

@ -0,0 +1,33 @@
defmodule BehaviourTest do
use ThriftTestCase
@thrift_file name: "behaviour.thrift", contents: """
struct S {
1: string username
}
service BehaviourService {
void ping(1: i64 my_int),
void my_bool(1: bool my_bool),
void numbers(1: byte b, 2: i16 i, 3: i32 eye32, 4: i64 eye64, 5: double dub),
void my_set(1: set<string> my_set),
void my_list(1: list<string> my_string),
void my_map(1: map<string, string> my_map)
map<string, bool> my_map2(1: map<string, map<string, string>> my_map)
void struct_param(1: S my_struct)
}
"""
thrift_test "that behaviour callbacks exist" do
behaviour_specs = Behaviour.behaviour_info(:callbacks)
assert {:ping, 1} in behaviour_specs
assert {:my_bool, 1} in behaviour_specs
assert {:numbers, 5} in behaviour_specs
assert {:my_set, 1} in behaviour_specs
assert {:my_list, 1} in behaviour_specs
assert {:my_map, 1} in behaviour_specs
assert {:my_map2, 1} in behaviour_specs
assert {:struct_param, 1} in behaviour_specs
end
end