Бесформенная библиотека служит отличной основой для создания универсальных повторно используемых компонентов. Мы демонстрируем использование типов HList
и Generic
для разбора строк в классы case.
Введение
Этот пост в блоге дополняет предстоящую статью Обработка журнала в реальном времени с помощью потоков Akka, которая включает в себя обработку записей журнала из журнала веб-сервера. Для удобства и ясности мы хотим разбить строки строки файла журнала на экземпляры класса case, что позволит нам обрабатывать их семантическим образом.
Существует бесчисленное множество библиотек для разбора строк; мы будем использовать scala-csv и shapeless, чтобы продемонстрировать общий и расширяемый подход. Часть кода, который мы используем, основана на примере CSV в бесформенной кодовой базе.
Получить источник
Исходный код примера проекта доступен на github.
Формат журнала
В нашем примере используется формат журнала веб-сервера со следующими компонентами:
- Удаленный IP-адрес
- Время в миллисекундах (метка времени Unix)
- Путь запроса
- Пользовательский агент
Это примерно соответствует следующей конфигурации журнала 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
, тип которого является экземпляром Generic
trait с представлением типа 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
:
дальнейшее чтение
- Типовые классы и родовое происхождение на meta.plasm.us
- бесформенный на github
- скала-csv на github
Эта статья изначально была опубликована в Блоге BeCompany.