mirror of
https://github.com/karust/openserp.git
synced 2026-08-16 05:16:02 +08:00
docs: add architecture, OpenAPI spec, Swagger UI, and contributing guide
This commit is contained in:
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -23,14 +23,22 @@ jobs:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Run unit tests
|
||||
run: go test -race -count=1 ./...
|
||||
|
||||
- name: Run go vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Lint OpenAPI spec
|
||||
run: npx --yes @redocly/cli lint docs/openapi.yaml
|
||||
|
||||
- name: Build
|
||||
run: go build .
|
||||
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
|
||||
120
CONTRIBUTING.md
Normal file
120
CONTRIBUTING.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Contributing to OpenSERP
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.24+
|
||||
- Chromium/Chrome (only required for browser-mode work and integration tests)
|
||||
- Optional: Docker
|
||||
|
||||
### Clone, build, run
|
||||
|
||||
```bash
|
||||
git clone https://github.com/karust/openserp.git
|
||||
cd openserp
|
||||
go build -o openserp .
|
||||
./openserp serve
|
||||
```
|
||||
|
||||
### Test commands
|
||||
|
||||
Unit tests (default, no browser/network assumptions):
|
||||
|
||||
```bash
|
||||
go test -race ./...
|
||||
```
|
||||
|
||||
Integration tests (explicitly enabled):
|
||||
|
||||
```bash
|
||||
OPENSERP_INTEGRATION_TESTS=1 go test -race -timeout=120s ./...
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Integration tests are gated by `testutil.RequireIntegration(t)`.
|
||||
- Do not create browser instances in `init()` or package-level variables.
|
||||
|
||||
## Adding a New Search Engine
|
||||
|
||||
### 1) Create engine package
|
||||
|
||||
Create a new folder (example: `myengine/`) with:
|
||||
|
||||
- `myengine/url.go` (`BuildURL`, and `BuildImageURL` when image support exists)
|
||||
- `myengine/search.go` (browser mode implementation)
|
||||
- `myengine/search_raw.go` (optional raw mode implementation)
|
||||
|
||||
### 2) Implement `core.SearchEngine`
|
||||
|
||||
Your engine type must implement:
|
||||
|
||||
- `Search(core.Query) ([]core.SearchResult, error)`
|
||||
- `SearchImage(core.Query) ([]core.SearchResult, error)`
|
||||
- `IsInitialized() bool`
|
||||
- `Name() string`
|
||||
- `GetRateLimiter() *rate.Limiter`
|
||||
|
||||
Use the existing engines (for example `google/`) as the reference pattern.
|
||||
|
||||
### 3) Register the engine in server wiring
|
||||
|
||||
Update [`cmd/serve.go`](cmd/serve.go):
|
||||
|
||||
- Add engine spec in `browserEngineSpecs()`
|
||||
- Add raw-mode handling if raw support exists
|
||||
|
||||
### 4) Add config block
|
||||
|
||||
Update [`config.yaml`](config.yaml) with your engine section:
|
||||
|
||||
- `rate_requests`
|
||||
- `rate_burst`
|
||||
- optional `proxy` tag
|
||||
- optional engine-specific fields
|
||||
|
||||
### 5) Add tests
|
||||
|
||||
- URL builder tests (table-driven)
|
||||
- Parser tests (prefer deterministic fixtures in `testdata/`)
|
||||
- Integration tests guarded by `testutil.RequireIntegration(t)`
|
||||
|
||||
## Code Style and Quality Checks
|
||||
|
||||
Run these before opening a PR:
|
||||
|
||||
```bash
|
||||
gofmt -w .
|
||||
go vet ./...
|
||||
golangci-lint run
|
||||
go test -race ./...
|
||||
```
|
||||
|
||||
Guidelines:
|
||||
|
||||
- Return `error` values instead of panicking in library code.
|
||||
- Reuse existing patterns in `core/` and existing engines.
|
||||
- Add comments only for non-obvious decisions (why, not what).
|
||||
|
||||
## Test Categories
|
||||
|
||||
- Unit tests: deterministic tests that run with `go test ./...` and do not require browser/network.
|
||||
- Integration tests: live/browser/network dependent tests gated by `OPENSERP_INTEGRATION_TESTS=1`.
|
||||
|
||||
When adding tests, keep unit and integration behavior clearly separated.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
For each PR:
|
||||
|
||||
1. Describe what changed and why.
|
||||
2. Link the related issue (if available).
|
||||
3. Include or update tests for behavior changes.
|
||||
4. Include updated docs when API/config/contracts change.
|
||||
|
||||
If you change API behavior, update:
|
||||
|
||||
- [`docs/openapi.yaml`](docs/openapi.yaml)
|
||||
- [`README.md`](README.md)
|
||||
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) when flow/design changes
|
||||
@@ -4,12 +4,14 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
apidocs "github.com/karust/openserp/docs"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
@@ -84,6 +86,9 @@ func NewServerWithOptions(host string, port int, opts ServerOptions, searchEngin
|
||||
}
|
||||
app.Use(RequestLoggerMiddleware())
|
||||
|
||||
app.Get("/openapi.yaml", serv.handleOpenAPISpec)
|
||||
app.Get("/docs", serv.handleSwaggerUI)
|
||||
app.Get("/docs/", serv.handleSwaggerUI)
|
||||
app.Get("/health", serv.handleHealthCheck)
|
||||
app.Get("/stats", serv.handleStats)
|
||||
app.Get("/stats/cache", serv.handleCacheStats)
|
||||
@@ -563,6 +568,45 @@ func (s *Server) applyProxyHeaders(c *fiber.Ctx, meta ProxyExecutionMeta) {
|
||||
c.Set("X-Proxy-Used", used)
|
||||
}
|
||||
|
||||
func (s *Server) handleOpenAPISpec(c *fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/yaml; charset=utf-8")
|
||||
return c.Send(apidocs.OpenAPIYAML)
|
||||
}
|
||||
|
||||
func (s *Server) handleSwaggerUI(c *fiber.Ctx) error {
|
||||
const specPath = "/openapi.yaml"
|
||||
page := fmt.Sprintf(`<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>OpenSERP API Docs</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
|
||||
<style>
|
||||
body { margin: 0; background: #f6f8fb; }
|
||||
#swagger-ui { max-width: 1200px; margin: 0 auto; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js" crossorigin></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
window.ui = SwaggerUIBundle({
|
||||
url: %q,
|
||||
dom_id: "#swagger-ui",
|
||||
deepLinking: true,
|
||||
displayRequestDuration: true,
|
||||
presets: [SwaggerUIBundle.presets.apis],
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>`, html.EscapeString(specPath))
|
||||
c.Set("Content-Type", "text/html; charset=utf-8")
|
||||
return c.SendString(page)
|
||||
}
|
||||
|
||||
func (s *Server) Listen() error {
|
||||
return s.app.Listen(s.addr)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package core
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -72,6 +73,52 @@ func requestWithHeader(t *testing.T, s *Server, path string, header string, valu
|
||||
return resp
|
||||
}
|
||||
|
||||
func TestOpenAPISpecEndpoint(t *testing.T) {
|
||||
engine := &engineMock{name: "google", initialized: true}
|
||||
srv := NewServerWithOptions("127.0.0.1", 7107, DefaultServerOptions(), engine)
|
||||
|
||||
resp := request(t, srv, "/openapi.yaml")
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected /openapi.yaml to return 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if got := resp.Header.Get("Content-Type"); !strings.Contains(got, "application/yaml") {
|
||||
t.Fatalf("expected YAML content-type, got %q", got)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read /openapi.yaml body: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(body), "openapi: 3.0.3") {
|
||||
t.Fatalf("expected OpenAPI version marker in body")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocsEndpointServesSwaggerUI(t *testing.T) {
|
||||
engine := &engineMock{name: "google", initialized: true}
|
||||
srv := NewServerWithOptions("127.0.0.1", 7108, DefaultServerOptions(), engine)
|
||||
|
||||
resp := request(t, srv, "/docs")
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected /docs to return 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if got := resp.Header.Get("Content-Type"); !strings.Contains(got, "text/html") {
|
||||
t.Fatalf("expected HTML content-type, got %q", got)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read /docs body: %v", err)
|
||||
}
|
||||
content := string(body)
|
||||
if !strings.Contains(content, "SwaggerUIBundle") {
|
||||
t.Fatalf("expected SwaggerUI bundle script on docs page")
|
||||
}
|
||||
if !strings.Contains(content, "/openapi.yaml") {
|
||||
t.Fatalf("expected docs page to reference /openapi.yaml")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidQueryParametersReturnJSONError(t *testing.T) {
|
||||
engine := &engineMock{name: "google", initialized: true}
|
||||
srv := NewServerWithOptions("127.0.0.1", 7104, DefaultServerOptions(), engine)
|
||||
|
||||
265
docs/ARCHITECTURE.md
Normal file
265
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# OpenSERP Architecture
|
||||
|
||||
## 1. Overview
|
||||
|
||||
OpenSERP is a Go API + CLI for search results extraction from Google, Yandex, Baidu, Bing, and DuckDuckGo.
|
||||
|
||||
It supports two execution modes:
|
||||
|
||||
- Browser mode (default): headless Chromium via `go-rod`, with engine-specific DOM parsing.
|
||||
- Raw HTTP mode: direct requests + HTML parsing (`goquery`) for engines that implement raw parsing.
|
||||
|
||||
Browser mode is the primary path and supports all engines. Raw mode currently supports Google, Yandex, and Baidu only.
|
||||
|
||||
## 2. Directory Structure
|
||||
|
||||
```text
|
||||
openserp/
|
||||
├── main.go # Entry point, executes cmd.RootCmd
|
||||
├── AGENTS.md # Contributor + agent project guidance
|
||||
├── README.md # User-facing quickstart and API overview
|
||||
├── config.yaml # Runtime configuration (loaded by Viper)
|
||||
├── docs/
|
||||
│ ├── ARCHITECTURE.md # This architecture reference
|
||||
│ ├── openapi.yaml # OpenAPI 3.0 specification
|
||||
│ └── embed.go # Embeds openapi.yaml for /openapi.yaml endpoint
|
||||
├── cmd/
|
||||
│ ├── root.go # Cobra root command + Viper config binding/defaults
|
||||
│ ├── serve.go # HTTP server bootstrap, engine wiring, browser pooling
|
||||
│ ├── search.go # CLI one-shot search command
|
||||
│ └── proxy_policy.go # Proxy policy mapping from config to runtime
|
||||
├── core/
|
||||
│ ├── common.go # Shared domain types: Query, SearchResult, SearchEngine
|
||||
│ ├── server.go # Fiber routes, request handlers, cache/proxy headers
|
||||
│ ├── middleware.go # CORS, request logging, JSON error envelope
|
||||
│ ├── browser.go # Chromium navigation lifecycle and page orchestration
|
||||
│ ├── http_client.go # Raw HTTP client (uTLS fingerprinting)
|
||||
│ ├── resilient.go # Retry + CB + rate limiting + proxy orchestration
|
||||
│ ├── retry.go # Backoff retry runner and retry conditions
|
||||
│ ├── circuit_breaker.go # Per-engine circuit breaker state machine
|
||||
│ ├── cache.go # In-memory TTL cache for API responses
|
||||
│ ├── proxy.go # Proxy normalization, pools, health/rotation, stats
|
||||
│ ├── logger.go # Logging setup helpers
|
||||
│ └── captcha.go # Captcha-related helpers/errors
|
||||
├── google/ # Google engine implementation
|
||||
│ ├── url.go # URL builders
|
||||
│ ├── search.go # Browser mode parser
|
||||
│ └── search_raw.go # Raw HTTP parser
|
||||
├── yandex/ # Yandex engine implementation
|
||||
│ ├── url.go
|
||||
│ ├── search.go
|
||||
│ └── search_raw.go
|
||||
├── baidu/ # Baidu engine implementation
|
||||
│ ├── url.go
|
||||
│ ├── search.go
|
||||
│ └── search_raw.go
|
||||
├── bing/ # Bing engine implementation (browser-only)
|
||||
│ ├── url.go
|
||||
│ └── search.go
|
||||
├── duckduckgo/ # DuckDuckGo engine implementation (browser-only)
|
||||
│ ├── url.go
|
||||
│ └── search.go
|
||||
├── testutil/ # Integration gating and shared test fixtures/helpers
|
||||
└── .github/workflows/ci.yml # CI checks (test/vet/build/lint/openapi lint)
|
||||
```
|
||||
|
||||
## 3. Key Interfaces and Types
|
||||
|
||||
### `core.SearchEngine`
|
||||
|
||||
Contract for all engines:
|
||||
|
||||
- `Search(Query) ([]SearchResult, error)` for web results
|
||||
- `SearchImage(Query) ([]SearchResult, error)` for image results
|
||||
- `IsInitialized() bool` for health readiness
|
||||
- `Name() string` for endpoint and stats identity
|
||||
- `GetRateLimiter() *rate.Limiter` for per-engine throttling
|
||||
|
||||
### `core.Query`
|
||||
|
||||
Parsed from query parameters and request headers:
|
||||
|
||||
- `Text` (`text`)
|
||||
- `LangCode` (`lang`)
|
||||
- `DateInterval` (`date`, format `YYYYMMDD..YYYYMMDD`)
|
||||
- `Filetype` (`file`)
|
||||
- `Site` (`site`)
|
||||
- `Limit` (`limit`, default `25`)
|
||||
- `Start` (`start`, default `0`)
|
||||
- `Filter` (`filter`, default `true`)
|
||||
- `Answers` (`answers`, default `false`)
|
||||
- `ProxyOverride` (`X-Use-Proxy` header: `<tag>` or `direct`)
|
||||
- Internal runtime fields: `ProxyURL`, `Insecure`
|
||||
|
||||
Validation summary:
|
||||
|
||||
- `start` must be `>= 0`
|
||||
- At least one of `text`, `site`, or `file` must be non-empty
|
||||
- Invalid query parsing is returned as JSON error response
|
||||
|
||||
### `core.SearchResult`
|
||||
|
||||
Single SERP item shape:
|
||||
|
||||
- `rank` (int)
|
||||
- `url` (string)
|
||||
- `title` (string)
|
||||
- `description` (string)
|
||||
- `ad` (bool)
|
||||
|
||||
Mega endpoints return `core.MegaSearchResult`, which extends `SearchResult` with:
|
||||
|
||||
- `engine` (string)
|
||||
|
||||
## 4. Request Flow
|
||||
|
||||
```text
|
||||
HTTP request
|
||||
-> Fiber router
|
||||
-> handleDedicatedEndpoint / handleMegaEndpoint
|
||||
-> Query.InitFromContext
|
||||
-> ResilientSearcher.SearchPrimary/SearchWithFallback (or mega parallel search)
|
||||
-> CircuitBreaker.AllowRequest
|
||||
-> RateLimiter.Wait
|
||||
-> Proxy policy resolution and proxy selection
|
||||
-> RetryableSearch (backoff/retry loop)
|
||||
-> Engine.Search / Engine.SearchImage
|
||||
Browser path: Browser.Navigate(url) -> DOM parse -> []SearchResult
|
||||
Raw path: raw HTTP request -> goquery parse -> []SearchResult
|
||||
-> De-duplication (mega endpoints)
|
||||
-> Cache.Set (if enabled and cacheable)
|
||||
-> JSON response + X-Cache/X-Proxy-*/X-Fallback-Engine headers
|
||||
```
|
||||
|
||||
## 5. Browser vs Raw Mode
|
||||
|
||||
### Browser Mode (default)
|
||||
|
||||
- Enabled when `server.raw_requests: false`
|
||||
- Uses Chromium + `go-rod` navigation and page parsing
|
||||
- Supported engines: Google, Yandex, Baidu, Bing, DuckDuckGo
|
||||
- Best compatibility, but heavier resource usage
|
||||
|
||||
### Raw HTTP Mode
|
||||
|
||||
- Enabled when `server.raw_requests: true`
|
||||
- Uses direct HTTP + HTML parsing without launching a browser
|
||||
- Supported engines: Google, Yandex, Baidu
|
||||
- Faster/lighter, but less reliable for anti-bot protected pages and missing image support
|
||||
|
||||
Mode switch options:
|
||||
|
||||
- Config: `server.raw_requests`
|
||||
- CLI flag: `--raw`
|
||||
|
||||
## 6. Resilience Stack
|
||||
|
||||
The effective request protection sequence is:
|
||||
|
||||
1. Rate limiter (`engine.GetRateLimiter().Wait`)
|
||||
2. Retry with exponential backoff (`core/retry.go`)
|
||||
3. Circuit breaker per engine (`core/circuit_breaker.go`)
|
||||
4. Proxy selection/rotation + health tracking (`core/proxy.go`)
|
||||
5. Response cache (API-level TTL cache in `core/cache.go`)
|
||||
|
||||
Important behaviors:
|
||||
|
||||
- `ErrCaptcha` is non-retryable.
|
||||
- `ErrProxyUnavailable` does not record circuit-breaker failure.
|
||||
- Dedicated endpoints are engine-pure by default (`allow_endpoint_fallback: false`).
|
||||
- Fallback responses are not cached on dedicated endpoints.
|
||||
|
||||
## 7. Config Reference
|
||||
|
||||
Defaults below are the shipped defaults in `config.yaml` (if present). If the config file is missing, fallback defaults from `cmd/root.go` are applied.
|
||||
|
||||
### `server`
|
||||
|
||||
| Key | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `server.host` | `0.0.0.0` | API bind host |
|
||||
| `server.port` | `7000` | API bind port |
|
||||
| `server.debug` | `false` | Debug mode, forces headful browser |
|
||||
| `server.verbose` | `true` | Info-level request logs |
|
||||
| `server.raw_requests` | `false` | `true` = raw HTTP mode |
|
||||
| `server.insecure` | `true` | Allow insecure TLS connections |
|
||||
|
||||
### `app`
|
||||
|
||||
| Key | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `app.timeout` | `15` | Request timeout in seconds |
|
||||
| `app.browser_path` | `""` | Custom browser binary path |
|
||||
| `app.head` | `false` | Headful browser UI |
|
||||
| `app.leakless` | `false` | Force browser process cleanup |
|
||||
| `app.leave_head` | `false` | Keep browser tabs open |
|
||||
| `app.stealth` | `false` | Enable stealth plugin |
|
||||
|
||||
### `proxies`
|
||||
|
||||
| Key | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `proxies.global` | unset | Force single proxy for all engines |
|
||||
| `proxies.entries[]` | empty | Tagged proxy pool entries (`url`, `tags`) |
|
||||
| `proxies.health.failure_threshold` | `3` | Disable proxy after N failures |
|
||||
|
||||
Per-engine optional proxy tag:
|
||||
|
||||
- `google.proxy`
|
||||
- `yandex.proxy`
|
||||
- `baidu.proxy`
|
||||
- `bing.proxy`
|
||||
- `duckduckgo.proxy`
|
||||
|
||||
### `cache`
|
||||
|
||||
| Key | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `cache.ttl_seconds` | `60` | Response cache TTL (0 disables cache) |
|
||||
| `cache.max_size` | `1000` | Max cached entries |
|
||||
|
||||
### `resilience`
|
||||
|
||||
| Key | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `resilience.max_retries` | `2` | Retry attempts per request |
|
||||
| `resilience.allow_endpoint_fallback` | `false` | Allow dedicated endpoints to fallback to other engines |
|
||||
|
||||
### `circuit_breaker`
|
||||
|
||||
| Key | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `circuit_breaker.failures` | `5` | Failures before opening circuit |
|
||||
| `circuit_breaker.recovery_seconds` | `60` | Open -> half-open wait time |
|
||||
| `circuit_breaker.successes` | `2` | Half-open successes to close circuit |
|
||||
|
||||
### `cors`
|
||||
|
||||
| Key | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `cors.enabled` | `true` | Enable CORS middleware |
|
||||
| `cors.allow_origins` | `"*"` | Allowed origins |
|
||||
| `cors.allow_methods` | `"GET, POST, OPTIONS"` | Allowed methods |
|
||||
| `cors.allow_headers` | `"Origin, Content-Type, Accept, Authorization, X-Use-Proxy"` | Allowed headers |
|
||||
| `cors.max_age` | `86400` | Preflight cache max age (seconds) |
|
||||
|
||||
### `2captcha`
|
||||
|
||||
| Key | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `2captcha.apikey` | unset | Optional captcha solver key |
|
||||
|
||||
### Engine rate-limit defaults
|
||||
|
||||
For each engine (`google`, `yandex`, `baidu`, `bing`, `duckduckgo`):
|
||||
|
||||
| Key | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `<engine>.rate_requests` | `4` | Average requests per minute |
|
||||
| `<engine>.rate_burst` | `2` | Burst capacity |
|
||||
| `<engine>.rate_seconds` | `60` (implicit) | Rate window seconds |
|
||||
| `<engine>.selector_timeout` | `5` (implicit) | Selector wait timeout seconds |
|
||||
|
||||
Google-only additional toggle:
|
||||
|
||||
- `google.captcha` (default: `true`)
|
||||
8
docs/embed.go
Normal file
8
docs/embed.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package docs
|
||||
|
||||
import _ "embed"
|
||||
|
||||
// OpenAPIYAML is the bundled OpenAPI specification served by /openapi.yaml.
|
||||
//
|
||||
//go:embed openapi.yaml
|
||||
var OpenAPIYAML []byte
|
||||
880
docs/openapi.yaml
Normal file
880
docs/openapi.yaml
Normal file
@@ -0,0 +1,880 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: OpenSERP API
|
||||
version: 0.6.3
|
||||
description: >
|
||||
OpenSERP provides dedicated and multi-engine search endpoints for Google, Yandex,
|
||||
Baidu, Bing, and DuckDuckGo. Responses are normalized into a shared schema and
|
||||
include runtime metadata via response headers.
|
||||
license:
|
||||
name: MIT
|
||||
url: https://opensource.org/licenses/MIT
|
||||
servers:
|
||||
- url: http://127.0.0.1:7000
|
||||
description: Local default server
|
||||
security: []
|
||||
tags:
|
||||
- name: Search
|
||||
description: Dedicated per-engine search endpoints
|
||||
- name: Mega
|
||||
description: Cross-engine aggregated search endpoints
|
||||
- name: Health
|
||||
description: Health and readiness endpoints
|
||||
- name: Stats
|
||||
description: Runtime statistics endpoints
|
||||
- name: Docs
|
||||
description: OpenAPI and Swagger UI endpoints
|
||||
paths:
|
||||
/{engine}/search:
|
||||
get:
|
||||
tags: [Search]
|
||||
operationId: searchWeb
|
||||
summary: Search web results from a specific engine
|
||||
description: >
|
||||
Engine path values are `google`, `yandex`, `baidu`, `bing`, and `duck` (`duck` maps
|
||||
to DuckDuckGo internally).
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/EnginePath"
|
||||
- $ref: "#/components/parameters/TextQuery"
|
||||
- $ref: "#/components/parameters/LangQuery"
|
||||
- $ref: "#/components/parameters/DateQuery"
|
||||
- $ref: "#/components/parameters/FileQuery"
|
||||
- $ref: "#/components/parameters/SiteQuery"
|
||||
- $ref: "#/components/parameters/LimitQuery"
|
||||
- $ref: "#/components/parameters/StartQuery"
|
||||
- $ref: "#/components/parameters/FilterQuery"
|
||||
- $ref: "#/components/parameters/AnswersQuery"
|
||||
- $ref: "#/components/parameters/UseProxyHeader"
|
||||
responses:
|
||||
"200":
|
||||
description: Search results
|
||||
headers:
|
||||
X-Cache:
|
||||
$ref: "#/components/headers/XCache"
|
||||
X-Fallback-Engine:
|
||||
$ref: "#/components/headers/XFallbackEngine"
|
||||
X-Proxy-Mode:
|
||||
$ref: "#/components/headers/XProxyMode"
|
||||
X-Proxy-Tag:
|
||||
$ref: "#/components/headers/XProxyTag"
|
||||
X-Proxy-Used:
|
||||
$ref: "#/components/headers/XProxyUsed"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/SearchResult"
|
||||
examples:
|
||||
default:
|
||||
value:
|
||||
- rank: 1
|
||||
url: https://go.dev/doc/
|
||||
title: The Go Programming Language
|
||||
description: Official Go language documentation.
|
||||
ad: false
|
||||
"503":
|
||||
$ref: "#/components/responses/ServiceUnavailableError"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalServerError"
|
||||
/{engine}/image:
|
||||
get:
|
||||
tags: [Search]
|
||||
operationId: searchImages
|
||||
summary: Search image results from a specific engine
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/EnginePath"
|
||||
- $ref: "#/components/parameters/TextQuery"
|
||||
- $ref: "#/components/parameters/LangQuery"
|
||||
- $ref: "#/components/parameters/DateQuery"
|
||||
- $ref: "#/components/parameters/FileQuery"
|
||||
- $ref: "#/components/parameters/SiteQuery"
|
||||
- $ref: "#/components/parameters/LimitQuery"
|
||||
- $ref: "#/components/parameters/StartQuery"
|
||||
- $ref: "#/components/parameters/FilterQuery"
|
||||
- $ref: "#/components/parameters/AnswersQuery"
|
||||
- $ref: "#/components/parameters/UseProxyHeader"
|
||||
responses:
|
||||
"200":
|
||||
description: Image search results
|
||||
headers:
|
||||
X-Cache:
|
||||
$ref: "#/components/headers/XCache"
|
||||
X-Fallback-Engine:
|
||||
$ref: "#/components/headers/XFallbackEngine"
|
||||
X-Proxy-Mode:
|
||||
$ref: "#/components/headers/XProxyMode"
|
||||
X-Proxy-Tag:
|
||||
$ref: "#/components/headers/XProxyTag"
|
||||
X-Proxy-Used:
|
||||
$ref: "#/components/headers/XProxyUsed"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/SearchResult"
|
||||
examples:
|
||||
default:
|
||||
value:
|
||||
- rank: 1
|
||||
url: https://upload.wikimedia.org/example/golang.png
|
||||
title: Golang logo image
|
||||
description: Example image result
|
||||
ad: false
|
||||
"503":
|
||||
$ref: "#/components/responses/ServiceUnavailableError"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalServerError"
|
||||
/mega/search:
|
||||
get:
|
||||
tags: [Mega]
|
||||
operationId: megaSearch
|
||||
summary: Search across multiple engines in parallel
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/TextQuery"
|
||||
- $ref: "#/components/parameters/LangQuery"
|
||||
- $ref: "#/components/parameters/DateQuery"
|
||||
- $ref: "#/components/parameters/FileQuery"
|
||||
- $ref: "#/components/parameters/SiteQuery"
|
||||
- $ref: "#/components/parameters/LimitQuery"
|
||||
- $ref: "#/components/parameters/StartQuery"
|
||||
- $ref: "#/components/parameters/FilterQuery"
|
||||
- $ref: "#/components/parameters/AnswersQuery"
|
||||
- $ref: "#/components/parameters/EnginesQuery"
|
||||
- $ref: "#/components/parameters/UseProxyHeader"
|
||||
responses:
|
||||
"200":
|
||||
description: Aggregated and de-duplicated results
|
||||
headers:
|
||||
X-Cache:
|
||||
$ref: "#/components/headers/XCache"
|
||||
X-Proxy-Mode:
|
||||
$ref: "#/components/headers/XProxyMode"
|
||||
X-Proxy-Tag:
|
||||
$ref: "#/components/headers/XProxyTag"
|
||||
X-Proxy-Used:
|
||||
$ref: "#/components/headers/XProxyUsed"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/MegaSearchResult"
|
||||
examples:
|
||||
default:
|
||||
value:
|
||||
- rank: 1
|
||||
url: https://go.dev/doc/
|
||||
title: The Go Programming Language
|
||||
description: Official documentation.
|
||||
ad: false
|
||||
engine: google
|
||||
- rank: 2
|
||||
url: https://pkg.go.dev/
|
||||
title: Go Packages
|
||||
description: Go package documentation.
|
||||
ad: false
|
||||
engine: bing
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequestError"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalServerError"
|
||||
/mega/image:
|
||||
get:
|
||||
tags: [Mega]
|
||||
operationId: megaImageSearch
|
||||
summary: Image search across multiple engines in parallel
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/TextQuery"
|
||||
- $ref: "#/components/parameters/LangQuery"
|
||||
- $ref: "#/components/parameters/DateQuery"
|
||||
- $ref: "#/components/parameters/FileQuery"
|
||||
- $ref: "#/components/parameters/SiteQuery"
|
||||
- $ref: "#/components/parameters/LimitQuery"
|
||||
- $ref: "#/components/parameters/StartQuery"
|
||||
- $ref: "#/components/parameters/FilterQuery"
|
||||
- $ref: "#/components/parameters/AnswersQuery"
|
||||
- $ref: "#/components/parameters/EnginesQuery"
|
||||
- $ref: "#/components/parameters/UseProxyHeader"
|
||||
responses:
|
||||
"200":
|
||||
description: Aggregated and de-duplicated image results
|
||||
headers:
|
||||
X-Cache:
|
||||
$ref: "#/components/headers/XCache"
|
||||
X-Proxy-Mode:
|
||||
$ref: "#/components/headers/XProxyMode"
|
||||
X-Proxy-Tag:
|
||||
$ref: "#/components/headers/XProxyTag"
|
||||
X-Proxy-Used:
|
||||
$ref: "#/components/headers/XProxyUsed"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/MegaSearchResult"
|
||||
examples:
|
||||
default:
|
||||
value:
|
||||
- rank: 1
|
||||
url: https://upload.wikimedia.org/example/gopher.png
|
||||
title: Go Gopher
|
||||
description: Example image
|
||||
ad: false
|
||||
engine: duckduckgo
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequestError"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalServerError"
|
||||
/mega/engines:
|
||||
get:
|
||||
tags: [Mega]
|
||||
operationId: listMegaEngines
|
||||
summary: List available engines and runtime state
|
||||
responses:
|
||||
"200":
|
||||
description: Engine list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/MegaEnginesResponse"
|
||||
examples:
|
||||
default:
|
||||
value:
|
||||
total: 5
|
||||
engines:
|
||||
- name: google
|
||||
initialized: true
|
||||
circuit_state: closed
|
||||
- name: bing
|
||||
initialized: true
|
||||
circuit_state: closed
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
/health:
|
||||
get:
|
||||
tags: [Health]
|
||||
operationId: healthCheck
|
||||
summary: Service health status
|
||||
responses:
|
||||
"200":
|
||||
description: Healthy or degraded service
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HealthStatus"
|
||||
examples:
|
||||
healthy:
|
||||
value:
|
||||
status: healthy
|
||||
uptime: 1h2m3s
|
||||
engines:
|
||||
- name: google
|
||||
initialized: true
|
||||
status: ready
|
||||
system:
|
||||
goroutines: 32
|
||||
memory_mb: 128
|
||||
go_version: go1.24.6
|
||||
"503":
|
||||
description: Unhealthy service
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HealthStatus"
|
||||
examples:
|
||||
unhealthy:
|
||||
value:
|
||||
status: unhealthy
|
||||
uptime: 12m10s
|
||||
engines:
|
||||
- name: google
|
||||
initialized: false
|
||||
status: not_initialized
|
||||
system:
|
||||
goroutines: 14
|
||||
memory_mb: 96
|
||||
go_version: go1.24.6
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
/stats:
|
||||
get:
|
||||
tags: [Stats]
|
||||
operationId: getStats
|
||||
summary: Combined cache, proxy, and circuit-breaker stats
|
||||
responses:
|
||||
"200":
|
||||
description: Runtime statistics
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/StatsResponse"
|
||||
examples:
|
||||
default:
|
||||
value:
|
||||
cache:
|
||||
status: true
|
||||
entries: 10
|
||||
hits: 200
|
||||
misses: 15
|
||||
bypasses: 4
|
||||
evictions: 0
|
||||
ttl_seconds: 60
|
||||
max_size: 1000
|
||||
proxy:
|
||||
configured_count: 2
|
||||
healthy_count: 2
|
||||
unhealthy_count: 0
|
||||
tags:
|
||||
default:
|
||||
configured: 2
|
||||
healthy: 2
|
||||
entries:
|
||||
- proxy: http://proxy1:8080
|
||||
tags: [default]
|
||||
healthy: true
|
||||
failures: 0
|
||||
disabled: false
|
||||
engines:
|
||||
google:
|
||||
tag: default
|
||||
selected_proxy: pooled
|
||||
circuit_breakers:
|
||||
- engine: google
|
||||
state: closed
|
||||
failure_count: 0
|
||||
last_changed: 2026-04-15T08:00:00Z
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
/stats/cache:
|
||||
get:
|
||||
tags: [Stats]
|
||||
operationId: getCacheStats
|
||||
summary: Cache statistics only
|
||||
responses:
|
||||
"200":
|
||||
description: Cache status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CacheStats"
|
||||
examples:
|
||||
enabled:
|
||||
value:
|
||||
status: true
|
||||
entries: 1
|
||||
hits: 1
|
||||
misses: 2
|
||||
bypasses: 1
|
||||
evictions: 0
|
||||
ttl_seconds: 60
|
||||
max_size: 1000
|
||||
disabled:
|
||||
value:
|
||||
status: false
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
/stats/proxy:
|
||||
get:
|
||||
tags: [Stats]
|
||||
operationId: getProxyStats
|
||||
summary: Proxy pool and per-engine proxy policy statistics
|
||||
responses:
|
||||
"200":
|
||||
description: Proxy stats payload
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ProxyStats"
|
||||
examples:
|
||||
default:
|
||||
value:
|
||||
configured_count: 1
|
||||
healthy_count: 1
|
||||
unhealthy_count: 0
|
||||
tags:
|
||||
us:
|
||||
configured: 1
|
||||
healthy: 1
|
||||
entries:
|
||||
- proxy: http://proxy-us:8080
|
||||
tags: [us]
|
||||
healthy: true
|
||||
failures: 0
|
||||
disabled: false
|
||||
engines:
|
||||
google:
|
||||
tag: us
|
||||
selected_proxy: pooled
|
||||
yandex:
|
||||
selected_proxy: direct
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
/stats/cb:
|
||||
get:
|
||||
tags: [Stats]
|
||||
operationId: getCircuitBreakerStats
|
||||
summary: Circuit breaker state per engine
|
||||
responses:
|
||||
"200":
|
||||
description: Circuit breaker stats payload
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CircuitBreakerStatsResponse"
|
||||
examples:
|
||||
default:
|
||||
value:
|
||||
circuit_breakers:
|
||||
- engine: google
|
||||
state: open
|
||||
failure_count: 5
|
||||
last_changed: 2026-04-15T08:00:00Z
|
||||
retry_in: 43
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
/openapi.yaml:
|
||||
get:
|
||||
tags: [Docs]
|
||||
operationId: getOpenAPISpec
|
||||
summary: Get raw OpenAPI YAML
|
||||
responses:
|
||||
"200":
|
||||
description: OpenAPI YAML
|
||||
content:
|
||||
application/yaml:
|
||||
schema:
|
||||
type: string
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
/docs:
|
||||
get:
|
||||
tags: [Docs]
|
||||
operationId: getSwaggerUI
|
||||
summary: Swagger UI for interactive API docs
|
||||
responses:
|
||||
"200":
|
||||
description: HTML page loading Swagger UI from CDN
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
type: string
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
components:
|
||||
parameters:
|
||||
EnginePath:
|
||||
name: engine
|
||||
in: path
|
||||
required: true
|
||||
description: Search engine endpoint alias (`duck` is DuckDuckGo).
|
||||
schema:
|
||||
type: string
|
||||
enum: [google, yandex, baidu, bing, duck]
|
||||
TextQuery:
|
||||
name: text
|
||||
in: query
|
||||
required: false
|
||||
description: >
|
||||
Search query text. At least one of `text`, `site`, or `file` must be non-empty.
|
||||
schema:
|
||||
type: string
|
||||
example: golang
|
||||
LangQuery:
|
||||
name: lang
|
||||
in: query
|
||||
required: false
|
||||
description: Language code (engine-specific behavior).
|
||||
schema:
|
||||
type: string
|
||||
example: EN
|
||||
DateQuery:
|
||||
name: date
|
||||
in: query
|
||||
required: false
|
||||
description: Date interval in `YYYYMMDD..YYYYMMDD` format.
|
||||
schema:
|
||||
type: string
|
||||
pattern: "^[0-9]{8}\\.\\.[0-9]{8}$"
|
||||
example: 20250101..20250131
|
||||
FileQuery:
|
||||
name: file
|
||||
in: query
|
||||
required: false
|
||||
description: File extension filter (for engines that support it).
|
||||
schema:
|
||||
type: string
|
||||
example: PDF
|
||||
SiteQuery:
|
||||
name: site
|
||||
in: query
|
||||
required: false
|
||||
description: Site/domain filter.
|
||||
schema:
|
||||
type: string
|
||||
example: github.com
|
||||
LimitQuery:
|
||||
name: limit
|
||||
in: query
|
||||
required: false
|
||||
description: Maximum results to return.
|
||||
schema:
|
||||
type: integer
|
||||
default: 25
|
||||
example: 10
|
||||
StartQuery:
|
||||
name: start
|
||||
in: query
|
||||
required: false
|
||||
description: Pagination offset (must be >= 0).
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 0
|
||||
default: 0
|
||||
example: 20
|
||||
FilterQuery:
|
||||
name: filter
|
||||
in: query
|
||||
required: false
|
||||
description: Duplicate filtering flag (primarily used by Google parser behavior).
|
||||
schema:
|
||||
type: boolean
|
||||
default: true
|
||||
example: true
|
||||
AnswersQuery:
|
||||
name: answers
|
||||
in: query
|
||||
required: false
|
||||
description: Include answer box style results when supported.
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
example: false
|
||||
EnginesQuery:
|
||||
name: engines
|
||||
in: query
|
||||
required: false
|
||||
description: >
|
||||
Comma-separated engine list for mega endpoints. If omitted, all available engines are used.
|
||||
schema:
|
||||
type: string
|
||||
example: google,bing,duckduckgo
|
||||
UseProxyHeader:
|
||||
name: X-Use-Proxy
|
||||
in: header
|
||||
required: false
|
||||
description: >
|
||||
Request-scoped proxy override. Use `direct` to disable proxy for the request or
|
||||
pass a proxy tag name (for example `us`) to force a tagged proxy pool.
|
||||
schema:
|
||||
type: string
|
||||
examples:
|
||||
direct:
|
||||
value: direct
|
||||
tag:
|
||||
value: us
|
||||
headers:
|
||||
XCache:
|
||||
description: Cache status when cache is enabled (`HIT`, `MISS`, `BYPASS`).
|
||||
schema:
|
||||
type: string
|
||||
XFallbackEngine:
|
||||
description: Engine name used when dedicated endpoint fallback served the response.
|
||||
schema:
|
||||
type: string
|
||||
XProxyMode:
|
||||
description: Effective proxy mode for the request (`off` or `tag_pool`).
|
||||
schema:
|
||||
type: string
|
||||
enum: [off, tag_pool]
|
||||
XProxyTag:
|
||||
description: Effective proxy tag when `X-Proxy-Mode=tag_pool`.
|
||||
schema:
|
||||
type: string
|
||||
XProxyUsed:
|
||||
description: Effective proxy target used (`direct`, masked proxy, `pooled`, `multiple`, `mixed`).
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
BadRequestError:
|
||||
description: Invalid request parameters
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
examples:
|
||||
invalidEngines:
|
||||
value:
|
||||
error: bad_request
|
||||
code: 400
|
||||
message: No valid search engines specified
|
||||
ServiceUnavailableError:
|
||||
description: Search failed and no result could be produced
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
examples:
|
||||
primaryFailed:
|
||||
value:
|
||||
error: service_unavailable
|
||||
code: 503
|
||||
message: all search engines failed
|
||||
NotFoundError:
|
||||
description: Endpoint not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
examples:
|
||||
notFound:
|
||||
value:
|
||||
error: not_found
|
||||
code: 404
|
||||
message: Cannot GET /unknown
|
||||
InternalServerError:
|
||||
description: Internal error while parsing/handling request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
examples:
|
||||
parseError:
|
||||
value:
|
||||
error: server_error
|
||||
code: 500
|
||||
message: invalid syntax
|
||||
schemas:
|
||||
SearchResult:
|
||||
type: object
|
||||
required: [rank, url, title, description, ad]
|
||||
properties:
|
||||
rank:
|
||||
type: integer
|
||||
example: 1
|
||||
url:
|
||||
type: string
|
||||
example: https://go.dev/doc/
|
||||
title:
|
||||
type: string
|
||||
example: The Go Programming Language
|
||||
description:
|
||||
type: string
|
||||
example: Official Go language documentation.
|
||||
ad:
|
||||
type: boolean
|
||||
example: false
|
||||
MegaSearchResult:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/SearchResult"
|
||||
- type: object
|
||||
required: [engine]
|
||||
properties:
|
||||
engine:
|
||||
type: string
|
||||
example: google
|
||||
ErrorResponse:
|
||||
type: object
|
||||
required: [error, code]
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
example: service_unavailable
|
||||
code:
|
||||
type: integer
|
||||
example: 503
|
||||
message:
|
||||
type: string
|
||||
example: all search engines failed
|
||||
EngineHealth:
|
||||
type: object
|
||||
required: [name, initialized, status]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: google
|
||||
initialized:
|
||||
type: boolean
|
||||
example: true
|
||||
status:
|
||||
type: string
|
||||
enum: [ready, not_initialized, circuit_open]
|
||||
example: ready
|
||||
HealthStatus:
|
||||
type: object
|
||||
required: [status, uptime, engines, system]
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum: [healthy, degraded, unhealthy]
|
||||
uptime:
|
||||
type: string
|
||||
example: 1h12m3s
|
||||
engines:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/EngineHealth"
|
||||
system:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
properties:
|
||||
goroutines:
|
||||
type: integer
|
||||
memory_mb:
|
||||
type: integer
|
||||
go_version:
|
||||
type: string
|
||||
CacheStatsEnabled:
|
||||
type: object
|
||||
required: [status, entries, hits, misses, bypasses, evictions, ttl_seconds, max_size]
|
||||
properties:
|
||||
status:
|
||||
type: boolean
|
||||
enum: [true]
|
||||
entries:
|
||||
type: integer
|
||||
hits:
|
||||
type: integer
|
||||
misses:
|
||||
type: integer
|
||||
bypasses:
|
||||
type: integer
|
||||
evictions:
|
||||
type: integer
|
||||
ttl_seconds:
|
||||
type: integer
|
||||
max_size:
|
||||
type: integer
|
||||
CacheStatsDisabled:
|
||||
type: object
|
||||
required: [status]
|
||||
properties:
|
||||
status:
|
||||
type: boolean
|
||||
enum: [false]
|
||||
CacheStats:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/CacheStatsEnabled"
|
||||
- $ref: "#/components/schemas/CacheStatsDisabled"
|
||||
ProxyTagSummary:
|
||||
type: object
|
||||
required: [configured, healthy]
|
||||
properties:
|
||||
configured:
|
||||
type: integer
|
||||
healthy:
|
||||
type: integer
|
||||
ProxyStatsEntry:
|
||||
type: object
|
||||
required: [proxy, tags, healthy, failures, disabled]
|
||||
properties:
|
||||
proxy:
|
||||
type: string
|
||||
example: http://proxy-us:8080
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
healthy:
|
||||
type: boolean
|
||||
failures:
|
||||
type: integer
|
||||
disabled:
|
||||
type: boolean
|
||||
ProxyEngineStats:
|
||||
type: object
|
||||
required: [selected_proxy]
|
||||
properties:
|
||||
tag:
|
||||
type: string
|
||||
selected_proxy:
|
||||
type: string
|
||||
example: direct
|
||||
ProxyStats:
|
||||
type: object
|
||||
required: [configured_count, healthy_count, unhealthy_count, tags, entries]
|
||||
properties:
|
||||
configured_count:
|
||||
type: integer
|
||||
healthy_count:
|
||||
type: integer
|
||||
unhealthy_count:
|
||||
type: integer
|
||||
tags:
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: "#/components/schemas/ProxyTagSummary"
|
||||
entries:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ProxyStatsEntry"
|
||||
engines:
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: "#/components/schemas/ProxyEngineStats"
|
||||
CircuitBreakerStat:
|
||||
type: object
|
||||
required: [engine, state, failure_count, last_changed]
|
||||
properties:
|
||||
engine:
|
||||
type: string
|
||||
example: google
|
||||
state:
|
||||
type: string
|
||||
enum: [closed, open, half-open]
|
||||
failure_count:
|
||||
type: integer
|
||||
last_changed:
|
||||
type: string
|
||||
format: date-time
|
||||
retry_in:
|
||||
type: integer
|
||||
description: Seconds until next half-open attempt (present when state is open).
|
||||
CircuitBreakerStatsResponse:
|
||||
type: object
|
||||
required: [circuit_breakers]
|
||||
properties:
|
||||
circuit_breakers:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/CircuitBreakerStat"
|
||||
StatsResponse:
|
||||
type: object
|
||||
required: [cache, proxy, circuit_breakers]
|
||||
properties:
|
||||
cache:
|
||||
$ref: "#/components/schemas/CacheStats"
|
||||
proxy:
|
||||
$ref: "#/components/schemas/ProxyStats"
|
||||
circuit_breakers:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/CircuitBreakerStat"
|
||||
MegaEngineInfo:
|
||||
type: object
|
||||
required: [name, initialized]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: google
|
||||
initialized:
|
||||
type: boolean
|
||||
circuit_state:
|
||||
type: string
|
||||
enum: [closed, open, half-open]
|
||||
MegaEnginesResponse:
|
||||
type: object
|
||||
required: [engines, total]
|
||||
properties:
|
||||
engines:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/MegaEngineInfo"
|
||||
total:
|
||||
type: integer
|
||||
Reference in New Issue
Block a user