v8::Handle<v8::Object> TRI_RequestCppToV8(v8::Isolate* isolate,
                                          TRI_v8_global_t const* v8g,
                                          arangodb::GeneralRequest* request,
                                          TRI_action_t const* action) {
  // setup the request
  v8::Handle<v8::Object> req = v8::Object::New(isolate);
  auto context = TRI_IGETC;
  // Example:
  //      {
  //        path : "/full/path/suffix1/suffix2",
  //
  //        prefix : "/full/path",
  //
  //        "suffix" : [
  //          "suffix1",
  //          "suffix2"
  //        ],
  //
  //        "parameters" : {
  //          "init" : "true"
  //        },
  //
  //        "headers" : {
  //          "accept" : "text/html",
  //          "accept-encoding" : "gzip, deflate",
  //          "accept-language" : "de-de,en-us;q=0.7,en;q=0.3",
  //          "user-agent" : "Mozilla/5.0"
  //        },
  //
  //        "cookies" : {
  //          "ARANGODB_SESSION_ID" : "0cwuzusd23nw3qiwui84uwqwqw23e"
  //        },
  //
  //        "requestType" : "GET",
  //        "requestBody" : "... only for PUT and POST ...",
  //        "user" : "authenticatedUser"
  //      }

  TRI_GET_GLOBAL_STRING(AuthorizedKey);
  if (request->authenticated()) {
    req->Set(context, AuthorizedKey, v8::True(isolate)).FromMaybe(false);
  } else {
    req->Set(context, AuthorizedKey, v8::False(isolate)).FromMaybe(false);
  }

  // create user or null
  std::string const& user = request->user();

  TRI_GET_GLOBAL_STRING(UserKey);
  if (user.empty()) {
    req->Set(context, UserKey, v8::Null(isolate)).FromMaybe(false);
  } else {
    req->Set(context, UserKey, TRI_V8_STD_STRING(isolate, user)).FromMaybe(false);
  }

  TRI_GET_GLOBAL_STRING(IsAdminUser);
  if (request->authenticated()) {
    if (user.empty() || ExecContext::current().isAdminUser()) {
      req->Set(context, IsAdminUser, v8::True(isolate)).FromMaybe(false);
    } else {
      req->Set(context, IsAdminUser, v8::False(isolate)).FromMaybe(false);
    }
  } else {
    req->Set(context, IsAdminUser, ExecContext::isAuthEnabled() ? v8::False(isolate)
                                                       : v8::True(isolate)).FromMaybe(false);
  }

  // create database attribute
  std::string const& database = request->databaseName();
  TRI_ASSERT(!database.empty());

  TRI_GET_GLOBAL_STRING(DatabaseKey);
  req->Set(context, DatabaseKey, TRI_V8_STD_STRING(isolate, database)).FromMaybe(false);

  // set the full url
  std::string const& fullUrl = request->fullUrl();
  TRI_GET_GLOBAL_STRING(UrlKey);
  req->Set(context, UrlKey, TRI_V8_STD_STRING(isolate, fullUrl)).FromMaybe(false);

  // set the protocol
  TRI_GET_GLOBAL_STRING(ProtocolKey);
  if (request->transportType() == Endpoint::TransportType::HTTP) {
    req->Set(context, ProtocolKey, TRI_V8_ASCII_STRING(isolate, "http")).FromMaybe(false);
  } else if (request->transportType() == Endpoint::TransportType::VST) {
    req->Set(context, ProtocolKey, TRI_V8_ASCII_STRING(isolate, "vst")).FromMaybe(false);
  }

  // set the connection info
  ConnectionInfo const& info = request->connectionInfo();

  v8::Handle<v8::Object> serverArray = v8::Object::New(isolate);
  TRI_GET_GLOBAL_STRING(AddressKey);
  serverArray->Set(context, AddressKey, TRI_V8_STD_STRING(isolate, info.serverAddress)).FromMaybe(false);
  TRI_GET_GLOBAL_STRING(PortKey);
  serverArray->Set(context, PortKey, v8::Number::New(isolate, info.serverPort)).FromMaybe(false);
  TRI_GET_GLOBAL_STRING(EndpointKey);
  serverArray->Set(context,
                   EndpointKey,
                   TRI_V8_STD_STRING(isolate, Endpoint::uriForm(info.endpoint))).FromMaybe(false);
  TRI_GET_GLOBAL_STRING(ServerKey);
  req->Set(context,
           ServerKey,
           serverArray).FromMaybe(false);

  TRI_GET_GLOBAL_STRING(PortTypeKey);
  req->DefineOwnProperty(TRI_IGETC, PortTypeKey,
                         TRI_V8_STD_STRING(isolate, info.portType()),
                         static_cast<v8::PropertyAttribute>(v8::ReadOnly))
      .FromMaybe(false);  // ignoring return value

  v8::Handle<v8::Object> clientArray = v8::Object::New(isolate);
  clientArray->Set(context, AddressKey, TRI_V8_STD_STRING(isolate, info.clientAddress)).FromMaybe(false);
  clientArray->Set(context, PortKey, v8::Number::New(isolate, info.clientPort)).FromMaybe(false);
  TRI_GET_GLOBAL_STRING(IdKey);
  clientArray->Set(context, IdKey, TRI_V8_STD_STRING(isolate, std::string("0"))).FromMaybe(false);
  TRI_GET_GLOBAL_STRING(ClientKey);
  req->Set(context, ClientKey, clientArray).FromMaybe(false);

  req->Set(context, TRI_V8_ASCII_STRING(isolate, "internals"), v8::External::New(isolate, request)).FromMaybe(false);

  // copy prefix
  std::string path = request->prefix();
  TRI_GET_GLOBAL_STRING(PrefixKey);
  req->Set(context, PrefixKey, TRI_V8_STD_STRING(isolate, path)).FromMaybe(false);

  // copy header fields
  v8::Handle<v8::Object> headerFields = v8::Object::New(isolate);
  // intentional copy, as we will modify the headers later
  auto headers = request->headers();

  std::string const& acceptPlain = request->contentTypeResponsePlain();

  if (!acceptPlain.empty()) {
    headers.emplace(StaticStrings::Accept, acceptPlain);
  } else {
    switch (request->contentTypeResponse()) {
      case ContentType::UNSET:
      case ContentType::CUSTOM:  // use Content-Type from _headers
        break;
      case ContentType::JSON:    // application/json
        headers.emplace(StaticStrings::Accept, StaticStrings::MimeTypeJson);
        break;
      case ContentType::VPACK:   // application/x-velocypack
        headers.emplace(StaticStrings::Accept, StaticStrings::MimeTypeVPack);
        break;
      case ContentType::TEXT:    // text/plain
        headers.emplace(StaticStrings::Accept, StaticStrings::MimeTypeText);
        break;
      case ContentType::HTML:    // text/html
        headers.emplace(StaticStrings::Accept, StaticStrings::MimeTypeHtml);
        break;
      case ContentType::DUMP:    // application/x-arango-dump
        headers.emplace(StaticStrings::Accept, StaticStrings::MimeTypeDump);
        break;
    }
  }

  switch (request->contentType()) {
    case ContentType::UNSET:
    case ContentType::CUSTOM:  // use Content-Type from _headers
      break;
    case ContentType::JSON:    // application/json
      headers.emplace(StaticStrings::ContentTypeHeader, StaticStrings::MimeTypeJson);
      break;
    case ContentType::VPACK:   // application/x-velocypack
      headers.emplace(StaticStrings::ContentTypeHeader, StaticStrings::MimeTypeVPack);
      break;
    case ContentType::TEXT:    // text/plain
      headers.emplace(StaticStrings::ContentTypeHeader, StaticStrings::MimeTypeText);
      break;
    case ContentType::HTML:    // text/html
      headers.emplace(StaticStrings::ContentTypeHeader, StaticStrings::MimeTypeHtml);
      break;
    case ContentType::DUMP:    // application/x-arango-dump
      headers.emplace(StaticStrings::ContentTypeHeader, StaticStrings::MimeTypeDump);
      break;
  }

  TRI_GET_GLOBAL_STRING(HeadersKey);
  req->Set(context, HeadersKey, headerFields).FromMaybe(false);
  TRI_GET_GLOBAL_STRING(RequestTypeKey);
  TRI_GET_GLOBAL_STRING(RequestBodyKey);

  auto setRequestBodyJsonOrVPack = [&]() {
    if (rest::ContentType::UNSET == request->contentType()) {
      bool digestable = false;
      try {
        auto parsed = request->payload(true);
        if (parsed.isObject() || parsed.isArray()) {
          request->setDefaultContentType();
          digestable = true;
        }
      } catch ( ... ) {}
      // ok, no json/vpack after all ;-)
      std::string_view raw = request->rawPayload();
      headers[StaticStrings::ContentLength] =
        StringUtils::itoa(raw.size());
      V8Buffer* buffer = V8Buffer::New(isolate, raw.data(), raw.size());
      auto bufObj = v8::Local<v8::Object>::New(isolate, buffer->_handle);
      TRI_GET_GLOBAL_STRING(RawRequestBodyKey);
      req->Set(context, RawRequestBodyKey, bufObj).FromMaybe(false);
      req->Set(context, RequestBodyKey, TRI_V8_PAIR_STRING(isolate, raw.data(), raw.size())).FromMaybe(false);
      if (!digestable) {
        return;
      }
    }

    if (rest::ContentType::JSON == request->contentType()) {
      std::string_view body = request->rawPayload();
      req->Set(context, RequestBodyKey, TRI_V8_PAIR_STRING(isolate, body.data(), body.size())).FromMaybe(false);
      headers[StaticStrings::ContentLength] =
          StringUtils::itoa(request->contentLength());
    } else if (rest::ContentType::VPACK == request->contentType()) {
      // the VPACK is passed as it is to to JavaScript
      // FIXME not every VPack can be converted to JSON
      VPackSlice slice = request->payload(true);
      std::string jsonString = slice.toJson();

      LOG_TOPIC("8afce", DEBUG, Logger::COMMUNICATION)
          << "json handed into v8 request:\n"
          << jsonString;

      req->Set(context, RequestBodyKey, TRI_V8_STD_STRING(isolate, jsonString)).FromMaybe(false);
      headers[StaticStrings::ContentLength] = StringUtils::itoa(jsonString.size());
      headers[StaticStrings::ContentTypeHeader] = StaticStrings::MimeTypeJson;
    }
  };

  // copy request type
  switch (request->requestType()) {
    case rest::RequestType::POST: {
      TRI_GET_GLOBAL_STRING(PostConstant);
      req->Set(context, RequestTypeKey, PostConstant).FromMaybe(false);
      setRequestBodyJsonOrVPack();
      break;
    }

    case rest::RequestType::PUT: {
      TRI_GET_GLOBAL_STRING(PutConstant);
      req->Set(context, RequestTypeKey, PutConstant).FromMaybe(false);
      setRequestBodyJsonOrVPack();
      break;
    }

    case rest::RequestType::PATCH: {
      TRI_GET_GLOBAL_STRING(PatchConstant);
      req->Set(context, RequestTypeKey, PatchConstant).FromMaybe(false);
      setRequestBodyJsonOrVPack();
      break;
    }
    case rest::RequestType::OPTIONS: {
      TRI_GET_GLOBAL_STRING(OptionsConstant);
      req->Set(context, RequestTypeKey, OptionsConstant).FromMaybe(false);
      break;
    }
    case rest::RequestType::DELETE_REQ: {
      TRI_GET_GLOBAL_STRING(DeleteConstant);
      req->Set(context, RequestTypeKey, DeleteConstant).FromMaybe(false);
      setRequestBodyJsonOrVPack();
      break;
    }
    case rest::RequestType::HEAD: {
      TRI_GET_GLOBAL_STRING(HeadConstant);
      req->Set(context, RequestTypeKey, HeadConstant).FromMaybe(false);
      break;
    }
    case rest::RequestType::GET:
    default: {
      TRI_GET_GLOBAL_STRING(GetConstant);
      req->Set(context, RequestTypeKey, GetConstant).FromMaybe(false);
      break;
    }
  }

  for (auto const& it : headers) {
    headerFields->Set(context,
                      TRI_V8_STD_STRING(isolate, it.first),
                      TRI_V8_STD_STRING(isolate, it.second)).FromMaybe(false);
  }

  // copy request parameter
  v8::Handle<v8::Object> valuesObject = v8::Object::New(isolate);

  for (auto& it : request->values()) {
    valuesObject->Set(context,
                      TRI_V8_STD_STRING(isolate, it.first),
                      TRI_V8_STD_STRING(isolate, it.second)).FromMaybe(false);
  }

  // copy request array parameter (a[]=1&a[]=2&...)
  for (auto& arrayValue : request->arrayValues()) {
    std::string const& k = arrayValue.first;
    std::vector<std::string> const& v = arrayValue.second;

    v8::Handle<v8::Array> list = v8::Array::New(isolate, static_cast<int>(v.size()));

    for (size_t i = 0; i < v.size(); ++i) {
      list->Set(context, (uint32_t)i, TRI_V8_STD_STRING(isolate, v[i])).FromMaybe(false);
    }

    valuesObject->Set(context, TRI_V8_STD_STRING(isolate, k), list).FromMaybe(false);
  }

  TRI_GET_GLOBAL_STRING(ParametersKey);
  req->Set(context, ParametersKey, valuesObject).FromMaybe(false);

  // copy cookie -- only for http protocol
  if (request->transportType() == Endpoint::TransportType::HTTP) {  // FIXME
    v8::Handle<v8::Object> cookiesObject = v8::Object::New(isolate);

    HttpRequest* httpRequest = dynamic_cast<HttpRequest*>(request);
    if (httpRequest == nullptr) {
      // maybe we can just continue
      THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
                                     "invalid request type");
    } else {
      for (auto& it : httpRequest->cookieValues()) {
        cookiesObject->Set(context,
                           TRI_V8_STD_STRING(isolate, it.first),
                           TRI_V8_STD_STRING(isolate, it.second)).FromMaybe(false);
      }
    }
    TRI_GET_GLOBAL_STRING(CookiesKey);
    req->Set(context, CookiesKey, cookiesObject).FromMaybe(false);
  }

  // copy suffix, which comes from the action:
  std::vector<std::string> const& suffixes = request->decodedSuffixes();
  std::vector<std::string> const& rawSuffixes = request->suffixes();

  uint32_t index = 0;
  char const* sep = "";

  size_t const n = suffixes.size();
  v8::Handle<v8::Array> suffixArray =
      v8::Array::New(isolate, static_cast<int>(n - action->_urlParts));
  v8::Handle<v8::Array> rawSuffixArray =
      v8::Array::New(isolate, static_cast<int>(n - action->_urlParts));

  for (size_t s = action->_urlParts; s < n; ++s) {
    suffixArray->Set(context, index, TRI_V8_STD_STRING(isolate, suffixes[s])).FromMaybe(false);
    rawSuffixArray->Set(context, index, TRI_V8_STD_STRING(isolate, rawSuffixes[s])).FromMaybe(false);
    ++index;

    path += sep + suffixes[s];
    sep = "/";
  }

  TRI_GET_GLOBAL_STRING(SuffixKey);
  req->Set(context, SuffixKey, suffixArray).FromMaybe(false);
  TRI_GET_GLOBAL_STRING(RawSuffixKey);
  req->Set(context, RawSuffixKey, rawSuffixArray).FromMaybe(false);

  // copy full path
  TRI_GET_GLOBAL_STRING(PathKey);
  req->Set(context, PathKey, TRI_V8_STD_STRING(isolate, path)).FromMaybe(false);

  return req;
}