Бесформенная библиотека служит отличной основой для создания универсальных повторно используемых компонентов. Мы демонстрируем использование типов HList и Generic для разбора строк в классы case.

Введение

Этот пост в блоге дополняет предстоящую статью Обработка журнала в реальном времени с помощью потоков Akka, которая включает в себя обработку записей журнала из журнала веб-сервера. Для удобства и ясности мы хотим разбить строки строки файла журнала на экземпляры класса case, что позволит нам обрабатывать их семантическим образом.

Существует бесчисленное множество библиотек для разбора строк; мы будем использовать scala-csv и shapeless, чтобы продемонстрировать общий и расширяемый подход. Часть кода, который мы используем, основана на примере CSV в бесформенной кодовой базе.

Получить источник

Исходный код примера проекта доступен на github.

Формат журнала

В нашем примере используется формат журнала веб-сервера со следующими компонентами:

  1. Удаленный IP-адрес
  2. Время в миллисекундах (метка времени Unix)
  3. Путь запроса
  4. Пользовательский агент

Это примерно соответствует следующей конфигурации журнала nginx:

log_format my_log_format
  '"$remote_addr","$msec","$request","$http_user_agent"';

Запись в журнале выглядит следующим образом:

"1.2.3.4","1466585706027","/foo","Chrome"

Мы будем использовать следующий класс case для представления событий журнала:

Разбор строковых значений в объекты

Тип парсера для отдельных элементов CSV-записи определяется трейтом Парсер. Результат синтаксического анализа — либо объект типа T, либо сообщение об ошибке:

Для удобства мы предоставляем парсер, который обрабатывает исключения и возвращает сообщение об ошибке. Это охватывает типичный сценарий использования методов парсера из библиотек Java, например. Integer.parseInt, что выдает NumberFormatException, или DateFormat.parse, что выдает ParseException:

Объект Parsers предоставляет тривиальный анализатор строк. Клиентское приложение может предоставлять дополнительные настраиваемые парсеры. Наше приложение предоставляет парсер для анализа миллисекундных выражений в объекты Joda Time Instant и парсер для IP-адресов:

Разбор CSV-записей

Класс LineParser[T] анализирует запись CSV, представленную как List[String], в экземпляр класса case T. Мы используем тип HList (гетерогенный список) из библиотеки shapeless для выражения типов элементов списка. Список строк сначала преобразуется в экземпляр HList, который, в свою очередь, преобразуется в экземпляр класса case.

Нам понадобятся некоторые типы из библиотеки shapeless:

Сначала мы определяем трейт LineParser[Out], который определяет возможность анализа списка строк в объект типа Out. В случае ошибки мы ожидаем, что ошибки синтаксического анализа отдельных элементов списка будут объединены в список, представленный воплощением Left типа результата Either.

Теперь мы определяем объект-компаньон для нашего трейта LineParser, который предоставляет методы для разбора List[String] экземпляров в HList экземпляров. Методы объявлены как implicit, что делает их доступными в трейте LineParser. Объект предоставляет метод для каждого из Listвоплощений Nil и Cons (::, хотя мы используем +: из-за конфликта имени с бесформенным типом ::).

Ожидается, что метод hnilParser выдаст пустой список и вернет ошибку, если он встретит список, содержащий один или несколько элементов:

Метод hconsParser анализирует конкатенацию головного элемента (тип H) и хвостовой список (тип T), одновременно комбинируя ошибки, которые могут возникнуть на этапах анализа. Обратите внимание на тег типа Parser для типа H, который гарантирует, что тип элемента списка H является членом класса типов Parser, т. е. предоставляется синтаксический анализатор для этого типа. Анализатор позже получается с использованием выражения implicitly[Parser[H]].

Следующий неявный метод преобразует HList R в класс case A, используя бесформенный тип Generic. Мы определяем неявный параметр gen, тип которого является экземпляром Generictrait с представлением типа R. Вызов метода gen.from преобразует представление HList в желаемый экземпляр класса case R.

Метод apply[A] параметризуется типом ожидаемого класса case. Он использует неявно предоставленный синтаксический анализатор для анализа элементов списка. Неявно доступный метод caseClassParser позволяет нам использовать класс case в качестве параметра типа для метода apply.

Собираем все вместе

Класс CsvReader[T] преобразует строки, разделенные запятыми, в объекты типа T.

Метод read разбирает строки источника потока Akka, который испускает элементы типа String, в объекты типа T. Обратите внимание, что параметр типа T имеет контекстную привязку типа LineParser, что гарантирует доступность синтаксического анализатора для этого типа, как объяснялось в предыдущем разделе.

Класс использует класс анализатора CSV из библиотеки scala-csv для разделения строк на списки строк. В случае, если строка была успешно разделена на список строк, создается LineParser[T] и вызывается его метод apply для списка строк.

Метод CsvReader[T].read теперь можно использовать для преобразования источника строк в источник элементов типа T:

дальнейшее чтение

Эта статья изначально была опубликована в Блоге BeCompany.