Skip to content

Repository files navigation

cloudevents

OpenYellow telegram chat Ask DeepWiki

Реализация CloudEvents 1.0 для OneScript - вендор-нейтрального описания события: набор контекстных атрибутов плюс полезная нагрузка.

CloudEvents убирает необходимость договариваться о форме события с каждым получателем отдельно: маршрутизаторы, брокеры и системы трассировки читают одни и те же атрибуты, не разбирая полезную нагрузку. Пакет реализует JSON Event Format и привязку к HTTP в двоичном и структурном режимах.

 id  source  specversion  type      subject  time  datacontenttype  dataschema

|-------------------------------|  |----------------------------------------|
      обязательные атрибуты                   опциональные атрибуты

Установка

opm install cloudevents

Использование

Создание события

#Использовать cloudevents

Событие = CloudEvents.Новое("com.example.заказ.создан", "/склад/1")
    .Тема("заказ-42")
    .ТипДанных("application/json")
    .Данные(Новый Структура("Номер,Сумма", 42, 1500));

Сообщить(Событие.ПолучитьИд());     // 01KZ6YEDDC1A5N6SJFE7411R7T
Сообщить(Событие.ПолучитьВремя());  // 2026-08-04T17:52:31.628Z

Атрибут specversion всегда равен 1.0. Если id не задан, он заполняется идентификатором ULID - он уникален и сортируется по времени создания. Если не задано time, подставляется текущий момент UTC.

Все методы построения возвращают само событие, поэтому вызовы выстраиваются в цепочку. Неопределено убирает атрибут:

Событие.Тема(Неопределено);  // subject больше не передаётся

Проверка

Если Не Событие.Корректно() Тогда
    ...
КонецЕсли;

// Перечисляет все нарушения сразу:
// CloudEvents: событие не соответствует спецификации 1.0: не задан обязательный
// атрибут source; не задан обязательный атрибут type
Событие.Проверить();

Значения проверяются сразу при установке, поэтому собрать событие с некорректным атрибутом не получится:

Событие.Ид("");                             // id не может быть пустым
Событие.Время("04.08.2026 17:52");          // не RFC 3339
Событие.ТипДанных("application");           // не RFC 2046
Событие.СхемаДанных("/schemas/order.json"); // dataschema требует абсолютного URI
Событие.Расширение("TraceId", "abc");       // имя не в нижнем регистре

Extension-атрибуты

Событие.Расширение("traceid", "0af7651916cd43dd8448eb211c80319c")
    .Расширение("retries", 5)
    .Расширение("urgent", Истина);

Сообщить(Событие.ПолучитьРасширение("retries"));   // 5
Сообщить(Событие.Расширения().Количество());       // 3

Имена состоят из строчных латинских букв и цифр, начинаются с буквы и не совпадают с основными атрибутами. Значения приводятся к типам спецификации: Дата становится меткой RFC 3339, двоичные данные - строкой Base64.

JSON

Текст = CloudEvents.ВJson(Событие);
// {"specversion":"1.0","id":"01KZ6YEDDC1A5N6SJFE7411R7T","source":"/склад/1",
//  "type":"com.example.заказ.создан","datacontenttype":"application/json",
//  "subject":"заказ-42","time":"2026-08-04T17:52:31.628Z",
//  "traceid":"0af7651916cd43dd8448eb211c80319c","retries":5,"urgent":true,
//  "data":{"Номер":42,"Сумма":1500}}

Сообщить(CloudEvents.ВJson(Событие, Истина));  // с отступами

Полученное = CloudEvents.ИзJson(Текст);

Разбор строгий: чужая версия спецификации, отсутствие обязательного атрибута, значение недопустимого типа, повторяющийся атрибут, одновременные data и data_base64, мусор вместо JSON - всё это исключения, а не молчаливое искажение события.

Двоичное содержимое

Событие = CloudEvents.Новое("com.example.файл.загружен", "/хранилище")
    .ТипДанных("application/octet-stream")
    .ДвоичныеДанные(ПолучитьДвоичныеДанныеИзСтроки("Привет", "UTF-8"));

Сообщить(Событие.ПолучитьДанныеBase64());  // 0J/RgNC40LLQtdGC
Сообщить(CloudEvents.ВJson(Событие));      // ..."data_base64":"0J/RgNC40LLQtdGC"

Данные и ДвоичныеДанные взаимно исключают друг друга: спецификация запрещает одновременное присутствие data и data_base64, поэтому установка одного снимает другое.

HTTP: двоичный режим

Атрибуты уходят в заголовки ce-*, datacontenttype - в обычный Content-Type, телом становятся данные.

Событие = CloudEvents.Новое("com.example.заказ.создан", "/склад/1")
    .Ид("42")
    .Время("2026-08-04T17:52:31.628Z")
    .ТипДанных("application/json")
    .Данные(Новый Структура("Номер,Сумма", 42, 1500));

Сообщение = CloudEvents.ВHttpДвоичный(Событие);

// ce-specversion: 1.0
// ce-id: 42
// ce-source: /%D1%81%D0%BA%D0%BB%D0%B0%D0%B4/1
// ce-type: com.example.%D0%B7%D0%B0%D0%BA%D0%B0%D0%B7.%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD
// ce-time: 2026-08-04T17:52:31.628Z
// Content-Type: application/json
//
// {"Номер":42,"Сумма":1500}

Запрос = Новый HTTPЗапрос("/events");
Для Каждого Заголовок Из Сообщение.Заголовки Цикл
    Запрос.Заголовки.Вставить(Заголовок.Ключ, Заголовок.Значение);
КонецЦикла;
Запрос.УстановитьТелоИзСтроки(Сообщение.Тело);

// Обратно - регистр имён заголовков не важен
Событие = CloudEvents.ИзHttpДвоичного(Сообщение.Заголовки, Сообщение.Тело);

HTTP: структурный режим

Телом становится событие целиком, Content-Type объявляет медиатип формата.

Сообщение = CloudEvents.ВHttpСтруктурный(Событие);

// Content-Type: application/cloudevents+json; charset=UTF-8
// {"specversion":"1.0","id":"42", ... }

Событие = CloudEvents.ИзHttpСтруктурного(Сообщение.Заголовки, Сообщение.Тело);

Публичный API

Модуль CloudEvents

Метод Возвращает Описание
Новое(ТипСобытия = Неопределено, ИсточникСобытия = Неопределено) CloudEvent Новое событие с заполненными specversion, id, time
ВJson(Событие, СОтступами = Ложь) Строка Сериализация в JSON Event Format
ИзJson(Текст) CloudEvent Строгий разбор JSON Event Format
ВHttpДвоичный(Событие) Структура Заголовки (Соответствие) и Тело для двоичного режима
ИзHttpДвоичного(Заголовки, Тело = Неопределено) CloudEvent Событие из двоичного режима
ВHttpСтруктурный(Событие) Структура Заголовки и Тело для структурного режима
ИзHttpСтруктурного(Заголовки, Тело) CloudEvent Событие из структурного режима

Константы медиатипов: МедиатипСобытия (application/cloudevents+json), МедиатипПакетаСобытий (application/cloudevents-batch+json), МедиатипСтруктурногоРежима (application/cloudevents+json; charset=UTF-8), МедиатипJson (application/json).

Класс CloudEvent

Построение - каждый метод возвращает то же событие:

Метод Атрибут Значение
Ид(Значение) id непустая строка
Источник(Значение) source непустой URI-reference
ТипСобытия(Значение) type непустая строка
Тема(Значение) subject непустая строка
Время(Значение) time Строка RFC 3339 либо Дата в UTC
ТипДанных(Значение) datacontenttype медиатип RFC 2046
СхемаДанных(Значение) dataschema абсолютный URI
Данные(Значение) data значение, представимое в JSON
ДвоичныеДанные(Значение) data_base64 ДвоичныеДанные или БуферДвоичныхДанных
Расширение(Имя, Значение) extension Строка, Число, Булево, Дата, двоичные данные

Чтение: ВерсияСпецификации(), ПолучитьИд(), ПолучитьИсточник(), ПолучитьТипСобытия(), ПолучитьТему(), ПолучитьВремя(), ПолучитьВремяКакДату(), ПолучитьТипДанных(), ПолучитьСхемуДанных(), ПолучитьДанные(), ПолучитьДанныеBase64(), ПолучитьДвоичныеДанные(), Расширения(), ПолучитьРасширение(Имя).

Проверка и сериализация: Проверить(), Корректно(), ВСтруктуру(), ВJson(СОтступами = Ложь).

Соответствие спецификации

Реализованы Core 1.0, JSON Event Format и HTTP Protocol Binding.

  • Обязательные атрибуты id, source, specversion, type проверяются на непустоту; specversion всегда 1.0, иная версия при разборе отвергается.
  • Опциональные datacontenttype (RFC 2046), dataschema (абсолютный URI), subject (непустая строка) и time (RFC 3339) проверяются по своим правилам; отсутствующий атрибут не сериализуется, а null при разборе равносилен отсутствию.
  • Типы значений атрибутов: Boolean, Integer (32 бита со знаком), String без управляющих символов, Binary (Base64 по RFC 4648), URI и URI-reference (RFC 3986), Timestamp (RFC 3339). Метка времени сохраняется вместе с исходным смещением, ПолучитьВремяКакДату() переводит её в UTC.
  • Имена extension-атрибутов - строчные латинские буквы и цифры; имя data и имена основных атрибутов запрещены.
  • Уникальность события определяется парой source + id; библиотека заполняет id значением ULID, поэтому пара уникальна по построению.
  • JSON: все атрибуты, включая расширения, - члены верхнего уровня. Данные лежат в data, двоичные - в data_base64; одновременное присутствие обоих членов отвергается. Медиатип структурного представления - application/cloudevents+json, кодировка UTF-8.
  • Если datacontenttype не объявляет JSON (подтип не json и не *+json), то data должно быть строкой - иначе событие некорректно.
  • HTTP, двоичный режим: атрибуты передаются заголовками ce-* (ce-id, ce-source, ce-type, ce-specversion, ce-subject, ce-time, ce-dataschema), datacontenttype - в Content-Type, тело равно данным. Заголовок ce-datacontenttype при разборе отвергается, как требует спецификация.
  • Значения заголовков процентно кодируются: пробел, кавычка, знак процента и всё вне U+0021..U+007E превращаются в %XY по байтам UTF-8. При разборе снимаются кавычки RFC 7230, а некорректные последовательности UTF-8 (например избыточное %C0%A0) отвергаются.
  • HTTP, структурный режим: Content-Type: application/cloudevents+json; charset=UTF-8, тело - сериализованное событие. Медиатип сравнивается без учёта регистра.

Решения и отступления

  • ТипСобытия вместо Тип. Тип - глобальная функция OneScript, её нельзя использовать как имя метода. По той же причине геттеры вынесены в отдельные методы с префиксом Получить, а не совмещены с сеттерами.
  • Имя расширения обязано начинаться с буквы. Спецификация лишь рекомендует это (SHOULD), но имя становится ключом структуры в ВСтруктуру(), а ключ структуры не может начинаться с цифры. Ограничение длины в 20 символов остаётся рекомендацией и не проверяется.
  • Не-ASCII в source и dataschema допускается. Строго по RFC 3986 такие символы требуют процентного кодирования, но /склад/1 - практичное и распространённое значение. Проверяются символы ASCII, а не-ASCII трактуются как символы IRI (RFC 3987) и процентно кодируются при передаче в HTTP-заголовках.
  • data со значением null не отличается от отсутствия данных. Формат JSON позволяет различать эти случаи; здесь Неопределено единообразно означает «атрибут не задан» и для данных тоже.
  • Свой разбор JSON. Встроенное ПрочитатьJSON распознаёт строки, похожие на дату ISO 8601, и превращает их в Дата, обрезая дробные секунды и часовой пояс. Это испортило бы и атрибут time, и любые строки внутри data, поэтому разбор выполняется по RFC 8259 своими средствами, а типы значений сохраняются точно.
  • Двоичный режим HTTP теряет типы расширений. Заголовки несут канонические строки, поэтому Расширение("retries", 5) после обратного преобразования вернётся строкой "5". Так устроена сама привязка.
  • Данные JSON без datacontenttype объявляются явно. При переносе в HTTP такому событию ставится Content-Type: application/json - спецификация рекомендует объявлять подразумеваемый тип при смене привязки.
  • Пакетный режим не поддерживается. application/cloudevents-batch+json при разборе отвергается с внятным сообщением; медиатип пакета доступен как константа.

Тесты

opm install -l
oneunit execute -d ./tests

Лицензия

MIT

About

Единый конверт события: JSON-формат, HTTP-биндинги (структурный и двоичный), валидация атрибутов

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages