Atdgen - это проект по созданию типов и структур данных, которые можно сериализовать в JSON. Это очень удобно при взаимодействии между несколькими процессами, создании REST API или использовании объектов JSON из других инструментов. Его можно сравнить с схемой JSON или буферами протокола, но с более богатыми типами и большим количеством функций.
Идея состоит в том, чтобы записать список типов в файл спецификации, файл .atd. Затем, запустив atdgen, можно сгенерировать код OCaml или Java для сериализации / десериализации значений этих типов в / из соответствующего json.
До недавнего времени atdgen мог генерировать код только для собственного OCaml. Но добавлена поддержка bucklescript! atdgen инструмент cli по-прежнему является встроенным двоичным кодом OCaml. Но он может выводить некоторый код OCaml, который можно скомпилировать с помощью bucklescript.
Работу по внедрению этой новой функции atdgen профинансировал Ahrefs. Мы высоко ценим инструменты с открытым исходным кодом. И, насколько это возможно, мы предпочитаем вносить свой вклад в существующие проекты с открытым исходным кодом, а не изобретать колесо самостоятельно.
Установка
Чтобы установить atdgen, нам сначала нужно установить opam (менеджер пакетов OCaml), поскольку atdgen не предоставляет готовых к использованию двоичных файлов и распространяется только как исходный пакет через opam. Процедура проста и задокументирована здесь: https://opam.ocaml.org/doc/2.0/Install.html
Затем нам нужно инициализировать opam и создать коммутатор. Подойдет любая версия ocaml выше или равная 4.03.0.
opam init -a
opam switch create . 4.07.1 -y
Как только это будет сделано, мы должны установить разрабатываемую версию atdgen. Поддержка Bucklescript официально не реализована.
opam pin add atd --dev-repo opam pin add atdgen --dev-repo
Убедитесь, что atdgen доступен.
$ which atdgen (current $PWD)/_opam/bin/atdgen
Конечно, нам понадобится Bucklescript.
yarn init yarn add bs-platform --dev
Нам также нужна среда выполнения bucklescript для atdgen, поскольку она в настоящее время не предоставляется самим atdgen. Итак, мы написали и открыли исходный код нашей версии среды выполнения: https://github.com/ahrefs/bs-atdgen-codec-runtime.
Эта среда выполнения отвечает за преобразование между значениями JSON и значениями OCaml. Значения JSON основаны на стандартном типе Js.Json.t, предоставляемом bucklescript, чтобы гарантировать простоту взаимодействия с остальной экосистемой.
Он опубликован на npm для легкой интеграции в проекты Bucklescript.
yarn add @ahrefs/bs-atdgen-codec-runtime
Конфигурация проекта
После предыдущего раздела package.json должен быть почти готов. Мы можем добавить несколько скриптов, чтобы было удобнее компилировать проект. Вот как это должно выглядеть после завершения.
{
"name": "demo-bs-atdgen",
"version": "0.0.1",
"description": "demo of atdgen with bucklescript",
"scripts": {
"clean": "bsb -clean-world",
"build": "bsb -make-world",
"watch": "bsb -make-world -w",
"atdgen": "atdgen -t meetup.atd && atdgen -bs meetup.atd"
},
"devDependencies": {
"bs-platform": "^4.0.5"
},
"peerDependencies": {
"bs-platform": "^4.0.5"
},
"dependencies": {
"@ahrefs/bs-atdgen-codec-runtime": "^1.0.4"
}
}
Конфигурация Bucklescript очень проста. Мы используем базовую конфигурацию, которую можно найти в любом проекте Bucklescript. За исключением того, что нам нужно добавить одну зависимость к bsconfig.json:
{
"name": "demo-bs-atdgen",
"version": "0.0.1",
"sources": {
"dir": "src",
"subdirs": true
},
"package-specs": {
"module": "commonjs",
"in-source": true
},
"suffix": ".bs.js",
"bs-dependencies": [
"@ahrefs/bs-atdgen-codec-runtime"
],
"warnings": {
"error": "+101"
},
"generate-merlin": true,
"namespace": true,
"refmt": 3
}
Первые определения ATD
Пришло время создать первый файл .atd, содержащий наши типы. Эта часть также задокументирована на https://atd.readthedocs.io/en/latest/tutorial.html#getting-started
В этом примере я решил провести встречу. Поместите определения типов в src / meetup.atd.
(* This is a comment. Same syntax as in ocaml. *)
type access = [ Private | Public ]
(* the date will be a float in the json and a Js.Date.t in ocaml *)
type date = float wrap <ocaml module="Js.Date" wrap="Js.Date.fromFloat" unwrap="Js.Date.valueOf">
(* Some people don't want to provide a phone number, make it optional *)
type person = {
name: string;
email: string;
?phone: string nullable;
}
type event = {
access: access;
name: string;
host: person;
date: date;
guests: person list;
}
type events = event list
Мы используем двоичный файл atdgen (скомпилированный ранее) для генерации типов ocaml и кода для сериализации / десериализации этих типов.
atdgen -t meetup.atd # generates an ocaml file containing the types atdgen -bs meetup.atd # generates the code to (de)serialize
Сгенерированные файлы:
- meetup_t.ml (i), которые содержат типы ocaml, соответствующие нашим определениям ATD.
- meetup_bs.ml (i), который содержит код ocaml для преобразования значений json и обратно.
На этом этапе мы можем скомпилировать наш проект.
yarn build
Если все работает правильно, теперь у нас есть два файла .bs.js в каталоге src.
$ tree src src ├── meetup.atd ├── meetup_bs.bs.js ├── meetup_bs.ml ├── meetup_bs.mli ├── meetup_t.bs.js ├── meetup_t.ml └── meetup_t.mli 0 directories, 7 files
На этом этапе мы можем создать новые файлы OCaml / Reason в каталоге src и использовать весь сгенерированный для нас код atdgen. Два примера, чтобы проиллюстрировать это.
Запросить REST API
Обычно atdgen используется для декодирования JSON, возвращаемого REST API. Вот краткий пример с использованием синтаксиса причины и bs-fetch.
let get = (url, decode) =>
Js.Promise.(
Fetch.fetchWithInit(
url,
Fetch.RequestInit.make(~method_=Get, ()),
)
|> then_(Fetch.Response.json)
|> then_(json => json |> decode |> resolve)
);
let v: Meetup_t.events =
get(
"http://localhost:8000/events",
Atdgen_codec_runtime.Decode.decode(Meetup_bs.read_events),
);
Чтение и запись файла JSON
Atdgen for bucklescript не занимается преобразованием строки в объект JSON. Это позволяет нам использовать эффективный парсер json, включенный в nodejs или браузер.
let read_events filename =
(* Read and parse the json file from disk, this doesn't involve atdgen. *)
let json =
Node_fs.readFileAsUtf8Sync filename
|> Js.Json.parseExn
in
(* Turn it into a proper record. The annotation is of course optional. *)
let events: Meetup_t.events =
Atdgen_codec_runtime.Decode.decode Meetup_bs.read_events json
in
events
Обратная операция, преобразование записи в объект JSON и запись его в файл, также проста.
let write_events filename events = Atdgen_codec_runtime.Encode.encode Meetup_bs.write_events events (* turn a list of records into json *) |. Js.Json.stringifyWithSpace 2 (* convert the json to a pretty string *) |> Node_fs.writeFileAsUtf8Sync filename (* write the json in our file *)
Полный пример
Теперь, когда у нас есть функции для чтения и записи событий, мы можем создать небольшой клиентский интерфейс, чтобы распечатать список событий и добавить новые события.
Исходный код полного примера доступен на github.
Вы можете запустить его так:
$ echo "[]" > events.json $ nodejs src/cli.bs.js add louis [email protected] $ nodejs src/cli.bs.js add bob [email protected] $ nodejs src/cli.bs.js print === OCaml/Reason Meetup! summary === date: Tue, 11 Sep 2018 15:04:16 GMT access: public host: bob <[email protected]> guests: 1 === OCaml/Reason Meetup! summary === date: Tue, 11 Sep 2018 15:04:13 GMT access: public host: louis <[email protected]> guests: 1 $ cat events.json [ { "guests": [ { "email": "[email protected]", "name": "bob" } ], "date": 1536678256177, "host": { "email": "[email protected]", "name": "bob" }, "name": "OCaml/Reason Meetup!", "access": "Public" }, { "guests": [ { "email": "[email protected]", "name": "louis" } ], "date": 1536678253790, "host": { "email": "[email protected]", "name": "louis" }, "name": "OCaml/Reason Meetup!", "access": "Public" } ]