DSLparser
Go-пакет для парсинга, валидации и выполнения DSL (Domain Specific Language) выражений для систем фрод-мониторинга. Пакет реализует специфичный язык запросов для проверки транзакций на мошенничество.
Содержание
Установка
go get github.com/MxAer/DSLparser
Импортируйте в ваш проект:
import "github.com/MxAer/DSLparser"
Быстрый старт
package main
import (
"fmt"
"github.com/MxAer/DSLparser"
)
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.