<commit_before>/*
 * Copyright 2016 <NAME>, <EMAIL>
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.amient.affinity.core.http

import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model.Uri.{Path, Query}
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.ws.UpgradeToWebSocket

import scala.annotation.tailrec
import scala.concurrent.Promise

object RequestMatchers {

  object WEBSOCK {
    def unapply(exchange: HttpExchange): Option[(Path, Query, WebSocketExchange)] = {
      exchange.request.header[UpgradeToWebSocket] map {
        case upgrade: UpgradeToWebSocket => (exchange.request.uri.path,exchange.request.uri.query(),  WebSocketExchange(upgrade, exchange.promise))
      }
    }
  }

  object HTTP {
    def unapply(exchange: HttpExchange): Option[(HttpMethod, Path, Query, Promise[HttpResponse])] = {
      exchange.request.header[UpgradeToWebSocket] match {
        case None => Some(exchange.request.method, exchange.request.uri.path, exchange.request.uri.query(), exchange.promise)
        case _ => None
      }
    }
  }

  def HTTP_(method: HttpMethod, exchange: HttpExchange): Option[(ContentType, RequestEntity, Path, Query, Promise[HttpResponse])] = {
    exchange.request.method match {
      case `method` =>
        Some(exchange.request.entity.contentType, exchange.request.entity, exchange.request.uri.path, exchange.request.uri.query(), exchange.promise)
      case _ => None
    }
  }

  object HTTP_POST {
    def unapply(exchange: HttpExchange): Option[(ContentType, RequestEntity, Path, Query, Promise[HttpResponse])] = HTTP_(POST, exchange)
  }

  object HTTP_PUT {
    def unapply(exchange: HttpExchange): Option[(ContentType, RequestEntity, Path, Query, Promise[HttpResponse])] = HTTP_(PUT, exchange)
  }

  object PATH {
    def unapplySeq(path: Path): Option[Seq[String]] = {
      @tailrec
      def r(p: Path, acc: Seq[String] = Seq()): Seq[String] =
        if (p.isEmpty) acc
        else if (p.startsWithSlash) r(p.tail, acc)
        else if (p.tail.isEmpty) acc :+ p.head.toString
        else r(p.tail, acc :+ p.head.toString)

      Some(r(path))
    }
  }

  object INT {
    def unapply(any: Any): Option[Int] = {
      try {
        Some(Integer.parseInt(any.toString))
      } catch {
        case e: NumberFormatException => None
      }
    }
  }

  object LONG {
    def unapply(any: Any): Option[Long] = {
      try {
        Some(java.lang.Long.parseLong(any.toString))
      } catch {
        case e: NumberFormatException => None
      }
    }
  }

  object QUERY {
    def unapplySeq(query: Query): Option[Seq[(String, String)]] = Some(query.sortBy(_._1))
  }

}<commit_msg>all request matchers now log errors and return None when matching errors occur<commit_after>/*
 * Copyright 2016 <NAME>, <EMAIL>
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.amient.affinity.core.http

import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model.Uri.{Path, Query}
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.ws.UpgradeToWebSocket
import org.slf4j.LoggerFactory

import scala.annotation.tailrec
import scala.concurrent.Promise
import scala.util.control.NonFatal

object RequestMatchers {

  private val logger = LoggerFactory.getLogger(RequestMatchers.getClass)

  object WEBSOCK {
    def unapply(exchange: HttpExchange): Option[(Path, Query, WebSocketExchange)] = try {
      exchange.request.header[UpgradeToWebSocket] map {
        case upgrade: UpgradeToWebSocket => (exchange.request.uri.path, exchange.request.uri.query(), WebSocketExchange(upgrade, exchange.promise))
      }
    } catch {
      case NonFatal(e) =>
        logger.warn("Error while matching WEBSOCK request", e)
        None
    }
  }

  object HTTP {
    def unapply(exchange: HttpExchange): Option[(HttpMethod, Path, Query, Promise[HttpResponse])] = try {
      exchange.request.header[UpgradeToWebSocket] match {
        case None => Some(exchange.request.method, exchange.request.uri.path, exchange.request.uri.query(), exchange.promise)
        case _ => None
      }
    } catch {
      case NonFatal(e) =>
        logger.warn("Error while matching HTTP request", e)
        None
    }
  }

  private def HTTP_(method: HttpMethod, exchange: HttpExchange): Option[(ContentType, RequestEntity, Path, Query, Promise[HttpResponse])] = try {
    exchange.request.method match {
      case `method` =>
        Some(exchange.request.entity.contentType, exchange.request.entity, exchange.request.uri.path, exchange.request.uri.query(), exchange.promise)
      case _ => None
    }
  } catch {
    case NonFatal(e) =>
      logger.warn(s"Error while matching HTTP($method) request", e)
      None
  }

  object HTTP_POST {
    def unapply(exchange: HttpExchange): Option[(ContentType, RequestEntity, Path, Query, Promise[HttpResponse])] = HTTP_(POST, exchange)
  }

  object HTTP_PUT {
    def unapply(exchange: HttpExchange): Option[(ContentType, RequestEntity, Path, Query, Promise[HttpResponse])] = HTTP_(PUT, exchange)
  }

  object PATH {
    def unapplySeq(path: Path): Option[Seq[String]] = try {
      @tailrec
      def r(p: Path, acc: Seq[String] = Seq()): Seq[String] =
        if (p.isEmpty) acc
        else if (p.startsWithSlash) r(p.tail, acc)
        else if (p.tail.isEmpty) acc :+ p.head.toString
        else r(p.tail, acc :+ p.head.toString)

      Some(r(path))
    } catch {
      case NonFatal(e) =>
        logger.warn(s"Error while matching request PATH($path)", e)
        None
    }
  }

  object INT {
    def unapply(any: Any): Option[Int] = try {
      Some(Integer.parseInt(any.toString))
    } catch {
      case NonFatal(e) =>
        logger.warn(s"Error while matching INT($any) request path component", e)
        None
    }
  }

  object LONG {
    def unapply(any: Any): Option[Long] = try {
      Some(java.lang.Long.parseLong(any.toString))
    } catch {
      case NonFatal(e) =>
        logger.warn(s"Error while matching LONG($any) request path component", e)
        None
    }
  }

  object QUERY {
    def unapplySeq(query: Query): Option[Seq[(String, String)]] = try {
      Some(query.sortBy(_._1))
    } catch {
      case NonFatal(e) =>
        logger.warn(s"Error while matching request QUERY($query)", e)
        None
    }
  }

}
{'fraction_non_alphanumeric': 0.11209326159364591, 'fraction_numerical': 0.002434025108890597, 'mean_word_length': 3.3276053215077606, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 26, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5528592', 'n_tokens_mistral': 2360, 'n_tokens_neox': 2279, 'n_words': 651}