  # HandlerWrapper GenServer: wraps a closure for use as a handler
  defmodule HandlerWrapper do
    use GenServer

    def start_link(handler_fn) do
      GenServer.start_link(__MODULE__, handler_fn)
    end

    def init(handler_fn) do
      {:ok, handler_fn}
    end

    def handle_cast({:trait_call, _method, args_json, reply_id}, handler_fn) do
      case Jason.decode(args_json) do
        {:ok, args} ->
          # Build request context from RequestData fields in args
          try do
            conn = build_conn(args)
            response = handler_fn.(conn)
            response_json = Jason.encode!(response)
            Native.complete_trait_call(reply_id, response_json)
          rescue
            _e -> Native.complete_trait_call(reply_id, "{\"error\": \"handler error\"}")
          end

        {:error, _} ->
          Native.complete_trait_call(reply_id, "{\"error\": \"json decode error\"}")
      end

      {:noreply, handler_fn}
    end

    # Convert RequestData JSON to request context struct
    defp build_conn(args) do
      %{{ conn_module }}{
        path_params: args["path_params"] || %{},
        query_params: args["query_params"] || %{},
        headers: args["headers"] || %{},
        cookies: args["cookies"] || %{},
        body: args["body"],
        raw_body: args["raw_body"],
        method: args["method"] || "GET",
        path: args["path"] || "/"
      }
    end
  end
