No description
| example | ||
| dsl.go | ||
| errors.go | ||
| evaluator.go | ||
| go.mod | ||
| LICENSE | ||
| parser.go | ||
| README.md | ||
| validator.go | ||
DSLparser
Go-пакет для парсинга, валидации и выполнения DSL (Domain Specific Language) выражений для систем фрод-мониторинга. Пакет реализует специфичный язык запросов для проверки транзакций на мошенничество.
Содержание
Установка
go get balyukhub.ru/igor/dsl-parser
Импортируйте в ваш проект:
import "balyukhub.ru/igor/dsl-parser"
Быстрый старт
package main
import (
"fmt"
"balyukhub.ru/igor/dsl-parser"
)
func main() {
// Создаем контекст транзакции
ctx := &dsl.EvaluationContext{
Transaction: dsl.Transaction{
Amount: 15000.50,
Currency: "RUB",
},
User: dsl.User{
Age: dsl.IntPtr(25),
Region: dsl.StringPtr("RU"),
},
}
// Валидируем выражение
expr := "amount > 10000 AND currency = 'RUB'"
valid, errors := dsl.Validate(expr, dsl.Tier3)
if valid {
// Вычисляем результат
result, description, err := dsl.Evaluate(expr, ctx)
fmt.Printf("Result: %v\nExpression: %s\n", result, description)
} else {
for _, err := range errors {
fmt.Printf("Error: %s\n", err.Message)
}
}
}
Синтаксис DSL
Базовые элементы
1. Поля (Fields)
amount - сумма транзакции (число)
currency - валюта транзакции (строка)
merchantId - ID мерчанта (строка)
ipAddress - IP-адрес (строка)
deviceId - ID устройства (строка)
user.age - возраст пользователя (число, может быть null)
user.region - регион пользователя (строка, может быть null)
2. Литералы (Literals)
- Числа:
1000,50.5,0.01 - Строки:
'RUB','USD','127.0.0.1'(в одинарных кавычках)
3. Операторы сравнения (Comparison Operators)
> - больше
>= - больше или равно
< - меньше
<= - меньше или равно
= - равно
!= - не равно
4. Логические операторы (Logical Operators)
AND - логическое И (регистронезависимый)
OR - логическое ИЛИ (регистронезависимый)
NOT - логическое НЕ (регистронезависимый)
5. Группировка (Grouping)
( ... ) - скобки для изменения приоритета
Грамматика (EBNF)
expression = term { "OR" term }
term = factor { "AND" factor }
factor = "NOT" factor | comparison | "(" expression ")"
comparison = field operator value
field = "amount" | "currency" | "merchantId" | "ipAddress" | "deviceId"
| "user.age" | "user.region"
operator = ">" | ">=" | "<" | "<=" | "=" | "!="
value = number | string
string = "'" { character } "'"
number = digit { digit } [ "." digit { digit } ]
Приоритет операторов
NOT- наивысший приоритетAND- средний приоритетOR- низший приоритет- Скобки
( )- изменяют приоритет
Правила типизации
-
Числовые поля (
amount,user.age):- Поддерживают все операторы сравнения:
>,>=,<,<=,=,!= - Сравниваются только с числовыми литералами
- Поддерживают все операторы сравнения:
-
Строковые поля (
currency,merchantId,ipAddress,deviceId,user.region):- Поддерживают только операторы:
=,!= - Сравниваются только со строковыми литералами
- Регистрозависимое сравнение
- Поддерживают только операторы:
-
Null-безопасность (только для полей пользователя):
- Если
user.ageилиuser.regionравноnull - Любое сравнение с
nullвозвращаетfalse - Пример:
user.age > 18→false(еслиuser.age = null)
- Если
Форматирование выражений
Пакет автоматически нормализует выражения:
AND/OR/NOTприводятся к верхнему регистру- Добавляются пробелы вокруг операторов
- Сохраняется логическая эквивалентность
Пример нормализации:
amount>100 and currency='RUB' → amount > 100 AND currency = 'RUB'
Уровни поддержки
Tier 0 - Базовый режим
- Все выражения считаются невалидными
- Возвращается ошибка
DSL_UNSUPPORTED_TIER - Используется для начальной интеграции без поддержки DSL
Tier 1 - Минимальный набор
- Поля:
amount - Операторы:
>,>=,<,<=,=,!= - Литералы: числа
- Пример:
amount > 1000
Tier 2 - Строковые поля
- Добавляет поля:
currency,merchantId,ipAddress,deviceId - Добавляет литералы: строки в одинарных кавычках
- Ограничение: для строк только
=и!= - Пример:
currency = 'USD'
Tier 3 - Базовая логика
- Добавляет операторы:
AND,OR - Приоритет:
ANDвышеOR - Пример:
amount > 100 AND currency = 'RUB'
Tier 4 - Расширенная логика
- Добавляет оператор:
NOT - Добавляет группировку:
( ... ) - Приоритет:
NOT>AND>OR - Пример:
NOT (amount > 10000 AND merchantId = '123')
Tier 5 - Поля пользователя
- Добавляет поля:
user.age,user.region - Null-безопасность: сравнение с
null→false - Пример:
user.age > 18 AND user.region = 'EU'
API
Основные функции
Validate(expression string, tier Tier) (bool, []ValidationError)
Валидирует DSL выражение для указанного уровня.
Параметры:
expression- строка с DSL выражениемtier- уровень поддержки (0-5)
Возвращает:
bool- true если выражение валидно[]ValidationError- список ошибок (пустой при успехе)
Коды ошибок:
DSL_PARSE_ERROR- синтаксическая ошибкаDSL_INVALID_FIELD- неизвестное полеDSL_INVALID_OPERATOR- недопустимый оператор для типаDSL_UNSUPPORTED_TIER- функция недоступна на текущем уровнеDSL_TOO_COMPLEX- выражение превышает 100 узлов AST
Evaluate(expression string, ctx *EvaluationContext) (bool, string, error)
Вычисляет DSL выражение для заданного контекста.
Параметры:
expression- валидное DSL выражениеctx- контекст с данными транзакции и пользователя
Возвращает:
bool- результат вычисления (true/false)string- нормализованная форма выраженияerror- ошибка вычисления (nil при успехе)
Типы данных
EvaluationContext
type EvaluationContext struct {
Transaction Transaction
User User
}
type Transaction struct {
Amount float64
Currency string
MerchantID string
IPAddress string
DeviceID string
}
type User struct {
Age *int // может быть nil
Region *string // может быть nil
}
Вспомогательные функции
func IntPtr(i int) *int
func StringPtr(s string) *string
Примеры
Пример 1: Простая проверка суммы
expr := "amount > 10000"
ctx := &dsl.EvaluationContext{
Transaction: dsl.Transaction{Amount: 15000},
}
result, normalized, _ := dsl.Evaluate(expr, ctx)
// result = true
// normalized = "amount > 10000"
Пример 2: Проверка валюты и суммы
expr := "amount > 100 AND currency = 'USD'"
ctx := &dsl.EvaluationContext{
Transaction: dsl.Transaction{
Amount: 500,
Currency: "USD",
},
}
// result = true
Пример 3: Сложная логика с пользователем
expr := "NOT (amount > 10000 AND merchantId = 'blocked') OR user.region = 'TRUSTED'"
ctx := &dsl.EvaluationContext{
Transaction: dsl.Transaction{
Amount: 15000,
MerchantID: "blocked",
},
User: dsl.User{
Region: dsl.StringPtr("TRUSTED"),
},
}
// result = true (благодаря второй части OR)
Пример 4: Null-безопасность
expr := "user.age > 18"
ctx := &dsl.EvaluationContext{
User: dsl.User{
Age: nil, // возраст не указан
},
}
// result = false (null → false)
Пример 5: Комбинированные условия
expr := "(amount > 1000 AND currency = 'EUR') OR (amount > 5000 AND currency = 'USD')"
ctx := &dsl.EvaluationContext{
Transaction: dsl.Transaction{
Amount: 6000,
Currency: "USD",
},
}
// result = true
Ограничения
1. Ограничение сложности
- Максимальное количество узлов AST: 100
- При превышении: ошибка
DSL_TOO_COMPLEX - Узел = поле, оператор, литерал или логическая операция
2. Ограничения типов
- Строковые поля: только
=и!= - Числовые поля: все операторы сравнения
- Смешивание типов запрещено:
amount > 'text'→ ошибка
3. Null-обработка
- Только для полей
user.ageиuser.region - Любая операция с
null→false - Не вызывает ошибок выполнения
4. Семантика выполнения
- Short-circuit evaluation для
AND/OR - Не упрощает логически противоречивые выражения
- Пример:
amount > 100 AND amount < 50→ вычисляется какfalse
5. Форматирование
- Автоматическая нормализация пробелов
- Регистронезависимые
AND/OR/NOT - Сохранение логической эквивалентности
Разработка
Добавление новых полей
- Добавить поле в
fieldTypes(тип) - Добавить обработку в
ComparisonNode.Evaluate() - Обновить документацию и грамматику
Добавление новых операторов
- Добавить оператор в
isComparisonOperator() - Добавить обработку в
compare*функции - Обновить валидатор типов
Тестирование
# Запуск тестов
go test ./...
# Тестирование конкретного уровня
go test -v -run "TestTier[1-5]"
# Проверка покрытия
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
Производительность
- Парсинг: O(n) по длине выражения
- Вычисление: O(n) по количеству узлов
- Валидация: O(n) + проверка уровня
- Подходит для обработки тысяч транзакций в секунду
Лицензия
MIT License.