diff --git a/example/mix.exs b/example/mix.exs index afd72472..457e2256 100644 --- a/example/mix.exs +++ b/example/mix.exs @@ -31,7 +31,8 @@ defmodule Calculator.MixProject do [ # Note: you will want to replace the next line to get elixir-thrift from either # Hex or GitHub in your own project. - {:thrift, path: ".."} + {:thrift, path: ".."}, + {:httpoison, "~> 1.7.0"} ] end end diff --git a/lib/mix/tasks/compile.thrift.ex b/lib/mix/tasks/compile.thrift.ex index 7d37beb5..016223ee 100644 --- a/lib/mix/tasks/compile.thrift.ex +++ b/lib/mix/tasks/compile.thrift.ex @@ -46,7 +46,8 @@ defmodule Mix.Tasks.Compile.Thrift do # other settings... thrift: [ files: Path.wildcard("thrift/**/*.thrift"), - output_path: "lib/generated" + output_path: "lib/generated", + output_test_data: "test/test_data/" ] ] end @@ -64,6 +65,13 @@ defmodule Mix.Tasks.Compile.Thrift do input_files = Keyword.get(config, :files, []) output_path = Keyword.get(config, :output_path, "lib") + output_test_data = + Keyword.get( + config, + :output_test_data, + "test/test_data/" + ) + parser_opts = config |> Keyword.take([:include_paths, :namespace]) @@ -77,8 +85,8 @@ defmodule Mix.Tasks.Compile.Thrift do {groups, [] = _diagnostics} -> groups - |> extract_targets(output_path, opts[:force]) - |> generate(manifest(), output_path, opts) + |> extract_targets({output_path, output_test_data}, opts[:force]) + |> generate(manifest(), [output_path, output_test_data], opts) {_groups, diagnostics} -> {:error, diagnostics} @@ -117,8 +125,8 @@ defmodule Mix.Tasks.Compile.Thrift do @typep mappings :: [{:stale, FileGroup.t(), [Path.t()]} | {:ok, FileGroup.t(), [Path.t()]}] - @spec extract_targets([FileGroup.t()], Path.t(), boolean) :: mappings - defp extract_targets(groups, output_path, force) when is_list(groups) do + @spec extract_targets([FileGroup.t()], {Path.t(), Path.t()}, boolean) :: mappings + defp extract_targets(groups, {output_path, _}, force) when is_list(groups) do for %FileGroup{initial_file: file} = group <- groups do targets = group @@ -135,7 +143,7 @@ defmodule Mix.Tasks.Compile.Thrift do @spec generate(mappings, Path.t(), Path.t(), OptionParser.parsed()) :: {:ok | :noop | :error, [Diagnostic.t()]} - defp generate(mappings, manifest, output_path, opts) do + defp generate(mappings, manifest, [output_path, output_test_data] = output, opts) do timestamp = :calendar.universal_time() verbose = opts[:verbose] @@ -157,13 +165,15 @@ defmodule Mix.Tasks.Compile.Thrift do else # Ensure we have an output directory and remove old target files. File.mkdir_p!(output_path) + File.mkdir_p!(output_test_data) + Enum.each(removed, &File.rm/1) unless Enum.empty?(stale) do Mix.Utils.compiling_n(length(stale), :thrift) Enum.each(stale, fn {group, _targets} -> - Thrift.Generator.generate!(group, output_path) + Thrift.Generator.generate!(group, output) verbose && Mix.shell().info("Compiled #{group.initial_file}") end) end diff --git a/lib/mix/tasks/thrift.generate.ex b/lib/mix/tasks/thrift.generate.ex index b3d20305..c4d1c78d 100644 --- a/lib/mix/tasks/thrift.generate.ex +++ b/lib/mix/tasks/thrift.generate.ex @@ -42,7 +42,8 @@ defmodule Mix.Tasks.Thrift.Generate do # other settings... thrift: [ include_paths: ["./extra_thrift"], - output_path: "lib/generated" + output_path: "lib/generated", + output_test_data: "test/test_data/" ] ] end @@ -55,12 +56,20 @@ defmodule Mix.Tasks.Thrift.Generate do {opts, files} = OptionParser.parse!( args, - switches: [include: :keep, namespace: :string, out: :string, verbose: :boolean], + switches: [ + include: :keep, + namespace: :string, + out: :string, + out_test: :string, + verbose: :boolean + ], aliases: [I: :include, o: :out, v: :verbose] ) config = Keyword.get(Mix.Project.config(), :thrift, []) output_path = opts[:out] || Keyword.get(config, :output_path, "lib") + output_test_data = opts[:out_test] || Keyword.get(config, :output_test_data, "lib") + namespace = opts[:namespace] || Keyword.get(config, :namespace) include_paths = diff --git a/lib/thrift/ast.ex b/lib/thrift/ast.ex index 0cc046f2..5c7165a4 100644 --- a/lib/thrift/ast.ex +++ b/lib/thrift/ast.ex @@ -98,6 +98,23 @@ defmodule Thrift.AST do end end + defmodule Typedef do + + @type t :: %Typedef{ + line: Thrift.Parser.line(), + annotations: Thrift.Parser.annotations(), + name: atom(), + type: Types.t() + } + @enforce_keys [:name, :type] + defstruct line: nil, annotations: %{}, name: nil, type: nil + + def new(name, type) do + %Typedef{name: List.to_atom(name), type: type} + end + + end + defmodule TEnum do @moduledoc """ An enumerated type with named values. @@ -528,18 +545,30 @@ defmodule Thrift.AST do } end - defp merge(schema, {:typedef, actual_type, type_alias}) do + defp merge(schema, %Typedef{} = typedef) do %Schema{ schema | typedefs: - put_new_strict( - schema.typedefs, - List.to_atom(type_alias), - add_namespace_to_type(schema.module, actual_type) - ) + put_new_strict( + schema.typedefs, + typedef.name, + add_namespace_to_name(schema.module, typedef) + ) } end + # defp merge(schema, {:typedef, actual_type, type_alias}) do + # %Schema{ + # schema + # | typedefs: + # put_new_strict( + # schema.typedefs, + # List.to_atom(type_alias), + # add_namespace_to_type(schema.module, actual_type) + # ) + # } + # end + defp add_namespace_to_name(nil, model) do model end diff --git a/lib/thrift/binary/framed/server/http.ex b/lib/thrift/binary/framed/server/http.ex new file mode 100644 index 00000000..1f072a34 --- /dev/null +++ b/lib/thrift/binary/framed/server/http.ex @@ -0,0 +1,3 @@ +defmodule Thrift.Binary.Framed.Server.HTTP do + +end diff --git a/lib/thrift/generator.ex b/lib/thrift/generator.ex index 94e6fd31..10f65dc6 100644 --- a/lib/thrift/generator.ex +++ b/lib/thrift/generator.ex @@ -10,7 +10,8 @@ defmodule Thrift.Generator do Generator, Generator.ConstantGenerator, Generator.EnumGenerator, - Generator.StructGenerator + Generator.StructGenerator, + Generator.TestDataGenerator } alias Thrift.Parser.FileGroup @@ -24,6 +25,7 @@ defmodule Thrift.Generator do schema |> Map.put(:file_group, file_group) |> generate_schema + |> hd() |> Enum.map(fn {name, _} -> target_path(name) end) end) end @@ -38,12 +40,12 @@ defmodule Thrift.Generator do |> Kernel.<>(".ex") end - def generate!(%FileGroup{} = file_group, output_dir) do + def generate!(%FileGroup{} = file_group, [_output_dir, _output_test_data] = output) do Enum.flat_map(file_group.schemas, fn {_, schema} -> schema |> Map.put(:file_group, file_group) |> generate_schema - |> write_schema_to_file(output_dir) + |> write_schema_to_file(output) end) end @@ -62,25 +64,35 @@ defmodule Thrift.Generator do current_module_file_group = FileGroup.set_current_module(schema.file_group, schema.module) schema = %Schema{schema | file_group: current_module_file_group} - List.flatten([ - generate_enum_modules(schema), - generate_const_modules(schema), - generate_struct_modules(schema), - generate_union_modules(schema), - generate_exception_modules(schema), - generate_services(schema), - generate_behaviours(schema) - ]) + modules = + List.flatten([ + generate_enum_modules(schema), + generate_const_modules(schema), + generate_struct_modules(schema), + generate_union_modules(schema), + generate_exception_modules(schema), + generate_services(schema), + generate_behaviours(schema) + ]) + + test_modules = generate_test_data_modules(schema) + [modules, test_modules] + end + + defp write_schema_to_file(module_groups, outputs) do + module_groups + |> Enum.zip(outputs) + |> Enum.map(&perform_write/1) end - defp write_schema_to_file(generated_modules, output_dir) do - generated_modules + defp perform_write({modules, output}) do + modules |> resolve_name_collisions |> Enum.map(fn {name, quoted} -> filename = target_path(name) source = Macro.to_string(quoted) - path = Path.join(output_dir, filename) + path = Path.join(output, filename) path |> Path.dirname() @@ -141,6 +153,45 @@ defmodule Thrift.Generator do {:defmodule, meta, [name, [do: {:__block__, [], ast1 ++ ast2}]]} end + defp generate_test_data_modules(schema) do + endless_label = fn label -> Stream.repeatedly(fn -> label end) end + zip_with_label = fn label, stream -> Stream.zip(endless_label.(label), stream) end + + typedefs_stream = zip_with_label.(:typedef, schema.typedefs) + structs_stream = zip_with_label.(:struct, schema.structs) + exceptions_stream = zip_with_label.(:exception, schema.exceptions) + unions_stream = zip_with_label.(:union, schema.unions) + enums_stream = zip_with_label.(:enum, schema.enums) + + all_streams = + Stream.concat([ + typedefs_stream, + structs_stream, + exceptions_stream, + unions_stream, + enums_stream + ]) + + for {label, {key, struct}} <- all_streams do + full_name = + case label do + :typedef -> + thrift_module = Atom.to_string(schema.module) + typedef = key |> Atom.to_string() |> String.capitalize() + name = "#{thrift_module}.#{typedef}" |> String.to_atom() + + FileGroup.dest_module(schema.file_group, name) + + _otherwise -> + FileGroup.dest_module(schema.file_group, struct) + end + + full_test_data_name = TestDataGenerator.test_data_module_from_data_module(full_name) + + {full_test_data_name, TestDataGenerator.generate(label, schema, full_name, struct)} + end + end + defp generate_enum_modules(schema) do for {_, enum} <- schema.enums do full_name = FileGroup.dest_module(schema.file_group, enum) diff --git a/lib/thrift/generator/behaviour.ex b/lib/thrift/generator/behaviour.ex index 119eb853..383f90a6 100644 --- a/lib/thrift/generator/behaviour.ex +++ b/lib/thrift/generator/behaviour.ex @@ -3,6 +3,7 @@ # equivalent to their thrift counterparts. defmodule Thrift.Generator.Behaviour do @moduledoc false + import Thrift.Generator.Utils.Types, only: [typespec: 2] alias Thrift.AST.{ Exception, @@ -42,6 +43,14 @@ defmodule Thrift.Generator.Behaviour do callback_name = Utils.underscore(function.name) return_type = typespec(function.return_type, file_group) + return_type = ok_type(return_type) + + exceptions = Enum.map( + function.exceptions, + &(&1.type |> typespec(file_group) |> exception_type()) + ) + + return_type = Enum.reduce([return_type | exceptions], &unite/2) params = function.params @@ -59,78 +68,21 @@ defmodule Thrift.Generator.Behaviour do 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(:binary, _), do: quote(do: binary) - 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(%TypeRef{} = ref, file_group) do - file_group - |> FileGroup.resolve(ref) - |> typespec(file_group) - end - - defp typespec(%TEnum{}, _) do + defp ok_type(typespec_) do quote do - non_neg_integer + {:ok, unquote(typespec_)} end end - defp typespec(%Union{name: name}, file_group) do - dest_module = FileGroup.dest_module(file_group, name) - - quote do - %unquote(dest_module){} - end - end - - defp typespec(%Exception{name: name}, file_group) do - dest_module = FileGroup.dest_module(file_group, name) - - quote do - %unquote(dest_module){} - end - end - - defp typespec(%Struct{name: name}, file_group) do - dest_module = FileGroup.dest_module(file_group, name) - + defp exception_type(typespec_) do quote do - %unquote(dest_module){} + {:exception, unquote(typespec_)} 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 - - defp typespec(unknown_typespec, _) do - Logger.error("Unknown type: #{inspect(unknown_typespec)}. Falling back to any()") - + defp unite(type, acc) do quote do - any + unquote(acc) | unquote(type) end end end diff --git a/lib/thrift/generator/binary/framed/http/client.ex b/lib/thrift/generator/binary/framed/http/client.ex new file mode 100644 index 00000000..4ce9c38c --- /dev/null +++ b/lib/thrift/generator/binary/framed/http/client.ex @@ -0,0 +1,71 @@ +defmodule Thrift.Generator.Binary.Framed.HTTP.Client do + alias Thrift.Generator.{ + Service + } + alias Thrift.Parser.FileGroup + alias Thrift.Protocol.Binary + + def generate(service_module, service, file_group) do + # thrift_root = generate_thrift_root(service_module, service, file_group) + + methods = Enum.map( + service.functions, + &(generate_method(service_module, &1, file_group)) + ) + + quote do + defmodule Binary.Framed.HTTP.Client do + alias Thrift.Protocol.Binary + + unquote_splicing(methods) + + end + end + end + + def generate_method(service_module, {func_name, function_ast}, file_group) do + + # function_name = nil + function_args = Enum.map(function_ast.params, &Macro.var(&1.name, nil)) + args_module = Service.module_name(function_ast, :args) + args_binary_module = Module.concat(args_module, :BinaryProtocol) + response_module = Service.module_name(function_ast, :response) + response_binary_module = Module.concat(response_module, :BinaryProtocol) + s_func_name = Atom.to_string(func_name) + + assignments = + function_ast.params + |> Enum.zip(function_args) + |> Enum.map(fn {param, var} -> + quote do + {unquote(param.name), unquote(var)} + end + end) + + quote do + def unquote(func_name)(thrift_url, unquote_splicing(function_args), opts \\ []) do + args = %unquote(args_module){unquote_splicing(assignments)} + serialized_args = unquote(args_binary_module).serialize(args) + header = Binary.serialize(:message_begin, {:call, 0, unquote(s_func_name)}) + payload = [header | serialized_args] |> IO.iodata_to_binary() + + {http_headers, opts} = Keyword.pop(opts, :headers, []) + opts = Keyword.merge([hackney: [pool: false]], opts) + + {:ok, response} = HTTPoison.post(thrift_url, payload, http_headers, opts) + + result = + with( + {:ok, {:reply, 0, unquote(s_func_name), rest}} <- Binary.deserialize(:message_begin, response.body), + # {%unquote(response_module){success: s}, ""} <- unquote(response_binary_module).deserialize(rest) + {%unquote(response_module){} = r, ""} <- unquote(response_binary_module).deserialize(rest) + ) do + Thrift.Utils.Web.normalize_q_result(r) + end + {response, result} + end + end + + end + +end diff --git a/lib/thrift/generator/binary/framed/http/server.ex b/lib/thrift/generator/binary/framed/http/server.ex new file mode 100644 index 00000000..a4f21580 --- /dev/null +++ b/lib/thrift/generator/binary/framed/http/server.ex @@ -0,0 +1,213 @@ +defmodule Thrift.Generator.Binary.Framed.HTTP.Server do + alias Thrift.Generator.{ + Service + } + + alias Thrift.Parser.FileGroup + alias Thrift.Protocol.Binary + + def generate(service_module, service, file_group) do + thrift_root = generate_thrift_root(service_module, service, file_group) + + quote do + defmodule Binary.Framed.HTTP.Server do + unquote(thrift_root) + + def get_child_spec(module_handler) do + {Plug.Cowboy, + scheme: :http, plug: {__MODULE__.Router, [module_handler]}, options: [port: 4001]} + end + end + end + end + + def generate_thrift_root(service_module, service, file_group) do + quote do + defmodule Router do + use Plug.Router + + alias Thrift.Protocol + + plug(:match) + plug(:dispatch, builder_opts()) + + def init(opts) do + opts + end + + post "/thrift" do + with( + {:ok, payload, conn} <- Plug.Conn.read_body(conn), + {:ok, parsed} <- Protocol.Binary.deserialize(:message_begin, payload) + ) do + handle_thrift_message(conn, parsed, opts) + else + {:error, _} -> send_resp(conn, 500, "bad thrift") + end + end + + post "/thrift/:method" do + {:ok, payload, conn} = Plug.Conn.read_body(conn) + conn = %{conn | body_params: payload} + Plug.forward(conn, [method], __MODULE__.Handlers, opts) + end + + match _ do + send_resp(conn, 404, "not found") + end + + def handle_thrift_message(conn, {:call, sequence_id, name, args_binary}, opts) do + conn = %{conn | body_params: args_binary, path_info: [name]} + Plug.forward(conn, [name], __MODULE__.Handlers, opts) + end + + unquote(generate_thrift_handlers(service_module, service, file_group)) + end + end + end + + def generate_thrift_handlers(service_module, service_ast, file_group) do + quote do + defmodule Handlers do + use Plug.Router + + plug(:match) + plug(:dispatch, builder_opts()) + + def init([opts]) do + opts + end + + unquote_splicing( + Enum.map(service_ast.functions, &generate_thrift_handle(service_module, file_group, &1)) + ) + + match _ do + send_resp(conn, 404, "Ohh") + end + + unquote_splicing(generate_exception_handlers(service_ast, service_module, file_group)) + end + end + end + + def generate_thrift_handle(service_module, _file_group, {fname, function_ast}) do + func_name = Atom.to_string(fname) + func_path = "/" <> func_name + + handle = + func_name + |> Macro.underscore() + |> String.to_atom() + + handler_args = Enum.map(function_ast.params, &Macro.var(&1.name, nil)) + args_module = Module.concat(service_module, Service.module_name(function_ast, :args)) + response_module = Module.concat(service_module, Service.module_name(function_ast, :response)) + + response_header = Binary.serialize(:message_begin, {:reply, 0, func_name}) + + # DANGER + # expanded into (clasue -> block) which broke with-else statement + # need to fix on Elixir side + # + # generate_exception_clause = + # fn exception_ast -> + # exception_type_ast = FileGroup.resolve(file_group, exception_ast) |> IO.inspect(label: "RESOLVED") + # exception_module = FileGroup.dest_module(file_group, exception_type_ast.type) |> IO.inspect(label: "module") + # # exception_var = Macro.var(exception_ast.name, nil) + # # field_setter = quote do: {unquote(exception_ast.name), unquote(exception_var)} + # field_setter = quote do: {unquote(exception_ast.name), exc} + # [quote do + # {:exception, %unquote(exception_module){} = exc} -> + # response = %unquote(response_module){unquote(field_setter)} + # unquote(response_module).BinaryProtocol.serialize(response) + # end] + # end + # + # exceptions_clauses = Enum.flat_map( + # function_ast.exceptions, + # generate_exception_clause + # ) + # + # END OF DANGER + # # # # # # + + struct_matches = + Enum.map(function_ast.params, fn param -> + {param.name, Macro.var(param.name, nil)} + end) + + quote do + post unquote(func_path) do + [handler_module] = opts + body = conn.body_params + headers = conn.req_headers + + encoded_response = + with( + { + # %Service.AddArgs{left: left, right: right} + %unquote(args_module){unquote_splicing(struct_matches)}, + _rest + } <- + unquote(args_module).BinaryProtocol.deserialize(body), + {:ok, result} <- + apply( + handler_module, + unquote(handle), + [unquote_splicing(handler_args), headers] + ), + response = %unquote(response_module){success: result} + ) do + unquote(response_module).BinaryProtocol.serialize(response) + else + :error -> + :error + + {:exception, exc} -> + handle_exc(exc, unquote(response_module)) + # unquote_splicing(exceptions_clauses) + end + + case encoded_response do + :error -> + send_resp(conn, 500, "LOL") + + encoded_response -> + encoded_response = IO.iodata_to_binary([unquote(response_header) | encoded_response]) + send_resp(conn, 200, encoded_response) + end + end + end + end + + def generate_exception_handlers(service_ast, service_module, file_group) do + unknown_exception_handler = + quote do + defp handle_exc(_, _) do + :error + end + end + + for {_fname, function_ast} <- service_ast.functions, + exception_ast <- function_ast.exceptions do + response_module = + Module.concat(service_module, Service.module_name(function_ast, :response)) + + generate_exception_handler(response_module, exception_ast, file_group) + end ++ [unknown_exception_handler] + end + + def generate_exception_handler(response_module, exception_ast, file_group) do + exception_type_ast = FileGroup.resolve(file_group, exception_ast) + exception_module = FileGroup.dest_module(file_group, exception_type_ast.type) + field_setter = quote do: {unquote(exception_ast.name), exc} + + quote do + defp handle_exc(%unquote(exception_module){} = exc, unquote(response_module)) do + response = %unquote(response_module){unquote(field_setter)} + unquote(response_module).BinaryProtocol.serialize(response) + end + end + end +end diff --git a/lib/thrift/generator/service.ex b/lib/thrift/generator/service.ex index 3d5acbca..83feb294 100644 --- a/lib/thrift/generator/service.ex +++ b/lib/thrift/generator/service.ex @@ -25,6 +25,8 @@ defmodule Thrift.Generator.Service do framed_client = Generator.Binary.Framed.Client.generate(service) framed_server = Generator.Binary.Framed.Server.generate(dest_module, service, file_group) + http_server = Generator.Binary.Framed.HTTP.Server.generate(dest_module, service, file_group) + http_client = Generator.Binary.Framed.HTTP.Client.generate(dest_module, service, file_group) service_module = quote do @@ -36,6 +38,10 @@ defmodule Thrift.Generator.Service do unquote(framed_client) unquote(framed_server) + + unquote(http_client) + + unquote(http_server) end end diff --git a/lib/thrift/generator/struct_generator.ex b/lib/thrift/generator/struct_generator.ex index 8c95f0b3..06791b43 100644 --- a/lib/thrift/generator/struct_generator.ex +++ b/lib/thrift/generator/struct_generator.ex @@ -44,7 +44,12 @@ defmodule Thrift.Generator.StructGenerator do end end - quote do + struct_spec_block = Enum.map( + struct.fields, + &(generate_struct_spec(&1, schema.file_group)) + ) + + quote do defmodule unquote(name) do @moduledoc false _ = unquote("Auto-generated Thrift #{label} #{struct.name}") @@ -59,7 +64,7 @@ defmodule Thrift.Generator.StructGenerator do ) unquote(define_block) - @type t :: %__MODULE__{} + @type t :: %__MODULE__{unquote_splicing(struct_spec_block)} def new, do: %__MODULE__{} unquote_splicing(List.wrap(extra_defs)) @@ -118,4 +123,8 @@ defmodule Thrift.Generator.StructGenerator do defp to_thrift(%TypeRef{referenced_type: type}, file_group) do to_thrift(FileGroup.resolve(file_group, type), file_group) end + + defp generate_struct_spec(field_ast, file_group) do + {field_ast.name, Utils.Types.typespec(field_ast.type, file_group)} + end end diff --git a/lib/thrift/generator/test_data_generator.ex b/lib/thrift/generator/test_data_generator.ex new file mode 100644 index 00000000..81e9248c --- /dev/null +++ b/lib/thrift/generator/test_data_generator.ex @@ -0,0 +1,306 @@ +defmodule Thrift.Generator.TestDataGenerator do + alias __MODULE__, as: TestDataGenerator + + alias Thrift.AST.{ + Exception, + Struct, + TEnum, + TypeRef, + Typedef, + Union + } + + alias Thrift.Parser.FileGroup + + def generate(label, schema, full_name, struct) do + case label do + :typedef -> TestDataGenerator.Typedef.generate(schema, full_name, struct) + :union -> TestDataGenerator.Union.generate(schema, full_name, struct) + :enum -> TestDataGenerator.Enum.generate(schema, full_name, struct) + _ -> TestDataGenerator.Struct.generate(schema, full_name, struct) + end + end + + def get_generator(type, file_group, annotations \\ %{}) + + def get_generator(:bool, _, _) do + quote do + bool() + end + end + + def get_generator(:string, _, _) do + ascii = ?a..?z |> Enum.to_list() + + quote do + let chars <- list(oneof(unquote(ascii))) do + List.to_string(chars) + end + end + end + + def get_generator(:binary, _, _) do + quote do + binary(100) + end + end + + def get_generator(:i8, _, annotations) do + min = refinement_from_annotations(annotations, :min, &parse_integer/1, -128) + max = refinement_from_annotations(annotations, :max, &parse_integer/1, 127) + + quote do + integer(unquote(min), unquote(max)) + end + end + + def get_generator(:i16, _, annotations) do + min = refinement_from_annotations(annotations, :min, &parse_integer/1, -32_768) + max = refinement_from_annotations(annotations, :max, &parse_integer/1, 32_767) + + quote do + integer(unquote(min), unquote(max)) + end + end + + def get_generator(:i32, _, annotations) do + min = refinement_from_annotations(annotations, :min, &parse_integer/1, -2_147_483_648) + max = refinement_from_annotations(annotations, :max, &parse_integer/1, 2_147_483_647) + + quote do + integer(unquote(min), unquote(max)) + end + end + + def get_generator(:i64, _, annotations) do + min = + refinement_from_annotations(annotations, :min, &parse_integer/1, -9_223_372_036_854_775_808) + + max = + refinement_from_annotations(annotations, :max, &parse_integer/1, 9_223_372_036_854_775_807) + + quote do + integer(unquote(min), unquote(max)) + end + end + + def get_generator(:double, _, annotations) do + min = refinement_from_annotations(annotations, :min, &parse_float/1, :inf) + max = refinement_from_annotations(annotations, :max, &parse_float/1, :inf) + + quote do + float(unquote(min), unquote(max)) + end + end + + def get_generator({:list, t}, file_group, annotations) do + subgen = get_generator(t, file_group, annotations) + + quote do + list(unquote(subgen)) + end + end + + def get_generator({:set, t}, file_group, annotations) do + subgen = get_generator({:list, t}, file_group, annotations) + + quote do + let set <- unquote(subgen) do + MapSet.new(set) + end + end + end + + def get_generator({:map, {k, v}}, file_group, annotations) do + separate = fn {k, v} -> + [type, key] = + k + |> Atom.to_string() + |> String.split("_", parts: 2) + + {type, {String.to_atom(key), v}} + end + + separated_annotations = + annotations + |> Enum.map(separate) + |> Enum.group_by(&elem(&1, 0), &elem(&1, 2)) + + key_annotations = Map.new(separated_annotations[:k] || %{}) + val_annotations = Map.new(separated_annotations[:v] || %{}) + + key_subgen = get_generator(k, file_group, key_annotations) + val_subgen = get_generator(v, file_group, val_annotations) + + quote do + map(unquote(key_subgen), unquote(val_subgen)) + end + end + + def get_generator(%TEnum{name: name}, file_group, _annotations) do + dest_module = + FileGroup.dest_module(file_group, name) + |> test_data_module_from_data_module + + quote do + unquote(dest_module).get_generator(context) + end + end + + def get_generator( + %TypeRef{referenced_type: type_name}, + %FileGroup{resolutions: resolutions} = file_group, + annotations + ) do + case resolutions[type_name] do + %Typedef{} = td -> + get_generator(td, file_group, annotations) + + other_type -> + file_group + |> FileGroup.resolve(other_type) + |> get_generator(file_group, annotations) + end + end + + def get_generator(%Union{name: name}, file_group, annotations) do + dest_module = + FileGroup.dest_module(file_group, name) + |> test_data_module_from_data_module + + props = gen_props(annotations) + + quote do + unquote(dest_module).get_generator(context, unquote(props)) + end + end + + def get_generator(%Exception{name: name}, file_group, annotations) do + dest_module = + FileGroup.dest_module(file_group, name) + |> test_data_module_from_data_module + + quote do + unquote(dest_module).get_generator(context) + end + end + + def get_generator(%Struct{name: name}, file_group, annotations) do + dest_module = + FileGroup.dest_module(file_group, name) + |> test_data_module_from_data_module + + props = gen_props(annotations) + + quote do + unquote(dest_module).get_generator(context, unquote(props)) + end + end + + def get_generator(%Typedef{} = typedef, file_group, annotations) do + dest_module = + FileGroup.dest_module(file_group, typedef) + |> test_data_module_from_data_module + + props = gen_props(annotations) + + quote do + unquote(dest_module).get_generator(context, unquote(props)) + end + end + + def test_data_module_from_data_module(data_module) do + Module.concat(TestData, data_module) + end + + def apply_defaults(struct_) do + apply_defaults(struct_, nil) + end + + def apply_defaults(struct_, context) when is_struct(struct_) do + module_name = + struct_.__struct__ + |> test_data_module_from_data_module() + + apply(module_name, :apply_defaults, [struct_, context]) + end + + def apply_defaults(some_list, context) when is_list(some_list) do + Enum.map(some_list, &apply_defaults(&1, context)) + end + + def apply_defaults(some_map, context) when is_map(some_map) do + Map.new(some_map, fn {k, v} -> {k, apply_defaults(v, context)} end) + end + + def apply_defaults(%MapSet{} = some_set, context) do + MapSet.new(some_set, &apply_defaults(&1, context)) + end + + def apply_defaults(anything_else, _context) do + anything_else + end + + def find_first_realization([], _fns, default) do + default + end + + def find_first_realization([module | rest], {fname, arity} = func, default) do + Code.ensure_loaded(module) + + if function_exported?(module, fname, arity) do + Function.capture(module, fname, arity) + else + find_first_realization(rest, func, default) + end + end + + defp refinement_from_annotations(annotations, field, parse_fun, default) do + annotations + |> Map.get(field) + |> case do + nil -> default + "^" <> v -> annotation_to_pin(v) + v -> parse_fun.(v) + end + end + + defp parse_integer(str) do + str |> Integer.parse() |> elem(0) + end + + defp parse_float(str) do + str |> Float.parse() |> elem(0) + end + + defp annotation_to_pin(anno) do + var_name = + anno + |> String.to_atom() + |> Macro.var(nil) + + quote do + ^unquote(var_name) + end + end + + defp gen_props(annotations) do + to_prop = fn {k, v} -> + v = + case v do + "^" <> v -> annotation_to_pin(v) + v -> v + end + + {k, v} + end + + props_from_annotations = + annotations + |> Enum.map(to_prop) + + quote do + [unquote_splicing(props_from_annotations)] + end + end +end diff --git a/lib/thrift/generator/test_data_generator/enum.ex b/lib/thrift/generator/test_data_generator/enum.ex new file mode 100644 index 00000000..5bcd6bf1 --- /dev/null +++ b/lib/thrift/generator/test_data_generator/enum.ex @@ -0,0 +1,27 @@ +defmodule Thrift.Generator.TestDataGenerator.Enum do + alias Thrift.Generator.TestDataGenerator + + def generate(_schema, name, enum_ast) do + # file_group = schema.file_group + test_data_module_name = TestDataGenerator.test_data_module_from_data_module(name) + # enums = Enum.map(enum_ast.values, fn {vname, _} -> to_name(vname) end) + enums = Enum.map(enum_ast.values, fn {_, val} -> val end) + + quote do + defmodule unquote(test_data_module_name) do + use PropCheck + + def get_generator(context \\ nil, props \\ []) do + oneof(unquote(enums)) + end + end + end + end + + def to_name(key) do + key + |> to_string() + |> String.downcase() + |> String.to_atom() + end +end diff --git a/lib/thrift/generator/test_data_generator/exception.ex b/lib/thrift/generator/test_data_generator/exception.ex new file mode 100644 index 00000000..5e2d343b --- /dev/null +++ b/lib/thrift/generator/test_data_generator/exception.ex @@ -0,0 +1,2 @@ +defmodule Thrift.Generator.TestDataGenerator.Exception do +end diff --git a/lib/thrift/generator/test_data_generator/struct.ex b/lib/thrift/generator/test_data_generator/struct.ex new file mode 100644 index 00000000..6609c970 --- /dev/null +++ b/lib/thrift/generator/test_data_generator/struct.ex @@ -0,0 +1,161 @@ +defmodule Thrift.Generator.TestDataGenerator.Struct do + alias Thrift.Generator.TestDataGenerator + + def generate(schema, name, struct_ast) do + file_group = schema.file_group + test_data_module_name = TestDataGenerator.test_data_module_from_data_module(name) + + struct_fields = Enum.map(struct_ast.fields, &gen_struct_field/1) + draw_fields = Enum.map(struct_ast.fields, &gen_draw(&1, file_group)) + + {fast_access, apply_defaults} = + struct_ast.fields + |> Enum.map(&gen_fast_access_replace/1) + |> Enum.unzip() + + gen = + case draw_fields do + [] -> + quote do + %unquote(name){unquote_splicing(struct_fields)} + end + + draw_fields -> + quote do + let [unquote_splicing(draw_fields)] do + %unquote(name){unquote_splicing(struct_fields)} + end + end + end + + gen_replace = + case fast_access do + [] -> + quote do + unquote(Macro.var(:struct_, nil)) + end + + _otherwise -> + quote do + %unquote(name){unquote_splicing(fast_access)} = struct_ + %{struct_ | unquote_splicing(apply_defaults)} + end + end + + quote do + defmodule unquote(test_data_module_name) do + use PropCheck + alias Thrift.Generator.TestDataGenerator + + def get_generator(context \\ [], props \\ []) do + ctx = Enum.map(context, &handler_module_from_context/1) + + f = + TestDataGenerator.find_first_realization( + ctx, + {:get_generator, 2}, + &get_default_generator/2 + ) + + f.(context, props) + end + + def get_default_generator(context \\ nil, props \\ []) do + unquote(gen) + end + + def apply_defaults(struct_, context \\ nil) do + unquote(gen_replace) + end + + defp handler_module_from_context(context) do + Module.concat(context, unquote(name)) + end + end + end + end + + def gen_struct_field(field_ast) do + quote do + unquote({field_ast.name, Macro.var(field_ast.name, nil)}) + end + end + + def gen_draw(field_ast, file_group) do + annotations = field_ast.annotations + draw_anno = Map.drop(annotations, [:act]) + generator = TestDataGenerator.get_generator(field_ast.type, file_group, draw_anno) + + declared_required = + case field_ast.required do + :default -> true + otherwise -> otherwise + end + + actually_required = Map.get(field_ast.annotations, :act) == "required" + + generator = + if declared_required or actually_required do + generator + else + quote do + oneof([unquote(generator), nil]) + end + end + + quote do + unquote(Macro.var(field_ast.name, nil)) <- unquote(generator) + end + end + + def gen_fast_access_replace(field_ast) do + field_var = Macro.var(field_ast.name, nil) + default = field_ast.default + + access = + quote do + unquote({field_ast.name, field_var}) + end + + with_default = + case default do + nil -> + quote do + Thrift.Generator.TestDataGenerator.apply_defaults(unquote(field_var), context) + end + + default -> + quote do + Thrift.Generator.TestDataGenerator.apply_defaults(unquote(field_var), context) || + unquote(default) + end + end + + replace = + quote do + unquote({field_ast.name, with_default}) + end + + {access, replace} + end + + def gen_apply_defaults(struct_ast, name) do + {fast_access, apply_defaults} = + struct_ast.fields + |> Enum.map(&gen_fast_access_replace/1) + |> Enum.unzip() + + case fast_access do + [] -> + quote do + unquote(Macro.var(:struct_, nil)) + end + + _otherwise -> + quote do + %unquote(name){unquote_splicing(fast_access)} = struct_ + %{struct_ | unquote_splicing(apply_defaults)} + end + end + end +end diff --git a/lib/thrift/generator/test_data_generator/typedef.ex b/lib/thrift/generator/test_data_generator/typedef.ex new file mode 100644 index 00000000..4cad8289 --- /dev/null +++ b/lib/thrift/generator/test_data_generator/typedef.ex @@ -0,0 +1,67 @@ +defmodule Thrift.Generator.TestDataGenerator.Typedef do + alias Thrift.Generator.TestDataGenerator + + def generate(schema, name, typedef_ast) do + file_group = schema.file_group + test_data_module_name = TestDataGenerator.test_data_module_from_data_module(name) + + quote do + defmodule unquote(test_data_module_name) do + use PropCheck + alias Thrift.Generator.TestDataGenerator + + def get_generator(context, props \\ []) + + def get_generator(context, props) when is_list(context) do + ctx = Enum.map(context, &handler_module_from_context/1) + + f = + TestDataGenerator.find_first_realization( + ctx, + {:get_generator, 2}, + &get_default_generator/2 + ) + + f.(context, props) + end + + def get_generator(nil, props) do + get_generator([], props) + end + + def get_generator(context, props) do + get_generator([context], props) + end + + def apply_defaults(example, nil) do + apply_defaults(example, []) + end + + def apply_defaults(example, context) when is_list(context) do + ctx = Enum.map(context, &handler_module_from_context/1) + + f = + TestDataGenerator.find_first_realization( + ctx, + {:apply_defaults, 2}, + &default_apply_defaults/2 + ) + + f.(example, context) + end + + def get_default_generator(context, props) do + unquote(TestDataGenerator.get_generator(typedef_ast.type, file_group, typedef_ast.annotations)) + end + + def default_apply_defaults(example, context) do + TestDataGenerator.apply_defaults(example, context) + end + + def handler_module_from_context(context) do + Module.concat(context, unquote(name)) + end + end + end + end +end diff --git a/lib/thrift/generator/test_data_generator/union.ex b/lib/thrift/generator/test_data_generator/union.ex new file mode 100644 index 00000000..0dde015b --- /dev/null +++ b/lib/thrift/generator/test_data_generator/union.ex @@ -0,0 +1,60 @@ +defmodule Thrift.Generator.TestDataGenerator.Union do + alias Thrift.Generator.TestDataGenerator + alias Thrift.Generator.TestDataGenerator.Struct, as: StructGenerator + + def generate(schema, name, struct_ast) do + file_group = schema.file_group + test_data_module_name = TestDataGenerator.test_data_module_from_data_module(name) + + subgens = Enum.map(struct_ast.fields, &gen_sub_gens(&1, file_group, name)) + fields = Enum.map(struct_ast.fields, &Macro.var(&1.name, nil)) + + {fast_access, apply_defaults} = + struct_ast.fields + |> Enum.map(&StructGenerator.gen_fast_access_replace/1) + |> Enum.unzip() + + gen_replace = + case fast_access do + [] -> + quote do + unquote(Macro.var(:struct_, nil)) + end + + _otherwise -> + quote do + %unquote(name){unquote_splicing(fast_access)} = struct_ + %{struct_ | unquote_splicing(apply_defaults)} + end + end + + quote do + defmodule unquote(test_data_module_name) do + use PropCheck + + def get_generator(context \\ nil, props \\ []) do + unquote_splicing(subgens) + + oneof([unquote_splicing(fields)]) + end + + def apply_defaults(struct_, context \\ nil) do + unquote(gen_replace) + end + end + end + end + + def gen_sub_gens(field_ast, file_group, module_name) do + field_var = Macro.var(field_ast.name, nil) + gen = TestDataGenerator.get_generator(field_ast.type, file_group, field_ast.annotations) + fill_field = [{field_ast.name, field_var}] + + quote do + unquote(field_var) = + let unquote(field_var) <- unquote(gen) do + %unquote(module_name){unquote_splicing(fill_field)} + end + end + end +end diff --git a/lib/thrift/generator/utils.ex b/lib/thrift/generator/utils.ex index 01daddb5..29bb2f6b 100644 --- a/lib/thrift/generator/utils.ex +++ b/lib/thrift/generator/utils.ex @@ -343,4 +343,5 @@ defmodule Thrift.Generator.Utils do defp resolution_failed(%ValueRef{} = ref) do raise "Fatal error: Could not find value: #{ref.referenced_value}" end + end diff --git a/lib/thrift/generator/utils/types.ex b/lib/thrift/generator/utils/types.ex new file mode 100644 index 00000000..4810608d --- /dev/null +++ b/lib/thrift/generator/utils/types.ex @@ -0,0 +1,90 @@ +defmodule Thrift.Generator.Utils.Types do + require Logger + + alias Thrift.AST.{ + Exception, + Struct, + TEnum, + TypeRef, + Union + } + + alias Thrift.Parser.FileGroup + + def typespec(:void, _), do: quote(do: nil) + def typespec(:bool, _), do: quote(do: boolean()) + def typespec(:string, _), do: quote(do: String.t()) + def typespec(:binary, _), do: quote(do: binary) + def typespec(:i8, _), do: quote(do: Thrift.i8()) + def typespec(:i16, _), do: quote(do: Thrift.i16()) + def typespec(:i32, _), do: quote(do: Thrift.i32()) + def typespec(:i64, _), do: quote(do: Thrift.i64()) + def typespec(:double, _), do: quote(do: Thrift.double()) + + def typespec(%TypeRef{} = ref, file_group) do + file_group + |> FileGroup.resolve(ref) + |> typespec(file_group) + end + + def typespec(%TEnum{}, _) do + quote do + non_neg_integer + end + end + + def typespec(%Union{name: name}, file_group) do + dest_module = FileGroup.dest_module(file_group, name) + + quote do + %unquote(dest_module){} + end + end + + def typespec(%Exception{name: name}, file_group) do + dest_module = FileGroup.dest_module(file_group, name) + + quote do + # %unquote(dest_module){} + unquote(dest_module).t() + end + end + + def typespec(%Struct{name: name}, file_group) do + dest_module = FileGroup.dest_module(file_group, name) + + quote do + # %unquote(dest_module){} + unquote(dest_module).t() + end + end + + def typespec({:set, _t}, _) do + quote do + %MapSet{} + end + end + + def typespec({:list, t}, file_group) do + quote do + [unquote(typespec(t, file_group))] + end + end + + def 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 + + def typespec(unknown_typespec, _) do + Logger.error("Unknown type: #{inspect(unknown_typespec)}. Falling back to any()") + + quote do + any() + end + end +end diff --git a/lib/thrift/parser/file_group.ex b/lib/thrift/parser/file_group.ex index 26499fbe..0a249b9a 100644 --- a/lib/thrift/parser/file_group.ex +++ b/lib/thrift/parser/file_group.ex @@ -17,6 +17,7 @@ defmodule Thrift.Parser.FileGroup do Struct, TEnum, TypeRef, + Typedef, Union, ValueRef } @@ -125,6 +126,10 @@ defmodule Thrift.Parser.FileGroup do resolve(group, resolutions[type_name]) end + def resolve(%FileGroup{} = group, %Typedef{type: type}) do + resolve(group, type) + end + def resolve(%FileGroup{resolutions: resolutions} = group, %ValueRef{ referenced_value: value_name }) do @@ -175,6 +180,10 @@ defmodule Thrift.Parser.FileGroup do dest_module(file_group, name) end + def dest_module(file_group, %Typedef{name: name}) do + dest_module(file_group, name) + end + def dest_module(file_group, Constant) do # Default to naming the constants module after the namespaced, camelized # basename of its file. For foo.thrift, this would be `foo.Foo`. @@ -185,6 +194,7 @@ defmodule Thrift.Parser.FileGroup do # (ignoring case), use that instead to avoid generating two modules with # the same spellings but different cases. schema = file_group.schemas[base] + IO.puts("file_group flatten") symbols = [ @@ -217,7 +227,7 @@ defmodule Thrift.Parser.FileGroup do struct_name = name_parts |> Enum.at(1) - |> initialcase() + |> Macro.camelize() case file_group.namespaces[module_name] do nil -> diff --git a/lib/thrift/utils.ex b/lib/thrift/utils.ex new file mode 100644 index 00000000..a36acdfc --- /dev/null +++ b/lib/thrift/utils.ex @@ -0,0 +1,15 @@ +defmodule Thrift.Utils.Web do + + def normalize_q_result(result) do + result + |> Map.from_struct() + |> Enum.find({:success, nil}, fn {_k, v} -> v != nil end) + |> case do + {:success, r} -> {:ok, r} + {_f, err} -> {:error, err} + end + + + end + +end diff --git a/mix.exs b/mix.exs index 5edb3e24..c9a4cd5d 100644 --- a/mix.exs +++ b/mix.exs @@ -85,7 +85,8 @@ defmodule Thrift.Mixfile do # Runtime {:connection, "~> 1.0"}, - {:ranch, "~> 1.6"} + {:ranch, "~> 1.6"}, + {:plug_cowboy, "~> 2.0"} ] end diff --git a/mix.lock b/mix.lock index adb4eb46..a85a5968 100644 --- a/mix.lock +++ b/mix.lock @@ -2,6 +2,8 @@ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, + "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, + "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, "credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"}, "dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"}, "earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"}, @@ -10,15 +12,21 @@ "ex_doc": {:hex, :ex_doc, "0.22.2", "03a2a58bdd2ba0d83d004507c4ee113b9c521956938298eba16e55cc4aba4a6c", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "cf60e1b3e2efe317095b6bb79651f83a2c1b3edcb4d319c421d7fcda8b3aff26"}, "excoveralls": {:hex, :excoveralls, "0.13.1", "b9f1697f7c9e0cfe15d1a1d737fb169c398803ffcbc57e672aa007e9fd42864c", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b4bb550e045def1b4d531a37fb766cbbe1307f7628bf8f0414168b3f52021cce"}, "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, + "httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"}, "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, + "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"}, + "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"}, } diff --git a/src/thrift_parser.yrl b/src/thrift_parser.yrl index 12812b75..c2019b75 100644 --- a/src/thrift_parser.yrl +++ b/src/thrift_parser.yrl @@ -105,7 +105,8 @@ ConstList -> ConstValue Separator ConstList: ['$1'|'$3']. % Typedef Typedef -> typedef FieldType Annotations ident Annotations Separator: - {typedef, '$2', unwrap('$4')}. + build_node('Typedef', line('$1'), '$5', [unwrap('$4'), '$2']). + % {typedef, '$2', unwrap('$4')}. % Enum