openapi: 3.1.0
info:
  title: cron-clock
  version: 1.0.0
  description: |
    Разбор cron-выражений. 5 полей (минута час день месяц день-недели) или 6 полей (секунды первыми).
    Поддерживаются списки, диапазоны, шаги, имена (JAN, MON), @aliases, `?`, `L`, `L-n`, `LW`, `nW`, `n#k`, `nL`.
    Дни недели: 0 и 7 воскресенье. Если заданы и число месяца, и день недели (не `*`), срабатывает любое из условий (Vixie cron).
    DST: время из весеннего «окна» сдвигается на его конец (если час фиксирован) или пропускается (если час задан шаблоном);
    время осеннего повтора срабатывает один раз (первое вхождение) или дважды (если час задан шаблоном).
  contact: {name: Ewsir, url: "https://github.com/ewsir247"}
servers:
  - url: http://localhost:8117
paths:
  /api/parse:
    get:
      summary: Объяснение, риски и ближайшие запуски
      parameters:
        - {name: expr, in: query, required: true, schema: {type: string, maxLength: 200}, example: "*/15 9-17 * * 1-5"}
        - {name: tz, in: query, schema: {type: string, default: UTC}, description: Имя IANA, например Europe/Berlin}
        - {name: n, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 10}}
        - {name: start, in: query, schema: {type: string}, description: "ISO 8601; без смещения читается в поясе tz. По умолчанию сейчас"}
      responses:
        "200":
          description: Результат
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Parsed"}
        "422": {$ref: "#/components/responses/Error"}
        "429": {$ref: "#/components/responses/Error"}
  /api/next:
    get:
      summary: Только ближайшие запуски
      parameters:
        - {name: expr, in: query, required: true, schema: {type: string, maxLength: 200}}
        - {name: tz, in: query, schema: {type: string, default: UTC}}
        - {name: n, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 10}}
        - {name: start, in: query, schema: {type: string}}
      responses:
        "200":
          description: Запуски
          content:
            application/json:
              schema:
                type: object
                properties:
                  normalized: {type: string}
                  tz: {type: string}
                  runs: {type: array, items: {$ref: "#/components/schemas/Run"}}
        "422": {$ref: "#/components/responses/Error"}
  /api/compare:
    post:
      summary: Сравнить два выражения на окне в днях
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [a, b]
              properties:
                a: {type: string}
                b: {type: string}
                tz: {type: string, default: UTC}
                window_days: {type: integer, minimum: 1, maximum: 400, default: 30}
                limit: {type: integer, minimum: 1, maximum: 50, default: 10}
                start: {type: string}
      responses:
        "200":
          description: Результат сравнения
          content:
            application/json:
              schema:
                type: object
                properties:
                  relation: {type: string, enum: [identical, equivalent_in_window, different]}
                  runs_a: {type: integer}
                  runs_b: {type: integer}
                  shared: {type: integer}
                  first_shared: {type: array, items: {type: string}}
                  first_only_a: {type: array, items: {type: string}}
                  first_only_b: {type: array, items: {type: string}}
                  truncated: {type: boolean}
        "422": {$ref: "#/components/responses/Error"}
  /api/timezones:
    get:
      summary: Доступные часовые пояса
      responses:
        "200": {description: Список}
  /healthz:
    get:
      summary: Проверка живости
      responses:
        "200": {description: ok}
components:
  responses:
    Error:
      description: Ошибка
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: object
                properties: {code: {type: string}, message: {type: string}, message_en: {type: string}, field: {type: [string, "null"]}}
  schemas:
    Run:
      type: object
      properties:
        utc: {type: string, example: "2026-03-29T01:00:00Z"}
        local: {type: string, example: "2026-03-29T03:00:00+02:00"}
        weekday: {type: string, enum: [Mon, Tue, Wed, Thu, Fri, Sat, Sun]}
        flag: {type: [string, "null"], enum: [shifted, ambiguous_first, ambiguous_second, null]}
    Risk:
      type: object
      properties:
        severity: {type: string, enum: [danger, warning, info]}
        code: {type: string}
        ru: {type: string}
        en: {type: string}
    Parsed:
      type: object
      properties:
        expr: {type: string}
        normalized: {type: string}
        fields: {type: integer, enum: [5, 6]}
        tz: {type: string}
        explain: {type: object, properties: {ru: {type: string}, en: {type: string}}}
        risks: {type: array, items: {$ref: "#/components/schemas/Risk"}}
        runs: {type: array, items: {$ref: "#/components/schemas/Run"}}
