open source · одно ядро на Rust · MIT

Спецификации API,которые запускаются.

Опишите каждый эндпоинт в текстовых .outry-файлах рядом с кодом. Запускайте их из терминала, редактора, приложения или CI — а когда код изменится, Outry покажет, какие запросы ему больше не соответствуют.

$curl -fsSL https://raw.githubusercontent.com/1rowvy/outry/master/install.sh | sh
~/shop-api — outry
~/shop-api $ 
✓ api/auth/login.outry       Login          POST  200  84ms
✓ api/users/create.outry     CreateUser     POST  201  41ms
✓ status == 201
✓ body matches User
✓ api/orders/create.outry    CreateOrder    POST  201  57ms
✓ body.total == 25
✓ api/orders/wait-paid.outry WaitUntilPaid  GET   200  3 polls 2.1s
✓ api/flows/checkout.outry   Checkout       flow  5 requests 2.4s
5 passed, 0 failed · 1 login, cached 4× · 2.6s
0коллекций для экспорта
1движок для CLI, приложения, редакторов и CI
3роутера Go: chi · gin · net/http
∞запросов за прогон — и один Login()

01 / формат

Один файл — это документация, запрос и тест.

Запрос .outry читается как HTTP, который он отправляет. Наведите на часть, чтобы увидеть, что она делает.

api/orders/create.outry
// Creates an order for the current user.CreateOrder: POST /v1/orders {  only: [dev, staging]   headers {    Authorization: "Bearer ${Login().body.token}"    Idempotency-Key: uuid()  }   body {    customer: CreateUser(name: "Ada").body.id,    items: [{ sku: "BOOK-1", qty: 2 }],  }   expect {    status == 201    body.total == 25    body matches Order  }   save order_id = body.id}
  1. 01
    Имя

    Запросы называются как функции и уникальны в проекте. Импорты не нужны.

  2. 02
    Окружения

    only ограничивает, где запрос может выполняться — напрямую или через вызов.

  3. 03
    Вызовы

    Login() — это запрос. Вызван десять раз — отправлен один раз за прогон.

  4. 04
    Тело

    Литералы как в JSON, с выражениями, функциями вроде uuid() и вызовами.

  5. 05
    Проверки

    Каждая строка — условие. Выполняются все; при ошибке видны обе стороны.

  6. 06
    Формы

    matches Order проверяет структуру — именованной формой или JSON Schema.

  7. 07
    Сохранение

    Типизированные значения для следующих запросов, хранятся вне репозитория.

02 / синхронизация

Он замечает, когда код меняется.

Outry разбирает Go-сервис через tree-sitter и сравнивает каждый запрос с его обработчиком: метод, путь, поля и типы тела, query, заголовки, middleware, тип ответа. Расхождение роняет CI на нужной строке — вместе с исправлением.

  1. 1поле структуры переименовано
  2. 2outry check
  3. 3--fix правит запрос
internal/api/users.go
package users type CreateUser struct {	Name     string `json:"name"`FullName string `json:"full_name"`	Email    string `json:"email"`} func (h *Handler) Create(c *gin.Context) {	var in CreateUser	if err := c.ShouldBindJSON(&in); err != nil {		c.JSON(400, errBody(err))		return	}	c.JSON(201, h.svc.Create(in))}
api/v1/users/post.outry
CreateUser: POST /v1/users {  handler: users.Create   body {    name: "Ada",full_name: "Ada",    email: "ada@example.com",  }   expect {    status == 201    body matches User  }}
$ outry check
api/v1/users/post.outry:5:5: error: required field `full_name` (string) is missing from body ← internal/api/users.go:4
api/v1/users/post.outry:5:5: error: body field `name` is not in users.CreateUser ← internal/api/users.go:4
9 files, 2 environments, 12 Go routes: 2 errors, 0 warnings; 2 fixable with `outry import go --fix`$ outry import go --fix
~ api/v1/users/post.outry  POST /v1/users  internal/api/users.go:8
fixed 2 differences in 1 file
$ outry check
9 files, 2 environments, 12 Go routes: 0 errors, 0 warnings# git diff internal/api/users.go
-	Name     string `json:"name"`
+	FullName string `json:"full_name"`

chi · gin · net/http (паттерны Go 1.22) · префиксы групп · Mount между пакетами · middleware

03 / композиция

Запросы складываются как функции.

flow — это сценарий. Вызовы сами подтягивают зависимости, делят одну банку cookies и один кеш вызовов, а каждый шаг виден в трассе с ответом и временем.

api/flows/checkout.outry
// A user buys one item and pays.flow Checkout {  user  = CreateUser(name: "Ada")  order = CreateOrder(customer: user.body.id)  Pay(order: order.body.id)  WaitUntilPaid(id: order.body.id)   expect {    GetOrder(id: order.body.id).body.status == "paid"  }  save last_order = order.body.id}
trace00.6s1.2s1.8s2.4s
✓Checkoutflow
2.4s
201CreateUserPOST /v1/users
120ms
200LoginPOST /auth/login
84ms
201CreateOrderPOST /v1/orders
160ms
200Loginиз кеша
0ms
202PayPOST /v1/orders/o_81f/pay
95ms
200WaitUntilPaidGET /v1/orders/o_81f
2.0s
200GetOrderGET /v1/orders/o_81f
55ms
poll каждую 1sитого 2.39s · 7 requests · 1 cookie jar

04 / архитектура

Один движок везде.

Парсер, вычислитель, HTTP, переменные, секреты и импорт из Go живут в outry-core. Всё остальное — тонкие обёртки, поэтому то, что проходит у вас, проходит и в CI.

outry-coreRust · парсер · eval · reqwest · tree-sitterCLIrun · check · fmt · importПриложениеTauri 2 · автообновлениеVS CodeLSP + просмотр ответаЛюбой редакторNeovim · Helix · ZedCIаннотации · JSON Lines
  • outry-coreRust · парсер · eval · reqwest · tree-sitter
  • CLIrun · check · fmt · import
  • ПриложениеTauri 2 · автообновление
  • VS CodeLSP + просмотр ответа
  • Любой редакторNeovim · Helix · Zed
  • CIаннотации · JSON Lines

05 / переменные

У каждого значения ровно один источник.

Переменные ищутся по фиксированной цепочке, а outry vars показывает, из какого слоя пришло каждое значение. Секреты — в системном хранилище паролей или в OUTRY_*.

  1. 1--varпереопределение на один прогон
  2. 2saveсохранено из ответов
  3. 3OUTRY_<NAME>переменные окружения, секреты CI
  4. 4env.toml [env.X] → [vars]лежит в репозитории
  5. 5keyringхранилище паролей, по запросу
$ outry vars -e dev
project shop-api · env dev
base     env.toml   http://localhost:8080
token    keychain   eyJ…(212 chars)
order_id saved      o_81f
page     --var      2
api_key  missing    not set: outry secret set api_key --env dev

06 / CI

Спецификация и есть тесты.

Сначала статические проверки — синтаксис, граф вызовов, переменные каждого окружения, форматирование, расхождения с Go — без единого запроса. Потом прогон на живом сервисе.

.github/workflows/api.yml
name: API testson: [push, pull_request] jobs:  api:    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v4      - run: curl -fsSL https://raw.githubusercontent.com/1rowvy/outry/master/install.sh | sh      - run: outry check --format github      - run: outry run api --env ci --no-keyring --fail-fast        env:          OUTRY_TOKEN: ${{ secrets.API_TOKEN }}
outry checksyntax · calls · 3 envs · fmt · 18 Go routes0.4s
outry run api --env ci42 requests · 6 flows · 118 checks9.8s
annotationapi/orders/create.outry#L7PR

error variable `api_key` is not defined in staging, prod

0 passed1 failed2 could not start

07 / документация

Читайте документацию.

Руководства разбирают реальные задачи; справочник описывает каждое ключевое слово, флаг и ключ конфига.

CLI коротко

outry run <пути|имена>
отправить запросы, выполнить проверки
outry check
статический анализ, окружения, fmt, Go
outry fmt
канонический стиль, как gofmt
outry import go
создать и сверить запросы по роутам
outry vars -e prod
все переменные и их источники
outry secret set token
положить секрет в хранилище
outry lsp
языковой сервер для любого редактора
outry convert
.http → .outry

Держите API рядом с его кодом.

Linux, macOS и Windows. Один статический бинарник, без рантайма.

$curl -fsSL https://raw.githubusercontent.com/1rowvy/outry/master/install.sh | sh