Scedel: DSL для описания схем данных
- пятница, 20 февраля 2026 г. в 00:00:05
Идея создания нового языка пришла мне в голову, когда я получил задачу описать ТЗ для подрядчика на разработку API. Передо мной встал вопрос: как легко и понятно описать требования к контрактам? Первым делом я подумал о JSON Schema, однако из-за её многословности я решил отказаться от неё, на мой взгляд, она недостаточно человекочитаема. Я перебрал еще варианты, которые могли помочь мне решить проблему: Proto, Typescript, Cue, даже об SQL подумал. Все немного не подходило под мою задачу. В итоге, я остановился на описании контракта на Typescript, и уточнении требований в комментариях.
Тогда-то мне и пришла в голову мысль разработать подходящий инструмент. Это наложилось на моё желание попрактиковаться в написании языков, и я взялся за дело.
Для начала, я составил список того, для чего будет использоваться и каким принципам будет следовать новый язык. Я остановился на том, что главной задачей будет создание единой точки правды для контрактов взаимодействия между различными системами. На основе описанной схемы можно валидировать данные, генерировать код, а также расширять одни схемы другими.
Основными же принципами я выделил следующее:
Описание схемы должно быть легко прочитано человеком, причём не только программистом, но человеком смежных профессий: аналитиком, PO, PM.
Язык не должен указывать на то, какие технологии будут использоваться для реализации контракта.
Необходимо давать возможность расширять правила своими собственными и дать возможность опосредованно управлять процессом валидации и генерации.
Хотя я взял за основу Typescript, как язык с очень богатым функционалом описания типов, решил отказаться от совместимости с каким-либо языком, так как это могло повредить читаемости и лаконичности.
Валидатор - это правило проверки соответствия данных схеме. Как пример, вот описание валидатора для типа String под названием alphaOnly
validator String(alphaOnly) = this matches /[a-zA-z]+/
А вот более полное описание для валидатора, запрещающего слова, в которых больше i прописных букв подряд:
validator String(noCaps, i=2) = { rule: not this matches /.*[A-Z]{$i,}.*/ message: 'Must not have CAPS words with length more than $i' }
Тип - описание структуры данных. Типы могут иметь ограничения, основанные на валидаторах.
type Status = 'banned'|'active' type User = { username: String(alphaOnly, min:3, max:10) status: Status }
include "./myDateTime.scedel" type Status = 'banned'|'active' type User = { username: String(alphaOnly, min:3, max:10) status: Status bannedAt: when status = 'banned' then DateTimeFormatted else absent }
Здесь также можно увидеть механизм условных типов и особый absent, который заявляет, что поле должно отсутствовать.
Аннотации позволяют давать инструкции сторонним инструментам. Например, мы можем указать, в какую директорию должен складывать сгенерированные файлы генератор кода. Язык не определяет набор аннотаций, они диктуются сторонним инструментарием.
@php.codegen.namespace = "App\\Entities" @php.codegen.dir = "src/Entities" type Comment = { @js.native.ignore id: Uint text: String @php.symfony.ignore createdAt: DateTimeFormatted? default now() }
Практически весь доступный функционал языка можно увидеть в следующем примере:
scedel-version 1.0 include "https://example.com/remote.scedel" include "./local.scedel" /* * multiline * comment */ validator String(numeric) = this matches /[0-9]+/ validator String(notNumeric) = not this matches /[0-9]+/ validator String(noCaps, i=2) = { rule: not this matches /.*[A-Z]{$i,}.*/ message: 'Must not have CAPS words with length more than $i' } // line comment type AliasType = Int type ConstrainedType = Float(min:10) type UnionType = 'First'|"Second" type NullableType1 = Bool|Null type NullableType2 = ?Bool type NegativeConstrainted = Url(domain: not ["localhost", '127.0.0.1']) type RecordType = { field1: String(max:10) fieldUnion: "Draft"|'Published' conditionalField: when this.recordField.subField2 = 'https://test.com' then UnionType else absent optionalField?: DateTime(format:"YYYY-MM-DD") nullableField: ?IpV6 dependentTypeFieldFrom: DateTime(max: this.dependentTypeFieldTo) dependentTypeFieldTo: DateTime(min: this.dependentTypeFieldFrom) fieldWithCustomValidator: String(noCaps) dictField: dict<String(max:10), String(max:255)> recordField: { subField1: Binary, subField2: Url(scheme:'https') } } type ArrayType = Ushort[min:1, max:3] type ComplexType = Email(domain:'gmail.com')[min:1]|False type IntersectType = RecordType & {additionalField: Url} @symfony.codegen.dir='src/Entities' @reactjs.codegen.dir='Entities' type WithAnnotationsType = Uuid @php.codegen.namespace='\\App\\Entities' on WithAnnotationsType
Давайте посмотрим, как Scedel может использоваться в реальности.
Предположим, Github выложил описание контрактов своего апи на https://api.github.com/contracts/issues.scedel
type GithubUser = { id: Ulong login: String(min:1) } type GithubIssue = { id: Ulong number: Int(min:1) title: String(min:1) state: "open" | "closed" locked: Bool user: GithubUser closed_at: when this.state = "closed" then DateTime else absent }
Вы хотите создать интеграцию своего сервиса с Гитхабом. Для этого вы можете создать собственный .scedel-файл типа:
include "https://api.github.com/contracts/issues.scedel" @php.codegen.symfony.namespace='\\App\\Entities' on GithubUser @php.codegen.symfony.namespace\='\\App\\Entities' on GithubIssue
Получившаяся схема будет вашим источником правды, который вы сможете использовать по своему усмотрению. Например, вы можете запустить генератор кода, который, опираясь на схему и аннотации, создаст набор классов, соответствующих этому контракту. Причем, вы можете аннотировать схему от Гитхаба для разных стеков, например, для бэкенда и фронтенда, и, играя аннотациями, генерировать независимый код для PHP и JS с общей базой.
Весь существующий материал сейчас находится на Гитхабе https://github.com/ScedelLang
Имеется довольно объемный RFC
https://github.com/ScedelLang/grammar/blob/main/RFC-Scedel-0.14.2.md
ANTLR-грамматика
https://github.com/ScedelLang/grammar/blob/main/Scedel-0.14.2.g4
Плагин для Idea для подсветки синтаксиса
https://github.com/ScedelLang/idea-plugin
И еще несколько пакетов для PHP и JS (на подходе C# и Python)
https://github.com/orgs/ScedelLang/repositories
Для каждого языка есть парсер, построитель репозитория схемы, валидатор JSON и генератор кода.
Всё распространяется под лицензией MIT.
Конечно, продукт пока еще сырой (на что намекает текущая мажорная версия RFC 0.14.2), и может быть не готов к использованию в серьёзном продакшене. Грамматика может еще активно меняться. Есть несколько идей по развитию языка, например, глобальные аннотации и неймспейсы. Однако Scedel уже может быть вам полезен.
Буду благодарен за конструктивную критику и приглашаю поучаствовать в развитии проекта.