Год назад возникла идея переписать весь Java-бекенд на Rust, который я уже несколько лет разрабатываю и поддерживаю. Я нашёл все аналоги библиотек и фреймворков из мира Java в экосистеме Rust:

  • Spring Framework = Axum

  • Jackson = Serde

  • Hibernate = SeaORM

  • Bouncy Castle = Rustls

В мире Rust я не смог найти аналог только одной библиотеке — JasperReports, которая используется для генерации отчётов. По этой причине пришлось отказаться от идеи переписывать весь проект на Rust.

Так как ниша по генерации ответов на Rust пуста, я создал свой open source проект — генератор отчетов.

Решил не изобретать велосипед, поэтому многие идеи взял от JasperReports. Например, чтобы сгенерировать отчёт, нужно две вещи:

  1. Шаблон отчёта.

  2. Данные, которые нужно подставить в отчёт.

Я выбрал шаблон отчёта в формате YAML, а не XML, так как YAML легче читать и редактировать для человека. Кстати, именно по этой причине в JasperReports имеется WYSIWYG-редактор шаблона отчёта — ориентироваться в XML довольно тяжело.

Пример шаблона отчета:

title:
  - header: $P{company_name} Employee Report
    level: 1
page_header:
  - text: "Confidential information\n\n"
    size: 7
column_header:
  - name: Name
    width: 30
  - name: Age
    width: 10
  - name: Salary
    width: 20
row:
  - value: $F(name)
  - value: $F(age)
  - value: $F(salary)
column_footer:
  - value: "Average:"
  - value: $P{average_age}
  - value: $P{average_salary}
page_footer:
  - text: "Tel: +1 123 456 789"
    size: 7
summary:
  - paragraph:
    - text: "Company address: $P{company_address}"
      size: 10

Шаблон состоит из следующих частей:

title - Заголовок отчета. Этот раздел содержит название отчета и используется для ясного обозначения темы или содержания документа. Это первое, что видит пользователь, открывая документ.

page_header - Верхний колонтитул страницы. Эта секция используется для вставки общей информации, которая должна отображаться на верхней части каждой страницы отчета.

column_header - Заголовки столбцов. В этом разделе указываются наименования столбцов и их ширина, что обеспечивает структурированное и четкое представление данных в таблицах отчета.

row - Строки данных. Секция предназначена для отображения конкретных значений данных. Это позволяет последовательно и читаемо представлять информацию о каждом элементе данных в соответствующем столбце.

column_footer - Подвал столбцов. Этот раздел используется для вывода итоговой или подытоживающей информации по данным столбцов.

page_footer - Нижний колонтитул страницы. Здесь размещается информация, которая должна быть легко доступна на каждой странице отчета.

summary - Сводка отчета. Итоговая информация после таблицы отчета.

При переборе строк таблицы доступ к ячейкам можно получить через выражение $F(COLUMN_NAME). Также в отчете поддерживаются глобальные параметры, доступ к которым можно получить через выражение $P(PARAM_NAME).

Пример данных для отчета:

{
   "rows": [
     {
       "name": "John",
       "age": 25,
       "salary": 50000
     },
     {
       "name": "Jane",
       "age": 30,
       "salary": 60000
     },
     {
       "name": "Jim",
       "age": 35,
       "salary": 70000
     }
   ],
   "params": {
     "company_name": "ABCDFG Ltd",
     "company_address": "1234 Elm St, Springfield, IL 62701",
     "average_age": 30,
     "average_salary": 60000
   }
}

Я не стал создавать структуру и требовать, чтобы данные были в неё помещены перед вызовом библиотеки. На вход ожидаю просто набор байтов, в которых будет JSON следующего формата.

Для использования библиотеки Metatron добавьте в файл Cargo.toml:

[dependencies]
metatron = "0.2.1"

Пример использования генератора отчетов:

fn main() {
    let template_vec = std::fs::read("report-template.yaml").unwrap();
    let template = std::str::from_utf8(&template_vec).unwrap();
    let data_vec = std::fs::read("report-data.json").unwrap();
    let data = std::str::from_utf8(&data_vec).unwrap();
    let images = HashMap::new();
    let doc = Report::generate(template, data, &images).unwrap();
    let result = shiva::pdf::Transformer::generate(&doc).unwrap();
    std::fs::write("report.pdf",result.0).unwrap();
}

Обратите внимание на строку 'let result = shiva::pdf::Transformer::generate(&doc).unwrap();'. Я использую для сериализации отчета библиотеку Shiva, которая на текущий момент поддерживает форматы: Plain text, Markdown, HTML и PDF. Другими словами, в строке 'let doc = Report::generate(template, data, &images).unwrap()' мы получаем отчет в Common Document Model (CDM), поддерживаемом библиотекой Shiva. В дальнейшем, по мере роста количества поддерживаемых форматов документов этой библиотекой, Metatron тоже сможет автоматически генерировать отчеты в новых форматах.

В итоге получается такой report.pdf

На текущий момент этот проект находится в стадии MVP (Minimum Viable Product). В ближайшем будущем планирую добавить:

  • Поддержку картинок

  • CLI

  • REST API сервер

  • Суботчеты

Название "Metatron" для библиотеки было вдохновлено несколькими ассоциативными характеристиками ангела Метатрона:

  1. Записывающий ангел: Метатрон часто ассоциируется с канцелярией и записями. Считается, что он записывает все действия и события во Вселенной. Эта ассоциация с делопроизводством и ведением записей отражает основную функцию библиотеки генератора отчетов.

  2. Мост между небесным и земным: Метатрон также известен как посредник между Божественным и человеческим, облегчая общение и понимание. В контексте библиотеки это может символизировать преобразование данных в понятные и доступные формы отчетов.

Исходный код проекта опубликован на GitHub. Если кто-то желает помочь с проектом, пишите на email.

Комментарии (16)


  1. Zer0S2m
    18.04.2024 11:03

    Интересные инструменты создаете, подписался!


    1. igumnov Автор
      18.04.2024 11:03

      Спасибо! Самому интересно кодить )


  1. Konlino
    18.04.2024 11:03

    Удачи!


    1. igumnov Автор
      18.04.2024 11:03

      Спасибо!


  1. Mingun
    18.04.2024 11:03
    +1

    В дальнейшем все же лучше принимать параметры в виде структур, это позволит программно создавать нужные объекты и кормить их библиотеке. конечно, и байтовые массивы можно, но это не типобезопасно (а значит, не в идеологии Rust) и менее эффективно, когда вы этого массива не имеете


    1. igumnov Автор
      18.04.2024 11:03

      Полностью с вами согласен. Думаю, в следующей мажорной версии сделаю то, о чем вы говорите. Мне просто нужно время, чтобы устаканить эту структуру. На данном этапе развития библиотеки решил это специфицировать. Если будут пользователи, которые начнут использовать библиотеку, а я изменю все структуры, им будет очень больно и неудобно переходить на более свежие версии библиотеки. Я не хочу подводить и подставлять своих пользователей


      1. domix32
        18.04.2024 11:03
        +1

        Ещё б Result из main возвращать и элвисом пользоваться вместо тысяч unwrap() хотя бы в примерах.


        1. igumnov Автор
          18.04.2024 11:03
          +1

          Я специально такой пример дал, а то новичков в Rust эти ? ставят в ступор )))


  1. 0x6b73ca
    18.04.2024 11:03
    +1

    А пророк божий в наивности? Который будет читать письмена писаря божьего


    1. igumnov Автор
      18.04.2024 11:03

      )


  1. blood_develop
    18.04.2024 11:03

    А разные источники данных будут поддерживаться? Мы долгое время сидели на devexpress (c#)

    Ну и там есть возможность и хранимки скл использовать в качестве источников данных, и апи-запросы в различных форматах ответов получать.

    А так же возможность указывать параметры запросов - пользовательский фильтр данных. Эти параметры пробрасывать в запросы


    1. igumnov Автор
      18.04.2024 11:03

      Честно говоря, я не думал о планировании источников данных — спасибо за идею. Можно ли вас попросить составить список из 5 самых популярных источников данных?

      Думаю, это хорошая идея добавить систему плагинов для них.


      1. blood_develop
        18.04.2024 11:03
        +1

        Ну, перечислить оные, думаю, не составит труда)

        Если реализовывать в виде плагинов, то для мвп будет полезным, имхо, реализовать такие источники данных как:

        • Скл-запросы в одну любую распростаненную субд типа постгрес/мсскл/мускул

        • Запрос данных из файла одного из распространенных форматов типа json/xml/csv

        • HTTP(S) запросы с поддержкой одного из распространенных типов ответов типа JSON

        • Ну а все остальное уже мб сообщество допилит по аналогии



  1. TriggerDev
    18.04.2024 11:03

    Дика извиняюсь, разговор не по теме, но вам не кажется что генерация изображений к библиотеке выглядит слишком пёстро?

    А так статья хорошая, инструменты создаёте полезные. Всех благ в разработке!


    1. igumnov Автор
      18.04.2024 11:03

      Не совсем понял про изображения. Пока что картинки не добавлены в отчеты. Стоит в TODO. Можете пояснить свою мысль?