Skip to content

Commit 28fd502

Browse files
committed
feat(api): add route validation endpoint with WebSocket support
Adds a new `/route/validate` endpoint that accepts YAML-encoded route configurations for validation. Supports both synchronous HTTP requests and real-time streaming via WebSocket for interactive validation workflows. Changes: - Implement Validate handler with YAML binding in route/validate.go - Add WebSocket manager for streaming validation results - Register GET/POST routes in handler.go - Regenerate Swagger documentation
1 parent 0716e80 commit 28fd502

File tree

5 files changed

+271
-351
lines changed

5 files changed

+271
-351
lines changed

internal/api/handler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ func NewHandler(requireAuth bool) *gin.Engine {
8686
route.GET("/providers", routeApi.Providers)
8787
route.GET("/by_provider", routeApi.ByProvider)
8888
route.POST("/playground", routeApi.Playground)
89+
route.GET("/validate", routeApi.Validate) // websocket
90+
route.POST("/validate", routeApi.Validate)
8991
}
9092

9193
file := v1.Group("/file")

internal/api/v1/docs/swagger.json

Lines changed: 118 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,7 +1087,7 @@
10871087
"post": {
10881088
"description": "Validate file",
10891089
"consumes": [
1090-
"text/plain"
1090+
"application/yaml"
10911091
],
10921092
"produces": [
10931093
"application/json"
@@ -3026,6 +3026,122 @@
30263026
"operationId": "providers"
30273027
}
30283028
},
3029+
"/route/validate": {
3030+
"get": {
3031+
"description": "Validate route,",
3032+
"consumes": [
3033+
"application/yaml"
3034+
],
3035+
"produces": [
3036+
"application/json"
3037+
],
3038+
"tags": [
3039+
"route",
3040+
"websocket"
3041+
],
3042+
"summary": "Validate route",
3043+
"parameters": [
3044+
{
3045+
"description": "Route",
3046+
"name": "route",
3047+
"in": "body",
3048+
"required": true,
3049+
"schema": {
3050+
"$ref": "#/definitions/Route"
3051+
}
3052+
}
3053+
],
3054+
"responses": {
3055+
"200": {
3056+
"description": "Route validated",
3057+
"schema": {
3058+
"$ref": "#/definitions/SuccessResponse"
3059+
}
3060+
},
3061+
"400": {
3062+
"description": "Bad request",
3063+
"schema": {
3064+
"$ref": "#/definitions/ErrorResponse"
3065+
}
3066+
},
3067+
"403": {
3068+
"description": "Forbidden",
3069+
"schema": {
3070+
"$ref": "#/definitions/ErrorResponse"
3071+
}
3072+
},
3073+
"417": {
3074+
"description": "Validation failed",
3075+
"schema": {}
3076+
},
3077+
"500": {
3078+
"description": "Internal server error",
3079+
"schema": {
3080+
"$ref": "#/definitions/ErrorResponse"
3081+
}
3082+
}
3083+
},
3084+
"x-id": "validate",
3085+
"operationId": "validate"
3086+
},
3087+
"post": {
3088+
"description": "Validate route,",
3089+
"consumes": [
3090+
"application/yaml"
3091+
],
3092+
"produces": [
3093+
"application/json"
3094+
],
3095+
"tags": [
3096+
"route",
3097+
"websocket"
3098+
],
3099+
"summary": "Validate route",
3100+
"parameters": [
3101+
{
3102+
"description": "Route",
3103+
"name": "route",
3104+
"in": "body",
3105+
"required": true,
3106+
"schema": {
3107+
"$ref": "#/definitions/Route"
3108+
}
3109+
}
3110+
],
3111+
"responses": {
3112+
"200": {
3113+
"description": "Route validated",
3114+
"schema": {
3115+
"$ref": "#/definitions/SuccessResponse"
3116+
}
3117+
},
3118+
"400": {
3119+
"description": "Bad request",
3120+
"schema": {
3121+
"$ref": "#/definitions/ErrorResponse"
3122+
}
3123+
},
3124+
"403": {
3125+
"description": "Forbidden",
3126+
"schema": {
3127+
"$ref": "#/definitions/ErrorResponse"
3128+
}
3129+
},
3130+
"417": {
3131+
"description": "Validation failed",
3132+
"schema": {}
3133+
},
3134+
"500": {
3135+
"description": "Internal server error",
3136+
"schema": {
3137+
"$ref": "#/definitions/ErrorResponse"
3138+
}
3139+
}
3140+
},
3141+
"x-id": "validate",
3142+
"operationId": "validate"
3143+
}
3144+
},
30293145
"/route/{which}": {
30303146
"get": {
30313147
"description": "List route",
@@ -6745,229 +6861,6 @@
67456861
"x-nullable": false,
67466862
"x-omitempty": false
67476863
},
6748-
"route.Route": {
6749-
"type": "object",
6750-
"properties": {
6751-
"access_log": {
6752-
"allOf": [
6753-
{
6754-
"$ref": "#/definitions/RequestLoggerConfig"
6755-
}
6756-
],
6757-
"x-nullable": true
6758-
},
6759-
"agent": {
6760-
"type": "string",
6761-
"x-nullable": false,
6762-
"x-omitempty": false
6763-
},
6764-
"alias": {
6765-
"type": "string",
6766-
"x-nullable": false,
6767-
"x-omitempty": false
6768-
},
6769-
"bind": {
6770-
"description": "for TCP and UDP routes, bind address to listen on",
6771-
"type": "string",
6772-
"x-nullable": true
6773-
},
6774-
"container": {
6775-
"description": "Docker only",
6776-
"allOf": [
6777-
{
6778-
"$ref": "#/definitions/Container"
6779-
}
6780-
],
6781-
"x-nullable": true
6782-
},
6783-
"disable_compression": {
6784-
"type": "boolean",
6785-
"x-nullable": false,
6786-
"x-omitempty": false
6787-
},
6788-
"excluded": {
6789-
"type": "boolean",
6790-
"x-nullable": true
6791-
},
6792-
"excluded_reason": {
6793-
"type": "string",
6794-
"x-nullable": true
6795-
},
6796-
"health": {
6797-
"description": "for swagger",
6798-
"allOf": [
6799-
{
6800-
"$ref": "#/definitions/HealthJSON"
6801-
}
6802-
],
6803-
"x-nullable": false,
6804-
"x-omitempty": false
6805-
},
6806-
"healthcheck": {
6807-
"description": "null on load-balancer routes",
6808-
"allOf": [
6809-
{
6810-
"$ref": "#/definitions/HealthCheckConfig"
6811-
}
6812-
],
6813-
"x-nullable": true
6814-
},
6815-
"homepage": {
6816-
"$ref": "#/definitions/HomepageItemConfig",
6817-
"x-nullable": false,
6818-
"x-omitempty": false
6819-
},
6820-
"host": {
6821-
"type": "string",
6822-
"x-nullable": false,
6823-
"x-omitempty": false
6824-
},
6825-
"idlewatcher": {
6826-
"allOf": [
6827-
{
6828-
"$ref": "#/definitions/IdlewatcherConfig"
6829-
}
6830-
],
6831-
"x-nullable": true
6832-
},
6833-
"index": {
6834-
"description": "Index file to serve for single-page app mode",
6835-
"type": "string",
6836-
"x-nullable": false,
6837-
"x-omitempty": false
6838-
},
6839-
"load_balance": {
6840-
"allOf": [
6841-
{
6842-
"$ref": "#/definitions/LoadBalancerConfig"
6843-
}
6844-
],
6845-
"x-nullable": true
6846-
},
6847-
"lurl": {
6848-
"description": "private fields",
6849-
"type": "string",
6850-
"x-nullable": true
6851-
},
6852-
"middlewares": {
6853-
"type": "object",
6854-
"additionalProperties": {
6855-
"$ref": "#/definitions/types.LabelMap"
6856-
},
6857-
"x-nullable": true
6858-
},
6859-
"no_tls_verify": {
6860-
"type": "boolean",
6861-
"x-nullable": false,
6862-
"x-omitempty": false
6863-
},
6864-
"path_patterns": {
6865-
"type": "array",
6866-
"items": {
6867-
"type": "string"
6868-
},
6869-
"x-nullable": true
6870-
},
6871-
"port": {
6872-
"$ref": "#/definitions/Port",
6873-
"x-nullable": false,
6874-
"x-omitempty": false
6875-
},
6876-
"provider": {
6877-
"description": "for backward compatibility",
6878-
"type": "string",
6879-
"x-nullable": true
6880-
},
6881-
"proxmox": {
6882-
"allOf": [
6883-
{
6884-
"$ref": "#/definitions/ProxmoxNodeConfig"
6885-
}
6886-
],
6887-
"x-nullable": true
6888-
},
6889-
"purl": {
6890-
"type": "string",
6891-
"x-nullable": false,
6892-
"x-omitempty": false
6893-
},
6894-
"response_header_timeout": {
6895-
"type": "integer",
6896-
"x-nullable": false,
6897-
"x-omitempty": false
6898-
},
6899-
"root": {
6900-
"type": "string",
6901-
"x-nullable": false,
6902-
"x-omitempty": false
6903-
},
6904-
"rule_file": {
6905-
"type": "string",
6906-
"x-nullable": true
6907-
},
6908-
"rules": {
6909-
"type": "array",
6910-
"items": {
6911-
"$ref": "#/definitions/rules.Rule"
6912-
},
6913-
"x-nullable": true
6914-
},
6915-
"scheme": {
6916-
"type": "string",
6917-
"enum": [
6918-
"http",
6919-
"https",
6920-
"h2c",
6921-
"tcp",
6922-
"udp",
6923-
"fileserver"
6924-
],
6925-
"x-nullable": false,
6926-
"x-omitempty": false
6927-
},
6928-
"spa": {
6929-
"description": "Single-page app mode: serves index for non-existent paths",
6930-
"type": "boolean",
6931-
"x-nullable": false,
6932-
"x-omitempty": false
6933-
},
6934-
"ssl_certificate": {
6935-
"description": "Path to client certificate",
6936-
"type": "string",
6937-
"x-nullable": false,
6938-
"x-omitempty": false
6939-
},
6940-
"ssl_certificate_key": {
6941-
"description": "Path to client certificate key",
6942-
"type": "string",
6943-
"x-nullable": false,
6944-
"x-omitempty": false
6945-
},
6946-
"ssl_protocols": {
6947-
"description": "Allowed TLS protocols",
6948-
"type": "array",
6949-
"items": {
6950-
"type": "string"
6951-
},
6952-
"x-nullable": false,
6953-
"x-omitempty": false
6954-
},
6955-
"ssl_server_name": {
6956-
"description": "SSL/TLS proxy options (nginx-like)",
6957-
"type": "string",
6958-
"x-nullable": false,
6959-
"x-omitempty": false
6960-
},
6961-
"ssl_trusted_certificate": {
6962-
"description": "Path to trusted CA certificates",
6963-
"type": "string",
6964-
"x-nullable": false,
6965-
"x-omitempty": false
6966-
}
6967-
},
6968-
"x-nullable": false,
6969-
"x-omitempty": false
6970-
},
69716864
"routeApi.RawRule": {
69726865
"type": "object",
69736866
"properties": {
@@ -6995,7 +6888,7 @@
69956888
"additionalProperties": {
69966889
"type": "array",
69976890
"items": {
6998-
"$ref": "#/definitions/route.Route"
6891+
"$ref": "#/definitions/Route"
69996892
}
70006893
},
70016894
"x-nullable": false,

0 commit comments

Comments
 (0)