This is an automated email from the ASF dual-hosted git repository.

LiteSun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 597d14011 test: add gateway-schema contract test for the zod resource 
schemas (#3449)
597d14011 is described below

commit 597d14011088d15a464149ae7b9e93621eef9114
Author: Yuhan <[email protected]>
AuthorDate: Tue Jul 28 14:28:04 2026 +0800

    test: add gateway-schema contract test for the zod resource schemas (#3449)
---
 package.json                                       |   1 +
 scripts/refresh-gateway-schema.mjs                 | 117 +++
 .../schema/apisix/__fixtures__/gateway/_meta.json  |  17 +
 .../apisix/__fixtures__/gateway/consumer.json      |  69 ++
 .../__fixtures__/gateway/consumer_group.json       |  55 ++
 .../apisix/__fixtures__/gateway/credential.json    |  62 ++
 .../apisix/__fixtures__/gateway/global_rule.json   |  33 +
 .../apisix/__fixtures__/gateway/plugin_config.json |  53 ++
 .../schema/apisix/__fixtures__/gateway/proto.json  |  56 ++
 .../schema/apisix/__fixtures__/gateway/route.json  | 939 +++++++++++++++++++++
 .../apisix/__fixtures__/gateway/service.json       | 605 +++++++++++++
 .../schema/apisix/__fixtures__/gateway/ssl.json    | 172 ++++
 .../apisix/__fixtures__/gateway/stream_route.json  | 717 ++++++++++++++++
 .../apisix/__fixtures__/gateway/upstream.json      | 522 ++++++++++++
 src/types/schema/apisix/gateway-contract.test.ts   | 171 ++++
 15 files changed, 3589 insertions(+)

diff --git a/package.json b/package.json
index 29f22f931..217d055fd 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
     "lint:fix": "pnpm lint --fix",
     "preview": "vite preview",
     "test": "vitest run src",
+    "refresh:gateway-schema": "node scripts/refresh-gateway-schema.mjs",
     "prepare": "husky",
     "e2e": "playwright test"
   },
diff --git a/scripts/refresh-gateway-schema.mjs 
b/scripts/refresh-gateway-schema.mjs
new file mode 100644
index 000000000..4802bbba2
--- /dev/null
+++ b/scripts/refresh-gateway-schema.mjs
@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Refreshes the committed snapshot of APISIX's own per-resource JSON schemas,
+ * used by gateway-contract.test.ts to assert the dashboard's zod layer is not
+ * stricter than the gateway.
+ *
+ * Run this against a running APISIX Admin API and commit the result whenever
+ * the pinned APISIX version changes:
+ *
+ *   pnpm refresh:gateway-schema
+ *
+ * Defaults target the local prod-like stack; override with env vars:
+ *   ADMIN_URL=http://127.0.0.1:9180 ADMIN_KEY=<key> pnpm 
refresh:gateway-schema
+ */
+import { execSync } from 'node:child_process';
+import { mkdirSync, writeFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const ADMIN_URL = process.env.ADMIN_URL ?? 'http://127.0.0.1:9180';
+const ADMIN_KEY = process.env.ADMIN_KEY ?? 'edd1c9f034335f136f87ad84b625c8f1';
+
+// Resources whose write schema the gateway exposes at
+// GET /apisix/admin/schema/<resource>. `secret` is omitted: it is schema'd
+// per-manager and the bare endpoint returns 400.
+const RESOURCES = [
+  'route',
+  'service',
+  'upstream',
+  'consumer',
+  'ssl',
+  'global_rule',
+  'plugin_config',
+  'consumer_group',
+  'stream_route',
+  'proto',
+  'credential',
+];
+
+const OUT_DIR = join(
+  dirname(fileURLToPath(import.meta.url)),
+  '..',
+  'src',
+  'types',
+  'schema',
+  'apisix',
+  '__fixtures__',
+  'gateway'
+);
+
+const fetchSchema = async (resource) => {
+  const res = await fetch(`${ADMIN_URL}/apisix/admin/schema/${resource}`, {
+    headers: { 'X-API-KEY': ADMIN_KEY },
+  });
+  if (!res.ok) {
+    throw new Error(`GET schema/${resource} -> ${res.status}`);
+  }
+  return res.json();
+};
+
+const apisixVersion = () => {
+  try {
+    const out = execSync(
+      'docker exec apisix-prod-apisix-1 apisix version 2>/dev/null',
+      { encoding: 'utf8' }
+    );
+    return out.trim().split(/\s+/).pop() ?? 'unknown';
+  } catch {
+    return process.env.APISIX_VERSION ?? 'unknown';
+  }
+};
+
+const main = async () => {
+  mkdirSync(OUT_DIR, { recursive: true });
+  for (const resource of RESOURCES) {
+    const schema = await fetchSchema(resource);
+    writeFileSync(
+      join(OUT_DIR, `${resource}.json`),
+      JSON.stringify(schema, null, 2) + '\n'
+    );
+    console.log(`wrote ${resource}.json`);
+  }
+  writeFileSync(
+    join(OUT_DIR, '_meta.json'),
+    JSON.stringify(
+      {
+        apisixVersion: apisixVersion(),
+        resources: RESOURCES,
+        note: 'Generated by scripts/refresh-gateway-schema.mjs. Do not edit by 
hand; re-run on APISIX version bumps.',
+      },
+      null,
+      2
+    ) + '\n'
+  );
+  console.log(`wrote _meta.json (${RESOURCES.length} resources)`);
+};
+
+main().catch((err) => {
+  console.error(err);
+  process.exit(1);
+});
diff --git a/src/types/schema/apisix/__fixtures__/gateway/_meta.json 
b/src/types/schema/apisix/__fixtures__/gateway/_meta.json
new file mode 100644
index 000000000..1b9a3799a
--- /dev/null
+++ b/src/types/schema/apisix/__fixtures__/gateway/_meta.json
@@ -0,0 +1,17 @@
+{
+  "apisixVersion": "3.17.0",
+  "resources": [
+    "route",
+    "service",
+    "upstream",
+    "consumer",
+    "ssl",
+    "global_rule",
+    "plugin_config",
+    "consumer_group",
+    "stream_route",
+    "proto",
+    "credential"
+  ],
+  "note": "Generated by scripts/refresh-gateway-schema.mjs. Do not edit by 
hand; re-run on APISIX version bumps."
+}
diff --git a/src/types/schema/apisix/__fixtures__/gateway/consumer.json 
b/src/types/schema/apisix/__fixtures__/gateway/consumer.json
new file mode 100644
index 000000000..500a93b3c
--- /dev/null
+++ b/src/types/schema/apisix/__fixtures__/gateway/consumer.json
@@ -0,0 +1,69 @@
+{
+  "type": "object",
+  "additionalProperties": false,
+  "required": [
+    "username"
+  ],
+  "properties": {
+    "labels": {
+      "description": "key/value pairs to specify attributes",
+      "patternProperties": {
+        ".*": {
+          "maxLength": 256,
+          "description": "value of label",
+          "pattern": "^\\S+$",
+          "type": "string",
+          "minLength": 1
+        }
+      },
+      "type": "object"
+    },
+    "id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "group_id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "desc": {
+      "type": "string",
+      "maxLength": 256
+    },
+    "plugins": {
+      "type": "object"
+    },
+    "create_time": {
+      "type": "integer"
+    },
+    "username": {
+      "type": "string",
+      "pattern": "^[a-zA-Z0-9_\\-]+$",
+      "maxLength": 256,
+      "minLength": 1
+    },
+    "update_time": {
+      "type": "integer"
+    }
+  }
+}
diff --git a/src/types/schema/apisix/__fixtures__/gateway/consumer_group.json 
b/src/types/schema/apisix/__fixtures__/gateway/consumer_group.json
new file mode 100644
index 000000000..31a1085e4
--- /dev/null
+++ b/src/types/schema/apisix/__fixtures__/gateway/consumer_group.json
@@ -0,0 +1,55 @@
+{
+  "type": "object",
+  "additionalProperties": false,
+  "required": [
+    "id",
+    "plugins"
+  ],
+  "properties": {
+    "name": {
+      "type": "string",
+      "maxLength": 256,
+      "minLength": 1
+    },
+    "labels": {
+      "description": "key/value pairs to specify attributes",
+      "patternProperties": {
+        ".*": {
+          "maxLength": 256,
+          "description": "value of label",
+          "pattern": "^\\S+$",
+          "type": "string",
+          "minLength": 1
+        }
+      },
+      "type": "object"
+    },
+    "desc": {
+      "type": "string",
+      "maxLength": 256
+    },
+    "plugins": {
+      "type": "object"
+    },
+    "create_time": {
+      "type": "integer"
+    },
+    "id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "update_time": {
+      "type": "integer"
+    }
+  }
+}
diff --git a/src/types/schema/apisix/__fixtures__/gateway/credential.json 
b/src/types/schema/apisix/__fixtures__/gateway/credential.json
new file mode 100644
index 000000000..34b9a6ac8
--- /dev/null
+++ b/src/types/schema/apisix/__fixtures__/gateway/credential.json
@@ -0,0 +1,62 @@
+{
+  "type": "object",
+  "additionalProperties": false,
+  "properties": {
+    "name": {
+      "type": "string",
+      "maxLength": 256,
+      "minLength": 1
+    },
+    "labels": {
+      "description": "key/value pairs to specify attributes",
+      "patternProperties": {
+        ".*": {
+          "maxLength": 256,
+          "description": "value of label",
+          "pattern": "^\\S+$",
+          "type": "string",
+          "minLength": 1
+        }
+      },
+      "type": "object"
+    },
+    "desc": {
+      "type": "string",
+      "maxLength": 256
+    },
+    "plugins": {
+      "type": "object",
+      "maxProperties": 1
+    },
+    "create_time": {
+      "type": "integer"
+    },
+    "id": {
+      "oneOf": [
+        {
+          "anyOf": [
+            {
+              "type": "string",
+              "pattern": "^[a-zA-Z0-9-_.]+$",
+              "maxLength": 64,
+              "minLength": 1
+            },
+            {
+              "type": "integer",
+              "minimum": 1
+            }
+          ]
+        },
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_]+/credentials/[a-zA-Z0-9-_.]+$",
+          "maxLength": 128,
+          "minLength": 15
+        }
+      ]
+    },
+    "update_time": {
+      "type": "integer"
+    }
+  }
+}
diff --git a/src/types/schema/apisix/__fixtures__/gateway/global_rule.json 
b/src/types/schema/apisix/__fixtures__/gateway/global_rule.json
new file mode 100644
index 000000000..4fb07e914
--- /dev/null
+++ b/src/types/schema/apisix/__fixtures__/gateway/global_rule.json
@@ -0,0 +1,33 @@
+{
+  "type": "object",
+  "additionalProperties": false,
+  "required": [
+    "id",
+    "plugins"
+  ],
+  "properties": {
+    "id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "create_time": {
+      "type": "integer"
+    },
+    "plugins": {
+      "type": "object"
+    },
+    "update_time": {
+      "type": "integer"
+    }
+  }
+}
diff --git a/src/types/schema/apisix/__fixtures__/gateway/plugin_config.json 
b/src/types/schema/apisix/__fixtures__/gateway/plugin_config.json
new file mode 100644
index 000000000..f9b748156
--- /dev/null
+++ b/src/types/schema/apisix/__fixtures__/gateway/plugin_config.json
@@ -0,0 +1,53 @@
+{
+  "type": "object",
+  "additionalProperties": false,
+  "required": [
+    "id",
+    "plugins"
+  ],
+  "properties": {
+    "name": {
+      "type": "string"
+    },
+    "labels": {
+      "description": "key/value pairs to specify attributes",
+      "patternProperties": {
+        ".*": {
+          "maxLength": 256,
+          "description": "value of label",
+          "pattern": "^\\S+$",
+          "type": "string",
+          "minLength": 1
+        }
+      },
+      "type": "object"
+    },
+    "desc": {
+      "type": "string",
+      "maxLength": 256
+    },
+    "plugins": {
+      "type": "object"
+    },
+    "create_time": {
+      "type": "integer"
+    },
+    "id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "update_time": {
+      "type": "integer"
+    }
+  }
+}
diff --git a/src/types/schema/apisix/__fixtures__/gateway/proto.json 
b/src/types/schema/apisix/__fixtures__/gateway/proto.json
new file mode 100644
index 000000000..12b0df2e5
--- /dev/null
+++ b/src/types/schema/apisix/__fixtures__/gateway/proto.json
@@ -0,0 +1,56 @@
+{
+  "type": "object",
+  "additionalProperties": false,
+  "required": [
+    "content"
+  ],
+  "properties": {
+    "name": {
+      "type": "string",
+      "maxLength": 256,
+      "minLength": 1
+    },
+    "labels": {
+      "description": "key/value pairs to specify attributes",
+      "patternProperties": {
+        ".*": {
+          "maxLength": 256,
+          "description": "value of label",
+          "pattern": "^\\S+$",
+          "type": "string",
+          "minLength": 1
+        }
+      },
+      "type": "object"
+    },
+    "content": {
+      "type": "string",
+      "maxLength": 1048576,
+      "minLength": 1
+    },
+    "id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "create_time": {
+      "type": "integer"
+    },
+    "desc": {
+      "type": "string",
+      "maxLength": 256
+    },
+    "update_time": {
+      "type": "integer"
+    }
+  }
+}
diff --git a/src/types/schema/apisix/__fixtures__/gateway/route.json 
b/src/types/schema/apisix/__fixtures__/gateway/route.json
new file mode 100644
index 000000000..3acea8690
--- /dev/null
+++ b/src/types/schema/apisix/__fixtures__/gateway/route.json
@@ -0,0 +1,939 @@
+{
+  "type": "object",
+  "anyOf": [
+    {
+      "required": [
+        "plugins",
+        "uri"
+      ]
+    },
+    {
+      "required": [
+        "upstream",
+        "uri"
+      ]
+    },
+    {
+      "required": [
+        "upstream_id",
+        "uri"
+      ]
+    },
+    {
+      "required": [
+        "service_id",
+        "uri"
+      ]
+    },
+    {
+      "required": [
+        "plugins",
+        "uris"
+      ]
+    },
+    {
+      "required": [
+        "upstream",
+        "uris"
+      ]
+    },
+    {
+      "required": [
+        "upstream_id",
+        "uris"
+      ]
+    },
+    {
+      "required": [
+        "service_id",
+        "uris"
+      ]
+    },
+    {
+      "required": [
+        "script",
+        "uri"
+      ]
+    },
+    {
+      "required": [
+        "script",
+        "uris"
+      ]
+    }
+  ],
+  "properties": {
+    "remote_addr": {
+      "description": "client IP",
+      "anyOf": [
+        {
+          "type": "string",
+          "title": "IPv4",
+          "format": "ipv4"
+        },
+        {
+          "type": "string",
+          "pattern": 
"^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$",
+          "title": "IPv4/CIDR"
+        },
+        {
+          "type": "string",
+          "title": "IPv6",
+          "format": "ipv6"
+        },
+        {
+          "type": "string",
+          "pattern": 
"^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$",
+          "title": "IPv6/CIDR"
+        }
+      ],
+      "type": "string"
+    },
+    "remote_addrs": {
+      "type": "array",
+      "items": {
+        "description": "client IP",
+        "anyOf": [
+          {
+            "type": "string",
+            "title": "IPv4",
+            "format": "ipv4"
+          },
+          {
+            "type": "string",
+            "pattern": 
"^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$",
+            "title": "IPv4/CIDR"
+          },
+          {
+            "type": "string",
+            "title": "IPv6",
+            "format": "ipv6"
+          },
+          {
+            "type": "string",
+            "pattern": 
"^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$",
+            "title": "IPv6/CIDR"
+          }
+        ],
+        "type": "string"
+      },
+      "minItems": 1,
+      "uniqueItems": true
+    },
+    "filter_func": {
+      "type": "string",
+      "pattern": "^function",
+      "minLength": 10
+    },
+    "id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "enable_websocket": {
+      "description": "enable websocket for request",
+      "type": "boolean"
+    },
+    "upstream": {
+      "type": "object",
+      "oneOf": [
+        {
+          "required": [
+            "nodes"
+          ]
+        },
+        {
+          "required": [
+            "service_name",
+            "discovery_type"
+          ]
+        }
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "hash_on": {
+          "type": "string",
+          "default": "vars",
+          "enum": [
+            "vars",
+            "header",
+            "cookie",
+            "consumer",
+            "vars_combinations"
+          ]
+        },
+        "id": {
+          "anyOf": [
+            {
+              "type": "string",
+              "pattern": "^[a-zA-Z0-9-_.]+$",
+              "maxLength": 64,
+              "minLength": 1
+            },
+            {
+              "type": "integer",
+              "minimum": 1
+            }
+          ]
+        },
+        "upstream_host": {
+          "type": "string",
+          "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+        },
+        "nodes": {
+          "anyOf": [
+            {
+              "type": "object",
+              "patternProperties": {
+                ".*": {
+                  "description": "weight of node",
+                  "type": "integer",
+                  "minimum": 0
+                }
+              }
+            },
+            {
+              "type": "array",
+              "items": {
+                "type": "object",
+                "required": [
+                  "host",
+                  "weight"
+                ],
+                "properties": {
+                  "priority": {
+                    "description": "priority of node",
+                    "type": "integer",
+                    "default": 0
+                  },
+                  "weight": {
+                    "description": "weight of node",
+                    "type": "integer",
+                    "minimum": 0
+                  },
+                  "port": {
+                    "description": "port of node",
+                    "type": "integer",
+                    "minimum": 1,
+                    "maximum": 65535
+                  },
+                  "host": {
+                    "type": "string",
+                    "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+                  },
+                  "metadata": {
+                    "description": "metadata of node",
+                    "type": "object"
+                  }
+                }
+              }
+            }
+          ]
+        },
+        "type": {
+          "description": "algorithms of load balancing",
+          "type": "string",
+          "default": "roundrobin"
+        },
+        "discovery_type": {
+          "description": "discovery type",
+          "type": "string"
+        },
+        "discovery_args": {
+          "type": "object",
+          "properties": {
+            "namespace_id": {
+              "description": "namespace id",
+              "type": "string"
+            },
+            "group_name": {
+              "description": "group name",
+              "type": "string"
+            }
+          }
+        },
+        "timeout": {
+          "type": "object",
+          "required": [
+            "connect",
+            "send",
+            "read"
+          ],
+          "properties": {
+            "send": {
+              "type": "number",
+              "exclusiveMinimum": 0
+            },
+            "connect": {
+              "type": "number",
+              "exclusiveMinimum": 0
+            },
+            "read": {
+              "type": "number",
+              "exclusiveMinimum": 0
+            }
+          }
+        },
+        "key": {
+          "description": "the key of chash for dynamic load balancing",
+          "type": "string"
+        },
+        "desc": {
+          "type": "string",
+          "maxLength": 256
+        },
+        "labels": {
+          "description": "key/value pairs to specify attributes",
+          "patternProperties": {
+            ".*": {
+              "maxLength": 256,
+              "description": "value of label",
+              "pattern": "^\\S+$",
+              "type": "string",
+              "minLength": 1
+            }
+          },
+          "type": "object"
+        },
+        "create_time": {
+          "type": "integer"
+        },
+        "checks": {
+          "type": "object",
+          "anyOf": [
+            {
+              "required": [
+                "active"
+              ]
+            },
+            {
+              "required": [
+                "active",
+                "passive"
+              ]
+            }
+          ],
+          "properties": {
+            "active": {
+              "type": "object",
+              "properties": {
+                "unhealthy": {
+                  "type": "object",
+                  "properties": {
+                    "interval": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "default": 1
+                    },
+                    "http_failures": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "maximum": 254,
+                      "default": 5
+                    },
+                    "tcp_failures": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "maximum": 254,
+                      "default": 2
+                    },
+                    "timeouts": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "maximum": 254,
+                      "default": 3
+                    },
+                    "http_statuses": {
+                      "type": "array",
+                      "items": {
+                        "type": "integer",
+                        "minimum": 200,
+                        "maximum": 599
+                      },
+                      "minItems": 1,
+                      "default": [
+                        429,
+                        404,
+                        500,
+                        501,
+                        502,
+                        503,
+                        504,
+                        505
+                      ],
+                      "uniqueItems": true
+                    }
+                  }
+                },
+                "port": {
+                  "type": "integer",
+                  "minimum": 1,
+                  "maximum": 65535
+                },
+                "req_headers": {
+                  "type": "array",
+                  "items": {
+                    "type": "string",
+                    "uniqueItems": true
+                  },
+                  "minItems": 1
+                },
+                "type": {
+                  "type": "string",
+                  "default": "http",
+                  "enum": [
+                    "http",
+                    "https",
+                    "tcp"
+                  ]
+                },
+                "timeout": {
+                  "type": "number",
+                  "default": 1
+                },
+                "healthy": {
+                  "type": "object",
+                  "properties": {
+                    "interval": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "default": 1
+                    },
+                    "http_statuses": {
+                      "type": "array",
+                      "items": {
+                        "type": "integer",
+                        "minimum": 200,
+                        "maximum": 599
+                      },
+                      "minItems": 1,
+                      "default": [
+                        200,
+                        302
+                      ],
+                      "uniqueItems": true
+                    },
+                    "successes": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "maximum": 254,
+                      "default": 2
+                    }
+                  }
+                },
+                "host": {
+                  "type": "string",
+                  "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+                },
+                "concurrency": {
+                  "type": "integer",
+                  "default": 10
+                },
+                "http_path": {
+                  "type": "string",
+                  "default": "/"
+                },
+                "https_verify_certificate": {
+                  "type": "boolean",
+                  "default": true
+                }
+              }
+            },
+            "passive": {
+              "type": "object",
+              "properties": {
+                "type": {
+                  "type": "string",
+                  "default": "http",
+                  "enum": [
+                    "http",
+                    "https",
+                    "tcp"
+                  ]
+                },
+                "unhealthy": {
+                  "type": "object",
+                  "properties": {
+                    "timeouts": {
+                      "type": "integer",
+                      "minimum": 0,
+                      "maximum": 254,
+                      "default": 7
+                    },
+                    "http_statuses": {
+                      "type": "array",
+                      "items": {
+                        "type": "integer",
+                        "minimum": 200,
+                        "maximum": 599
+                      },
+                      "minItems": 1,
+                      "default": [
+                        429,
+                        500,
+                        503
+                      ],
+                      "uniqueItems": true
+                    },
+                    "http_failures": {
+                      "type": "integer",
+                      "minimum": 0,
+                      "maximum": 254,
+                      "default": 5
+                    },
+                    "tcp_failures": {
+                      "type": "integer",
+                      "minimum": 0,
+                      "maximum": 254,
+                      "default": 2
+                    }
+                  }
+                },
+                "healthy": {
+                  "type": "object",
+                  "properties": {
+                    "http_statuses": {
+                      "type": "array",
+                      "items": {
+                        "type": "integer",
+                        "minimum": 200,
+                        "maximum": 599
+                      },
+                      "minItems": 1,
+                      "default": [
+                        200,
+                        201,
+                        202,
+                        203,
+                        204,
+                        205,
+                        206,
+                        207,
+                        208,
+                        226,
+                        300,
+                        301,
+                        302,
+                        303,
+                        304,
+                        305,
+                        306,
+                        307,
+                        308
+                      ],
+                      "uniqueItems": true
+                    },
+                    "successes": {
+                      "type": "integer",
+                      "minimum": 0,
+                      "maximum": 254,
+                      "default": 5
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        "retry_timeout": {
+          "type": "number",
+          "minimum": 0
+        },
+        "name": {
+          "type": "string",
+          "maxLength": 256,
+          "minLength": 1
+        },
+        "tls": {
+          "type": "object",
+          "properties": {
+            "client_key": {
+              "type": "string",
+              "maxLength": 65536,
+              "minLength": 64
+            },
+            "client_cert": {
+              "type": "string",
+              "maxLength": 65536,
+              "minLength": 128
+            },
+            "client_cert_id": {
+              "anyOf": [
+                {
+                  "type": "string",
+                  "pattern": "^[a-zA-Z0-9-_.]+$",
+                  "maxLength": 64,
+                  "minLength": 1
+                },
+                {
+                  "type": "integer",
+                  "minimum": 1
+                }
+              ]
+            },
+            "verify": {
+              "type": "boolean",
+              "description": "Turn on server certificate verification, 
currently only kafka upstream is supported",
+              "default": false
+            }
+          },
+          "dependencies": {
+            "client_key": {
+              "required": [
+                "client_cert"
+              ]
+            },
+            "client_cert": {
+              "required": [
+                "client_key"
+              ]
+            },
+            "client_cert_id": {
+              "not": {
+                "required": [
+                  "client_cert",
+                  "client_key"
+                ]
+              }
+            }
+          }
+        },
+        "retries": {
+          "type": "integer",
+          "minimum": 0
+        },
+        "keepalive_pool": {
+          "type": "object",
+          "properties": {
+            "idle_timeout": {
+              "type": "number",
+              "default": 60,
+              "minimum": 0
+            },
+            "requests": {
+              "type": "integer",
+              "default": 1000,
+              "minimum": 1
+            },
+            "size": {
+              "type": "integer",
+              "default": 320,
+              "minimum": 1
+            }
+          }
+        },
+        "service_name": {
+          "type": "string",
+          "maxLength": 256,
+          "minLength": 1
+        },
+        "scheme": {
+          "default": "http",
+          "description": "The scheme of the upstream. For L7 proxy, it can be 
one of grpc/grpcs/http/https. For L4 proxy, it can be one of tcp/tls/udp. For 
specific protocols, it can be kafka.",
+          "enum": [
+            "grpc",
+            "grpcs",
+            "http",
+            "https",
+            "tcp",
+            "tls",
+            "udp",
+            "kafka"
+          ]
+        },
+        "pass_host": {
+          "description": "mod of host passing",
+          "default": "pass",
+          "type": "string",
+          "enum": [
+            "pass",
+            "node",
+            "rewrite"
+          ]
+        },
+        "update_time": {
+          "type": "integer"
+        }
+      }
+    },
+    "upstream_id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "script": {
+      "type": "string",
+      "maxLength": 102400,
+      "minLength": 10
+    },
+    "timeout": {
+      "type": "object",
+      "required": [
+        "connect",
+        "send",
+        "read"
+      ],
+      "properties": {
+        "send": {
+          "type": "number",
+          "exclusiveMinimum": 0
+        },
+        "connect": {
+          "type": "number",
+          "exclusiveMinimum": 0
+        },
+        "read": {
+          "type": "number",
+          "exclusiveMinimum": 0
+        }
+      }
+    },
+    "hosts": {
+      "type": "array",
+      "items": {
+        "type": "string",
+        "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+      },
+      "minItems": 1,
+      "uniqueItems": true
+    },
+    "script_id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "host": {
+      "type": "string",
+      "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+    },
+    "uris": {
+      "type": "array",
+      "items": {
+        "description": "HTTP uri",
+        "type": "string"
+      },
+      "minItems": 1,
+      "uniqueItems": true
+    },
+    "desc": {
+      "type": "string",
+      "maxLength": 256
+    },
+    "labels": {
+      "description": "key/value pairs to specify attributes",
+      "patternProperties": {
+        ".*": {
+          "maxLength": 256,
+          "description": "value of label",
+          "pattern": "^\\S+$",
+          "type": "string",
+          "minLength": 1
+        }
+      },
+      "type": "object"
+    },
+    "create_time": {
+      "type": "integer"
+    },
+    "name": {
+      "type": "string",
+      "maxLength": 256,
+      "minLength": 1
+    },
+    "uri": {
+      "type": "string",
+      "maxLength": 4096,
+      "minLength": 1
+    },
+    "plugin_config_id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "vars": {
+      "type": "array"
+    },
+    "status": {
+      "description": "route status, 1 to enable, 0 to disable",
+      "default": 1,
+      "type": "integer",
+      "enum": [
+        1,
+        0
+      ]
+    },
+    "priority": {
+      "type": "integer",
+      "default": 0
+    },
+    "plugins": {
+      "type": "object"
+    },
+    "service_id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "methods": {
+      "type": "array",
+      "items": {
+        "description": "HTTP method",
+        "type": "string",
+        "enum": [
+          "GET",
+          "POST",
+          "PUT",
+          "DELETE",
+          "PATCH",
+          "HEAD",
+          "OPTIONS",
+          "CONNECT",
+          "TRACE",
+          "PURGE"
+        ]
+      },
+      "uniqueItems": true
+    },
+    "update_time": {
+      "type": "integer"
+    }
+  },
+  "not": {
+    "anyOf": [
+      {
+        "required": [
+          "script",
+          "plugins"
+        ]
+      },
+      {
+        "required": [
+          "script",
+          "plugin_config_id"
+        ]
+      }
+    ]
+  },
+  "allOf": [
+    {
+      "oneOf": [
+        {
+          "required": [
+            "uri"
+          ]
+        },
+        {
+          "required": [
+            "uris"
+          ]
+        }
+      ]
+    },
+    {
+      "oneOf": [
+        {
+          "not": {
+            "anyOf": [
+              {
+                "required": [
+                  "host"
+                ]
+              },
+              {
+                "required": [
+                  "hosts"
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "required": [
+            "host"
+          ]
+        },
+        {
+          "required": [
+            "hosts"
+          ]
+        }
+      ]
+    },
+    {
+      "oneOf": [
+        {
+          "not": {
+            "anyOf": [
+              {
+                "required": [
+                  "remote_addr"
+                ]
+              },
+              {
+                "required": [
+                  "remote_addrs"
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "required": [
+            "remote_addr"
+          ]
+        },
+        {
+          "required": [
+            "remote_addrs"
+          ]
+        }
+      ]
+    }
+  ],
+  "additionalProperties": false
+}
diff --git a/src/types/schema/apisix/__fixtures__/gateway/service.json 
b/src/types/schema/apisix/__fixtures__/gateway/service.json
new file mode 100644
index 000000000..a1e6fc620
--- /dev/null
+++ b/src/types/schema/apisix/__fixtures__/gateway/service.json
@@ -0,0 +1,605 @@
+{
+  "type": "object",
+  "additionalProperties": false,
+  "properties": {
+    "desc": {
+      "type": "string",
+      "maxLength": 256
+    },
+    "id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "create_time": {
+      "type": "integer"
+    },
+    "upstream": {
+      "type": "object",
+      "oneOf": [
+        {
+          "required": [
+            "nodes"
+          ]
+        },
+        {
+          "required": [
+            "service_name",
+            "discovery_type"
+          ]
+        }
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "hash_on": {
+          "type": "string",
+          "default": "vars",
+          "enum": [
+            "vars",
+            "header",
+            "cookie",
+            "consumer",
+            "vars_combinations"
+          ]
+        },
+        "id": {
+          "anyOf": [
+            {
+              "type": "string",
+              "pattern": "^[a-zA-Z0-9-_.]+$",
+              "maxLength": 64,
+              "minLength": 1
+            },
+            {
+              "type": "integer",
+              "minimum": 1
+            }
+          ]
+        },
+        "upstream_host": {
+          "type": "string",
+          "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+        },
+        "nodes": {
+          "anyOf": [
+            {
+              "type": "object",
+              "patternProperties": {
+                ".*": {
+                  "description": "weight of node",
+                  "type": "integer",
+                  "minimum": 0
+                }
+              }
+            },
+            {
+              "type": "array",
+              "items": {
+                "type": "object",
+                "required": [
+                  "host",
+                  "weight"
+                ],
+                "properties": {
+                  "priority": {
+                    "description": "priority of node",
+                    "type": "integer",
+                    "default": 0
+                  },
+                  "weight": {
+                    "description": "weight of node",
+                    "type": "integer",
+                    "minimum": 0
+                  },
+                  "port": {
+                    "description": "port of node",
+                    "type": "integer",
+                    "minimum": 1,
+                    "maximum": 65535
+                  },
+                  "host": {
+                    "type": "string",
+                    "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+                  },
+                  "metadata": {
+                    "description": "metadata of node",
+                    "type": "object"
+                  }
+                }
+              }
+            }
+          ]
+        },
+        "type": {
+          "description": "algorithms of load balancing",
+          "type": "string",
+          "default": "roundrobin"
+        },
+        "discovery_type": {
+          "description": "discovery type",
+          "type": "string"
+        },
+        "discovery_args": {
+          "type": "object",
+          "properties": {
+            "namespace_id": {
+              "description": "namespace id",
+              "type": "string"
+            },
+            "group_name": {
+              "description": "group name",
+              "type": "string"
+            }
+          }
+        },
+        "timeout": {
+          "type": "object",
+          "required": [
+            "connect",
+            "send",
+            "read"
+          ],
+          "properties": {
+            "send": {
+              "type": "number",
+              "exclusiveMinimum": 0
+            },
+            "connect": {
+              "type": "number",
+              "exclusiveMinimum": 0
+            },
+            "read": {
+              "type": "number",
+              "exclusiveMinimum": 0
+            }
+          }
+        },
+        "key": {
+          "description": "the key of chash for dynamic load balancing",
+          "type": "string"
+        },
+        "desc": {
+          "type": "string",
+          "maxLength": 256
+        },
+        "labels": {
+          "description": "key/value pairs to specify attributes",
+          "patternProperties": {
+            ".*": {
+              "maxLength": 256,
+              "description": "value of label",
+              "pattern": "^\\S+$",
+              "type": "string",
+              "minLength": 1
+            }
+          },
+          "type": "object"
+        },
+        "create_time": {
+          "type": "integer"
+        },
+        "checks": {
+          "type": "object",
+          "anyOf": [
+            {
+              "required": [
+                "active"
+              ]
+            },
+            {
+              "required": [
+                "active",
+                "passive"
+              ]
+            }
+          ],
+          "properties": {
+            "active": {
+              "type": "object",
+              "properties": {
+                "unhealthy": {
+                  "type": "object",
+                  "properties": {
+                    "interval": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "default": 1
+                    },
+                    "http_failures": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "maximum": 254,
+                      "default": 5
+                    },
+                    "tcp_failures": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "maximum": 254,
+                      "default": 2
+                    },
+                    "timeouts": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "maximum": 254,
+                      "default": 3
+                    },
+                    "http_statuses": {
+                      "type": "array",
+                      "items": {
+                        "type": "integer",
+                        "minimum": 200,
+                        "maximum": 599
+                      },
+                      "minItems": 1,
+                      "default": [
+                        429,
+                        404,
+                        500,
+                        501,
+                        502,
+                        503,
+                        504,
+                        505
+                      ],
+                      "uniqueItems": true
+                    }
+                  }
+                },
+                "port": {
+                  "type": "integer",
+                  "minimum": 1,
+                  "maximum": 65535
+                },
+                "req_headers": {
+                  "type": "array",
+                  "items": {
+                    "type": "string",
+                    "uniqueItems": true
+                  },
+                  "minItems": 1
+                },
+                "type": {
+                  "type": "string",
+                  "default": "http",
+                  "enum": [
+                    "http",
+                    "https",
+                    "tcp"
+                  ]
+                },
+                "timeout": {
+                  "type": "number",
+                  "default": 1
+                },
+                "healthy": {
+                  "type": "object",
+                  "properties": {
+                    "interval": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "default": 1
+                    },
+                    "http_statuses": {
+                      "type": "array",
+                      "items": {
+                        "type": "integer",
+                        "minimum": 200,
+                        "maximum": 599
+                      },
+                      "minItems": 1,
+                      "default": [
+                        200,
+                        302
+                      ],
+                      "uniqueItems": true
+                    },
+                    "successes": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "maximum": 254,
+                      "default": 2
+                    }
+                  }
+                },
+                "host": {
+                  "type": "string",
+                  "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+                },
+                "concurrency": {
+                  "type": "integer",
+                  "default": 10
+                },
+                "http_path": {
+                  "type": "string",
+                  "default": "/"
+                },
+                "https_verify_certificate": {
+                  "type": "boolean",
+                  "default": true
+                }
+              }
+            },
+            "passive": {
+              "type": "object",
+              "properties": {
+                "type": {
+                  "type": "string",
+                  "default": "http",
+                  "enum": [
+                    "http",
+                    "https",
+                    "tcp"
+                  ]
+                },
+                "unhealthy": {
+                  "type": "object",
+                  "properties": {
+                    "timeouts": {
+                      "type": "integer",
+                      "minimum": 0,
+                      "maximum": 254,
+                      "default": 7
+                    },
+                    "http_statuses": {
+                      "type": "array",
+                      "items": {
+                        "type": "integer",
+                        "minimum": 200,
+                        "maximum": 599
+                      },
+                      "minItems": 1,
+                      "default": [
+                        429,
+                        500,
+                        503
+                      ],
+                      "uniqueItems": true
+                    },
+                    "http_failures": {
+                      "type": "integer",
+                      "minimum": 0,
+                      "maximum": 254,
+                      "default": 5
+                    },
+                    "tcp_failures": {
+                      "type": "integer",
+                      "minimum": 0,
+                      "maximum": 254,
+                      "default": 2
+                    }
+                  }
+                },
+                "healthy": {
+                  "type": "object",
+                  "properties": {
+                    "http_statuses": {
+                      "type": "array",
+                      "items": {
+                        "type": "integer",
+                        "minimum": 200,
+                        "maximum": 599
+                      },
+                      "minItems": 1,
+                      "default": [
+                        200,
+                        201,
+                        202,
+                        203,
+                        204,
+                        205,
+                        206,
+                        207,
+                        208,
+                        226,
+                        300,
+                        301,
+                        302,
+                        303,
+                        304,
+                        305,
+                        306,
+                        307,
+                        308
+                      ],
+                      "uniqueItems": true
+                    },
+                    "successes": {
+                      "type": "integer",
+                      "minimum": 0,
+                      "maximum": 254,
+                      "default": 5
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        "retry_timeout": {
+          "type": "number",
+          "minimum": 0
+        },
+        "name": {
+          "type": "string",
+          "maxLength": 256,
+          "minLength": 1
+        },
+        "tls": {
+          "type": "object",
+          "properties": {
+            "client_key": {
+              "type": "string",
+              "maxLength": 65536,
+              "minLength": 64
+            },
+            "client_cert": {
+              "type": "string",
+              "maxLength": 65536,
+              "minLength": 128
+            },
+            "client_cert_id": {
+              "anyOf": [
+                {
+                  "type": "string",
+                  "pattern": "^[a-zA-Z0-9-_.]+$",
+                  "maxLength": 64,
+                  "minLength": 1
+                },
+                {
+                  "type": "integer",
+                  "minimum": 1
+                }
+              ]
+            },
+            "verify": {
+              "type": "boolean",
+              "description": "Turn on server certificate verification, 
currently only kafka upstream is supported",
+              "default": false
+            }
+          },
+          "dependencies": {
+            "client_key": {
+              "required": [
+                "client_cert"
+              ]
+            },
+            "client_cert": {
+              "required": [
+                "client_key"
+              ]
+            },
+            "client_cert_id": {
+              "not": {
+                "required": [
+                  "client_cert",
+                  "client_key"
+                ]
+              }
+            }
+          }
+        },
+        "retries": {
+          "type": "integer",
+          "minimum": 0
+        },
+        "keepalive_pool": {
+          "type": "object",
+          "properties": {
+            "idle_timeout": {
+              "type": "number",
+              "default": 60,
+              "minimum": 0
+            },
+            "requests": {
+              "type": "integer",
+              "default": 1000,
+              "minimum": 1
+            },
+            "size": {
+              "type": "integer",
+              "default": 320,
+              "minimum": 1
+            }
+          }
+        },
+        "service_name": {
+          "type": "string",
+          "maxLength": 256,
+          "minLength": 1
+        },
+        "scheme": {
+          "default": "http",
+          "description": "The scheme of the upstream. For L7 proxy, it can be 
one of grpc/grpcs/http/https. For L4 proxy, it can be one of tcp/tls/udp. For 
specific protocols, it can be kafka.",
+          "enum": [
+            "grpc",
+            "grpcs",
+            "http",
+            "https",
+            "tcp",
+            "tls",
+            "udp",
+            "kafka"
+          ]
+        },
+        "pass_host": {
+          "description": "mod of host passing",
+          "default": "pass",
+          "type": "string",
+          "enum": [
+            "pass",
+            "node",
+            "rewrite"
+          ]
+        },
+        "update_time": {
+          "type": "integer"
+        }
+      }
+    },
+    "update_time": {
+      "type": "integer"
+    },
+    "upstream_id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "script": {
+      "type": "string",
+      "maxLength": 102400,
+      "minLength": 10
+    },
+    "labels": {
+      "description": "key/value pairs to specify attributes",
+      "patternProperties": {
+        ".*": {
+          "maxLength": 256,
+          "description": "value of label",
+          "pattern": "^\\S+$",
+          "type": "string",
+          "minLength": 1
+        }
+      },
+      "type": "object"
+    },
+    "plugins": {
+      "type": "object"
+    },
+    "name": {
+      "type": "string",
+      "maxLength": 256,
+      "minLength": 1
+    },
+    "enable_websocket": {
+      "description": "enable websocket for request",
+      "type": "boolean"
+    },
+    "hosts": {
+      "type": "array",
+      "items": {
+        "type": "string",
+        "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+      },
+      "minItems": 1,
+      "uniqueItems": true
+    }
+  }
+}
diff --git a/src/types/schema/apisix/__fixtures__/gateway/ssl.json 
b/src/types/schema/apisix/__fixtures__/gateway/ssl.json
new file mode 100644
index 000000000..85880a367
--- /dev/null
+++ b/src/types/schema/apisix/__fixtures__/gateway/ssl.json
@@ -0,0 +1,172 @@
+{
+  "type": "object",
+  "if": {
+    "properties": {
+      "type": {
+        "enum": [
+          "server"
+        ]
+      }
+    }
+  },
+  "else": {
+    "required": [
+      "key",
+      "cert"
+    ]
+  },
+  "properties": {
+    "update_time": {
+      "type": "integer"
+    },
+    "key": {
+      "type": "string",
+      "maxLength": 65536,
+      "minLength": 64
+    },
+    "desc": {
+      "type": "string",
+      "maxLength": 256
+    },
+    "id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "create_time": {
+      "type": "integer"
+    },
+    "status": {
+      "description": "ssl status, 1 to enable, 0 to disable",
+      "default": 1,
+      "type": "integer",
+      "enum": [
+        1,
+        0
+      ]
+    },
+    "sni": {
+      "type": "string",
+      "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+    },
+    "type": {
+      "description": "ssl certificate type, server to server certificate, 
client to client certificate for upstream",
+      "type": "string",
+      "default": "server",
+      "enum": [
+        "server",
+        "client"
+      ]
+    },
+    "cert": {
+      "type": "string",
+      "maxLength": 65536,
+      "minLength": 128
+    },
+    "labels": {
+      "description": "key/value pairs to specify attributes",
+      "patternProperties": {
+        ".*": {
+          "maxLength": 256,
+          "description": "value of label",
+          "pattern": "^\\S+$",
+          "type": "string",
+          "minLength": 1
+        }
+      },
+      "type": "object"
+    },
+    "ssl_protocols": {
+      "type": "array",
+      "items": {
+        "enum": [
+          "TLSv1.1",
+          "TLSv1.2",
+          "TLSv1.3"
+        ]
+      },
+      "maxItems": 3,
+      "description": "set ssl protocols",
+      "uniqueItems": true
+    },
+    "keys": {
+      "type": "array",
+      "items": {
+        "type": "string",
+        "maxLength": 65536,
+        "minLength": 64
+      }
+    },
+    "client": {
+      "type": "object",
+      "required": [
+        "ca"
+      ],
+      "properties": {
+        "ca": {
+          "type": "string",
+          "maxLength": 65536,
+          "minLength": 128
+        },
+        "skip_mtls_uri_regex": {
+          "type": "array",
+          "items": {
+            "description": "uri regular expression to skip mtls",
+            "type": "string"
+          },
+          "minItems": 1,
+          "uniqueItems": true
+        },
+        "depth": {
+          "type": "integer",
+          "minimum": 0,
+          "default": 1
+        }
+      }
+    },
+    "snis": {
+      "type": "array",
+      "items": {
+        "type": "string",
+        "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+      },
+      "minItems": 1
+    },
+    "certs": {
+      "type": "array",
+      "items": {
+        "type": "string",
+        "maxLength": 65536,
+        "minLength": 128
+      }
+    }
+  },
+  "additionalProperties": false,
+  "then": {
+    "oneOf": [
+      {
+        "required": [
+          "sni",
+          "key",
+          "cert"
+        ]
+      },
+      {
+        "required": [
+          "snis",
+          "key",
+          "cert"
+        ]
+      }
+    ]
+  }
+}
diff --git a/src/types/schema/apisix/__fixtures__/gateway/stream_route.json 
b/src/types/schema/apisix/__fixtures__/gateway/stream_route.json
new file mode 100644
index 000000000..9f52dd74a
--- /dev/null
+++ b/src/types/schema/apisix/__fixtures__/gateway/stream_route.json
@@ -0,0 +1,717 @@
+{
+  "type": "object",
+  "additionalProperties": false,
+  "properties": {
+    "remote_addr": {
+      "description": "client IP",
+      "anyOf": [
+        {
+          "type": "string",
+          "title": "IPv4",
+          "format": "ipv4"
+        },
+        {
+          "type": "string",
+          "pattern": 
"^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$",
+          "title": "IPv4/CIDR"
+        },
+        {
+          "type": "string",
+          "title": "IPv6",
+          "format": "ipv6"
+        },
+        {
+          "type": "string",
+          "pattern": 
"^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$",
+          "title": "IPv6/CIDR"
+        }
+      ],
+      "type": "string"
+    },
+    "server_port": {
+      "description": "server port",
+      "type": "integer",
+      "minimum": 1,
+      "maximum": 65535
+    },
+    "desc": {
+      "type": "string",
+      "maxLength": 256
+    },
+    "id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "create_time": {
+      "type": "integer"
+    },
+    "update_time": {
+      "type": "integer"
+    },
+    "sni": {
+      "description": "server name indication",
+      "type": "string",
+      "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+    },
+    "labels": {
+      "description": "key/value pairs to specify attributes",
+      "patternProperties": {
+        ".*": {
+          "maxLength": 256,
+          "description": "value of label",
+          "pattern": "^\\S+$",
+          "type": "string",
+          "minLength": 1
+        }
+      },
+      "type": "object"
+    },
+    "upstream_id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "server_addr": {
+      "description": "server IP",
+      "anyOf": [
+        {
+          "type": "string",
+          "title": "IPv4",
+          "format": "ipv4"
+        },
+        {
+          "type": "string",
+          "pattern": 
"^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([12]?[0-9]|3[0-2])$",
+          "title": "IPv4/CIDR"
+        },
+        {
+          "type": "string",
+          "title": "IPv6",
+          "format": "ipv6"
+        },
+        {
+          "type": "string",
+          "pattern": 
"^([a-fA-F0-9]{0,4}:){1,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?/[0-9]{1,3}$",
+          "title": "IPv6/CIDR"
+        }
+      ],
+      "type": "string"
+    },
+    "protocol": {
+      "type": "object",
+      "required": [
+        "name"
+      ],
+      "properties": {
+        "conf": {
+          "description": "protocol-specific configuration",
+          "type": "object"
+        },
+        "superior_id": {
+          "anyOf": [
+            {
+              "type": "string",
+              "pattern": "^[a-zA-Z0-9-_.]+$",
+              "maxLength": 64,
+              "minLength": 1
+            },
+            {
+              "type": "integer",
+              "minimum": 1
+            }
+          ]
+        },
+        "logger": {
+          "type": "array",
+          "items": {
+            "properties": {
+              "conf": {
+                "description": "logger plugin configuration",
+                "type": "object"
+              },
+              "filter": {
+                "description": "logger filter rules",
+                "type": "array"
+              },
+              "name": {
+                "type": "string"
+              }
+            },
+            "additionalProperties": false,
+            "dependencies": {
+              "name": [
+                "conf"
+              ]
+            }
+          }
+        },
+        "name": {
+          "type": "string"
+        }
+      }
+    },
+    "plugins": {
+      "type": "object"
+    },
+    "service_id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "upstream": {
+      "type": "object",
+      "oneOf": [
+        {
+          "required": [
+            "nodes"
+          ]
+        },
+        {
+          "required": [
+            "service_name",
+            "discovery_type"
+          ]
+        }
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "hash_on": {
+          "type": "string",
+          "default": "vars",
+          "enum": [
+            "vars",
+            "header",
+            "cookie",
+            "consumer",
+            "vars_combinations"
+          ]
+        },
+        "id": {
+          "anyOf": [
+            {
+              "type": "string",
+              "pattern": "^[a-zA-Z0-9-_.]+$",
+              "maxLength": 64,
+              "minLength": 1
+            },
+            {
+              "type": "integer",
+              "minimum": 1
+            }
+          ]
+        },
+        "upstream_host": {
+          "type": "string",
+          "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+        },
+        "nodes": {
+          "anyOf": [
+            {
+              "type": "object",
+              "patternProperties": {
+                ".*": {
+                  "description": "weight of node",
+                  "type": "integer",
+                  "minimum": 0
+                }
+              }
+            },
+            {
+              "type": "array",
+              "items": {
+                "type": "object",
+                "required": [
+                  "host",
+                  "weight"
+                ],
+                "properties": {
+                  "priority": {
+                    "description": "priority of node",
+                    "type": "integer",
+                    "default": 0
+                  },
+                  "weight": {
+                    "description": "weight of node",
+                    "type": "integer",
+                    "minimum": 0
+                  },
+                  "port": {
+                    "description": "port of node",
+                    "type": "integer",
+                    "minimum": 1,
+                    "maximum": 65535
+                  },
+                  "host": {
+                    "type": "string",
+                    "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+                  },
+                  "metadata": {
+                    "description": "metadata of node",
+                    "type": "object"
+                  }
+                }
+              }
+            }
+          ]
+        },
+        "type": {
+          "description": "algorithms of load balancing",
+          "type": "string",
+          "default": "roundrobin"
+        },
+        "discovery_type": {
+          "description": "discovery type",
+          "type": "string"
+        },
+        "discovery_args": {
+          "type": "object",
+          "properties": {
+            "namespace_id": {
+              "description": "namespace id",
+              "type": "string"
+            },
+            "group_name": {
+              "description": "group name",
+              "type": "string"
+            }
+          }
+        },
+        "timeout": {
+          "type": "object",
+          "required": [
+            "connect",
+            "send",
+            "read"
+          ],
+          "properties": {
+            "send": {
+              "type": "number",
+              "exclusiveMinimum": 0
+            },
+            "connect": {
+              "type": "number",
+              "exclusiveMinimum": 0
+            },
+            "read": {
+              "type": "number",
+              "exclusiveMinimum": 0
+            }
+          }
+        },
+        "key": {
+          "description": "the key of chash for dynamic load balancing",
+          "type": "string"
+        },
+        "desc": {
+          "type": "string",
+          "maxLength": 256
+        },
+        "labels": {
+          "description": "key/value pairs to specify attributes",
+          "patternProperties": {
+            ".*": {
+              "maxLength": 256,
+              "description": "value of label",
+              "pattern": "^\\S+$",
+              "type": "string",
+              "minLength": 1
+            }
+          },
+          "type": "object"
+        },
+        "create_time": {
+          "type": "integer"
+        },
+        "checks": {
+          "type": "object",
+          "anyOf": [
+            {
+              "required": [
+                "active"
+              ]
+            },
+            {
+              "required": [
+                "active",
+                "passive"
+              ]
+            }
+          ],
+          "properties": {
+            "active": {
+              "type": "object",
+              "properties": {
+                "unhealthy": {
+                  "type": "object",
+                  "properties": {
+                    "interval": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "default": 1
+                    },
+                    "http_failures": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "maximum": 254,
+                      "default": 5
+                    },
+                    "tcp_failures": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "maximum": 254,
+                      "default": 2
+                    },
+                    "timeouts": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "maximum": 254,
+                      "default": 3
+                    },
+                    "http_statuses": {
+                      "type": "array",
+                      "items": {
+                        "type": "integer",
+                        "minimum": 200,
+                        "maximum": 599
+                      },
+                      "minItems": 1,
+                      "default": [
+                        429,
+                        404,
+                        500,
+                        501,
+                        502,
+                        503,
+                        504,
+                        505
+                      ],
+                      "uniqueItems": true
+                    }
+                  }
+                },
+                "port": {
+                  "type": "integer",
+                  "minimum": 1,
+                  "maximum": 65535
+                },
+                "req_headers": {
+                  "type": "array",
+                  "items": {
+                    "type": "string",
+                    "uniqueItems": true
+                  },
+                  "minItems": 1
+                },
+                "type": {
+                  "type": "string",
+                  "default": "http",
+                  "enum": [
+                    "http",
+                    "https",
+                    "tcp"
+                  ]
+                },
+                "timeout": {
+                  "type": "number",
+                  "default": 1
+                },
+                "healthy": {
+                  "type": "object",
+                  "properties": {
+                    "interval": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "default": 1
+                    },
+                    "http_statuses": {
+                      "type": "array",
+                      "items": {
+                        "type": "integer",
+                        "minimum": 200,
+                        "maximum": 599
+                      },
+                      "minItems": 1,
+                      "default": [
+                        200,
+                        302
+                      ],
+                      "uniqueItems": true
+                    },
+                    "successes": {
+                      "type": "integer",
+                      "minimum": 1,
+                      "maximum": 254,
+                      "default": 2
+                    }
+                  }
+                },
+                "host": {
+                  "type": "string",
+                  "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+                },
+                "concurrency": {
+                  "type": "integer",
+                  "default": 10
+                },
+                "http_path": {
+                  "type": "string",
+                  "default": "/"
+                },
+                "https_verify_certificate": {
+                  "type": "boolean",
+                  "default": true
+                }
+              }
+            },
+            "passive": {
+              "type": "object",
+              "properties": {
+                "type": {
+                  "type": "string",
+                  "default": "http",
+                  "enum": [
+                    "http",
+                    "https",
+                    "tcp"
+                  ]
+                },
+                "unhealthy": {
+                  "type": "object",
+                  "properties": {
+                    "timeouts": {
+                      "type": "integer",
+                      "minimum": 0,
+                      "maximum": 254,
+                      "default": 7
+                    },
+                    "http_statuses": {
+                      "type": "array",
+                      "items": {
+                        "type": "integer",
+                        "minimum": 200,
+                        "maximum": 599
+                      },
+                      "minItems": 1,
+                      "default": [
+                        429,
+                        500,
+                        503
+                      ],
+                      "uniqueItems": true
+                    },
+                    "http_failures": {
+                      "type": "integer",
+                      "minimum": 0,
+                      "maximum": 254,
+                      "default": 5
+                    },
+                    "tcp_failures": {
+                      "type": "integer",
+                      "minimum": 0,
+                      "maximum": 254,
+                      "default": 2
+                    }
+                  }
+                },
+                "healthy": {
+                  "type": "object",
+                  "properties": {
+                    "http_statuses": {
+                      "type": "array",
+                      "items": {
+                        "type": "integer",
+                        "minimum": 200,
+                        "maximum": 599
+                      },
+                      "minItems": 1,
+                      "default": [
+                        200,
+                        201,
+                        202,
+                        203,
+                        204,
+                        205,
+                        206,
+                        207,
+                        208,
+                        226,
+                        300,
+                        301,
+                        302,
+                        303,
+                        304,
+                        305,
+                        306,
+                        307,
+                        308
+                      ],
+                      "uniqueItems": true
+                    },
+                    "successes": {
+                      "type": "integer",
+                      "minimum": 0,
+                      "maximum": 254,
+                      "default": 5
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        "retry_timeout": {
+          "type": "number",
+          "minimum": 0
+        },
+        "name": {
+          "type": "string",
+          "maxLength": 256,
+          "minLength": 1
+        },
+        "tls": {
+          "type": "object",
+          "properties": {
+            "client_key": {
+              "type": "string",
+              "maxLength": 65536,
+              "minLength": 64
+            },
+            "client_cert": {
+              "type": "string",
+              "maxLength": 65536,
+              "minLength": 128
+            },
+            "client_cert_id": {
+              "anyOf": [
+                {
+                  "type": "string",
+                  "pattern": "^[a-zA-Z0-9-_.]+$",
+                  "maxLength": 64,
+                  "minLength": 1
+                },
+                {
+                  "type": "integer",
+                  "minimum": 1
+                }
+              ]
+            },
+            "verify": {
+              "type": "boolean",
+              "description": "Turn on server certificate verification, 
currently only kafka upstream is supported",
+              "default": false
+            }
+          },
+          "dependencies": {
+            "client_key": {
+              "required": [
+                "client_cert"
+              ]
+            },
+            "client_cert": {
+              "required": [
+                "client_key"
+              ]
+            },
+            "client_cert_id": {
+              "not": {
+                "required": [
+                  "client_cert",
+                  "client_key"
+                ]
+              }
+            }
+          }
+        },
+        "retries": {
+          "type": "integer",
+          "minimum": 0
+        },
+        "keepalive_pool": {
+          "type": "object",
+          "properties": {
+            "idle_timeout": {
+              "type": "number",
+              "default": 60,
+              "minimum": 0
+            },
+            "requests": {
+              "type": "integer",
+              "default": 1000,
+              "minimum": 1
+            },
+            "size": {
+              "type": "integer",
+              "default": 320,
+              "minimum": 1
+            }
+          }
+        },
+        "service_name": {
+          "type": "string",
+          "maxLength": 256,
+          "minLength": 1
+        },
+        "scheme": {
+          "default": "http",
+          "description": "The scheme of the upstream. For L7 proxy, it can be 
one of grpc/grpcs/http/https. For L4 proxy, it can be one of tcp/tls/udp. For 
specific protocols, it can be kafka.",
+          "enum": [
+            "grpc",
+            "grpcs",
+            "http",
+            "https",
+            "tcp",
+            "tls",
+            "udp",
+            "kafka"
+          ]
+        },
+        "pass_host": {
+          "description": "mod of host passing",
+          "default": "pass",
+          "type": "string",
+          "enum": [
+            "pass",
+            "node",
+            "rewrite"
+          ]
+        },
+        "update_time": {
+          "type": "integer"
+        }
+      }
+    },
+    "name": {
+      "type": "string",
+      "maxLength": 256,
+      "minLength": 1
+    }
+  }
+}
diff --git a/src/types/schema/apisix/__fixtures__/gateway/upstream.json 
b/src/types/schema/apisix/__fixtures__/gateway/upstream.json
new file mode 100644
index 000000000..f4be357bc
--- /dev/null
+++ b/src/types/schema/apisix/__fixtures__/gateway/upstream.json
@@ -0,0 +1,522 @@
+{
+  "type": "object",
+  "oneOf": [
+    {
+      "required": [
+        "nodes"
+      ]
+    },
+    {
+      "required": [
+        "service_name",
+        "discovery_type"
+      ]
+    }
+  ],
+  "additionalProperties": false,
+  "properties": {
+    "hash_on": {
+      "type": "string",
+      "default": "vars",
+      "enum": [
+        "vars",
+        "header",
+        "cookie",
+        "consumer",
+        "vars_combinations"
+      ]
+    },
+    "id": {
+      "anyOf": [
+        {
+          "type": "string",
+          "pattern": "^[a-zA-Z0-9-_.]+$",
+          "maxLength": 64,
+          "minLength": 1
+        },
+        {
+          "type": "integer",
+          "minimum": 1
+        }
+      ]
+    },
+    "upstream_host": {
+      "type": "string",
+      "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+    },
+    "nodes": {
+      "anyOf": [
+        {
+          "type": "object",
+          "patternProperties": {
+            ".*": {
+              "description": "weight of node",
+              "type": "integer",
+              "minimum": 0
+            }
+          }
+        },
+        {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "required": [
+              "host",
+              "weight"
+            ],
+            "properties": {
+              "priority": {
+                "description": "priority of node",
+                "type": "integer",
+                "default": 0
+              },
+              "weight": {
+                "description": "weight of node",
+                "type": "integer",
+                "minimum": 0
+              },
+              "port": {
+                "description": "port of node",
+                "type": "integer",
+                "minimum": 1,
+                "maximum": 65535
+              },
+              "host": {
+                "type": "string",
+                "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+              },
+              "metadata": {
+                "description": "metadata of node",
+                "type": "object"
+              }
+            }
+          }
+        }
+      ]
+    },
+    "type": {
+      "description": "algorithms of load balancing",
+      "type": "string",
+      "default": "roundrobin"
+    },
+    "discovery_type": {
+      "description": "discovery type",
+      "type": "string"
+    },
+    "discovery_args": {
+      "type": "object",
+      "properties": {
+        "namespace_id": {
+          "description": "namespace id",
+          "type": "string"
+        },
+        "group_name": {
+          "description": "group name",
+          "type": "string"
+        }
+      }
+    },
+    "timeout": {
+      "type": "object",
+      "required": [
+        "connect",
+        "send",
+        "read"
+      ],
+      "properties": {
+        "send": {
+          "type": "number",
+          "exclusiveMinimum": 0
+        },
+        "connect": {
+          "type": "number",
+          "exclusiveMinimum": 0
+        },
+        "read": {
+          "type": "number",
+          "exclusiveMinimum": 0
+        }
+      }
+    },
+    "key": {
+      "description": "the key of chash for dynamic load balancing",
+      "type": "string"
+    },
+    "desc": {
+      "type": "string",
+      "maxLength": 256
+    },
+    "labels": {
+      "description": "key/value pairs to specify attributes",
+      "patternProperties": {
+        ".*": {
+          "maxLength": 256,
+          "description": "value of label",
+          "pattern": "^\\S+$",
+          "type": "string",
+          "minLength": 1
+        }
+      },
+      "type": "object"
+    },
+    "create_time": {
+      "type": "integer"
+    },
+    "checks": {
+      "type": "object",
+      "anyOf": [
+        {
+          "required": [
+            "active"
+          ]
+        },
+        {
+          "required": [
+            "active",
+            "passive"
+          ]
+        }
+      ],
+      "properties": {
+        "active": {
+          "type": "object",
+          "properties": {
+            "unhealthy": {
+              "type": "object",
+              "properties": {
+                "interval": {
+                  "type": "integer",
+                  "minimum": 1,
+                  "default": 1
+                },
+                "http_failures": {
+                  "type": "integer",
+                  "minimum": 1,
+                  "maximum": 254,
+                  "default": 5
+                },
+                "tcp_failures": {
+                  "type": "integer",
+                  "minimum": 1,
+                  "maximum": 254,
+                  "default": 2
+                },
+                "timeouts": {
+                  "type": "integer",
+                  "minimum": 1,
+                  "maximum": 254,
+                  "default": 3
+                },
+                "http_statuses": {
+                  "type": "array",
+                  "items": {
+                    "type": "integer",
+                    "minimum": 200,
+                    "maximum": 599
+                  },
+                  "minItems": 1,
+                  "default": [
+                    429,
+                    404,
+                    500,
+                    501,
+                    502,
+                    503,
+                    504,
+                    505
+                  ],
+                  "uniqueItems": true
+                }
+              }
+            },
+            "port": {
+              "type": "integer",
+              "minimum": 1,
+              "maximum": 65535
+            },
+            "req_headers": {
+              "type": "array",
+              "items": {
+                "type": "string",
+                "uniqueItems": true
+              },
+              "minItems": 1
+            },
+            "type": {
+              "type": "string",
+              "default": "http",
+              "enum": [
+                "http",
+                "https",
+                "tcp"
+              ]
+            },
+            "timeout": {
+              "type": "number",
+              "default": 1
+            },
+            "healthy": {
+              "type": "object",
+              "properties": {
+                "interval": {
+                  "type": "integer",
+                  "minimum": 1,
+                  "default": 1
+                },
+                "http_statuses": {
+                  "type": "array",
+                  "items": {
+                    "type": "integer",
+                    "minimum": 200,
+                    "maximum": 599
+                  },
+                  "minItems": 1,
+                  "default": [
+                    200,
+                    302
+                  ],
+                  "uniqueItems": true
+                },
+                "successes": {
+                  "type": "integer",
+                  "minimum": 1,
+                  "maximum": 254,
+                  "default": 2
+                }
+              }
+            },
+            "host": {
+              "type": "string",
+              "pattern": "^\\*$|^\\*?[0-9a-zA-Z-._\\[\\]:]+$"
+            },
+            "concurrency": {
+              "type": "integer",
+              "default": 10
+            },
+            "http_path": {
+              "type": "string",
+              "default": "/"
+            },
+            "https_verify_certificate": {
+              "type": "boolean",
+              "default": true
+            }
+          }
+        },
+        "passive": {
+          "type": "object",
+          "properties": {
+            "type": {
+              "type": "string",
+              "default": "http",
+              "enum": [
+                "http",
+                "https",
+                "tcp"
+              ]
+            },
+            "unhealthy": {
+              "type": "object",
+              "properties": {
+                "timeouts": {
+                  "type": "integer",
+                  "minimum": 0,
+                  "maximum": 254,
+                  "default": 7
+                },
+                "http_statuses": {
+                  "type": "array",
+                  "items": {
+                    "type": "integer",
+                    "minimum": 200,
+                    "maximum": 599
+                  },
+                  "minItems": 1,
+                  "default": [
+                    429,
+                    500,
+                    503
+                  ],
+                  "uniqueItems": true
+                },
+                "http_failures": {
+                  "type": "integer",
+                  "minimum": 0,
+                  "maximum": 254,
+                  "default": 5
+                },
+                "tcp_failures": {
+                  "type": "integer",
+                  "minimum": 0,
+                  "maximum": 254,
+                  "default": 2
+                }
+              }
+            },
+            "healthy": {
+              "type": "object",
+              "properties": {
+                "http_statuses": {
+                  "type": "array",
+                  "items": {
+                    "type": "integer",
+                    "minimum": 200,
+                    "maximum": 599
+                  },
+                  "minItems": 1,
+                  "default": [
+                    200,
+                    201,
+                    202,
+                    203,
+                    204,
+                    205,
+                    206,
+                    207,
+                    208,
+                    226,
+                    300,
+                    301,
+                    302,
+                    303,
+                    304,
+                    305,
+                    306,
+                    307,
+                    308
+                  ],
+                  "uniqueItems": true
+                },
+                "successes": {
+                  "type": "integer",
+                  "minimum": 0,
+                  "maximum": 254,
+                  "default": 5
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "retry_timeout": {
+      "type": "number",
+      "minimum": 0
+    },
+    "name": {
+      "type": "string",
+      "maxLength": 256,
+      "minLength": 1
+    },
+    "tls": {
+      "type": "object",
+      "properties": {
+        "client_key": {
+          "type": "string",
+          "maxLength": 65536,
+          "minLength": 64
+        },
+        "client_cert": {
+          "type": "string",
+          "maxLength": 65536,
+          "minLength": 128
+        },
+        "client_cert_id": {
+          "anyOf": [
+            {
+              "type": "string",
+              "pattern": "^[a-zA-Z0-9-_.]+$",
+              "maxLength": 64,
+              "minLength": 1
+            },
+            {
+              "type": "integer",
+              "minimum": 1
+            }
+          ]
+        },
+        "verify": {
+          "type": "boolean",
+          "description": "Turn on server certificate verification, currently 
only kafka upstream is supported",
+          "default": false
+        }
+      },
+      "dependencies": {
+        "client_key": {
+          "required": [
+            "client_cert"
+          ]
+        },
+        "client_cert": {
+          "required": [
+            "client_key"
+          ]
+        },
+        "client_cert_id": {
+          "not": {
+            "required": [
+              "client_cert",
+              "client_key"
+            ]
+          }
+        }
+      }
+    },
+    "retries": {
+      "type": "integer",
+      "minimum": 0
+    },
+    "keepalive_pool": {
+      "type": "object",
+      "properties": {
+        "idle_timeout": {
+          "type": "number",
+          "default": 60,
+          "minimum": 0
+        },
+        "requests": {
+          "type": "integer",
+          "default": 1000,
+          "minimum": 1
+        },
+        "size": {
+          "type": "integer",
+          "default": 320,
+          "minimum": 1
+        }
+      }
+    },
+    "service_name": {
+      "type": "string",
+      "maxLength": 256,
+      "minLength": 1
+    },
+    "scheme": {
+      "default": "http",
+      "description": "The scheme of the upstream. For L7 proxy, it can be one 
of grpc/grpcs/http/https. For L4 proxy, it can be one of tcp/tls/udp. For 
specific protocols, it can be kafka.",
+      "enum": [
+        "grpc",
+        "grpcs",
+        "http",
+        "https",
+        "tcp",
+        "tls",
+        "udp",
+        "kafka"
+      ]
+    },
+    "pass_host": {
+      "description": "mod of host passing",
+      "default": "pass",
+      "type": "string",
+      "enum": [
+        "pass",
+        "node",
+        "rewrite"
+      ]
+    },
+    "update_time": {
+      "type": "integer"
+    }
+  }
+}
diff --git a/src/types/schema/apisix/gateway-contract.test.ts 
b/src/types/schema/apisix/gateway-contract.test.ts
new file mode 100644
index 000000000..d38795bf2
--- /dev/null
+++ b/src/types/schema/apisix/gateway-contract.test.ts
@@ -0,0 +1,171 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+
+import { describe, expect, it } from 'vitest';
+import type { ZodObject, ZodRawShape, ZodTypeAny } from 'zod';
+
+import { APISIX } from '.';
+
+// Cheapest structural guard for the hand-written zod transcription of the
+// Admin API (#3417): the zod layer must be *looser or equal* to the gateway's
+// own schema, i.e. it must not reject what the gateway accepts. That whole
+// class of "form rejects a valid config" bugs (#3146/#3147/#3362/#3376/#3395)
+// comes from a field the gateway has that the zod schema silently drops.
+//
+// The gateway's per-resource schema is snapshotted under __fixtures__/gateway
+// (refresh with `pnpm refresh:gateway-schema` on APISIX version bumps). For
+// each top-level property the gateway defines this asserts, against the merged
+// dashboard schema:
+//   - field coverage: the zod schema knows the field (else the resolver strips
+//     it on submit);
+//   - enum: the zod field accepts every value the gateway's enum allows.
+//
+// (Required-ness is intentionally NOT compared: the dashboard's full read
+// schema requires the server-managed id/create_time/update_time that the
+// gateway write schema does not, and the write path uses Post/Put variants
+// that omit them, so a required-direction check against the full schema only
+// produces false positives.)
+//
+// Fields the dashboard deliberately does not accept live in ALLOWLIST with a
+// reason — never a silent omission. A stale allowlist entry (one zod now
+// covers) also fails, forcing cleanup. Scope is top-level only; nested objects
+// and plugin configs (passthrough) are out of scope.
+
+const FIXTURE_DIR = fileURLToPath(new URL('./__fixtures__/gateway/', 
import.meta.url));
+
+type GatewaySchema = {
+  properties?: Record<string, { enum?: unknown[]; type?: string }>;
+  required?: string[];
+};
+
+const loadFixture = (resource: string): GatewaySchema =>
+  JSON.parse(readFileSync(`${FIXTURE_DIR}${resource}.json`, 'utf8'));
+
+// gateway resource name -> dashboard zod schema (the full stored shape).
+const SCHEMAS: Record<string, ZodTypeAny> = {
+  route: APISIX.Route,
+  service: APISIX.Service,
+  upstream: APISIX.Upstream,
+  consumer: APISIX.Consumer,
+  ssl: APISIX.SSL,
+  global_rule: APISIX.GlobalRule,
+  plugin_config: APISIX.PluginConfig,
+  consumer_group: APISIX.ConsumerGroup,
+  stream_route: APISIX.StreamRoute,
+  proto: APISIX.Proto,
+  credential: APISIX.Credential,
+};
+
+// Gateway top-level fields the dashboard's zod schema does not model. Every
+// entry carries a reason: either an intentional non-support decision, or a
+// KNOWN GAP flagged for a follow-up fix — never a silent omission.
+const ALLOWLIST: Record<string, Record<string, string>> = {
+  route: {},
+  service: {},
+  upstream: {
+    create_time: 'server-managed timestamp not modelled on the upstream schema 
(write path omits it; read-only display only)',
+    update_time: 'server-managed timestamp not modelled on the upstream schema 
(write path omits it; read-only display only)',
+  },
+  consumer: {
+    id: 'consumers are keyed by username in the dashboard; the gateway id is 
not modelled',
+  },
+  ssl: {},
+  global_rule: {},
+  plugin_config: {},
+  consumer_group: {
+    name: 'the dashboard consumer-group form exposes id/desc/labels/plugins, 
not a separate name field',
+  },
+  stream_route: {
+    name: 'the base StreamRoute schema omits name (and status); name IS 
accepted on the write path via StreamRoutePostSchema (#3437) — the read schema 
just does not model it',
+  },
+  proto: {
+    name: 'the dashboard proto form models content/id only',
+    labels: 'the dashboard proto form models content/id only',
+    desc: 'the dashboard proto form models content/id only',
+  },
+  credential: {
+    name: 'credentials are keyed by id in the dashboard; the gateway name is 
not modelled',
+  },
+};
+
+// Enum VALUES a covered field is allowed not to accept, as `field="value"`.
+// Same rule: each carries a reason (intentional or a flagged gap).
+const ENUM_ALLOWLIST: Record<string, Record<string, string>> = {
+  upstream: {
+    'scheme="kafka"':
+      'KNOWN GAP — the dashboard upstream `scheme` enum omits "kafka"; a 
kafka-scheme upstream cannot be created via the form. Flagged for a follow-up 
fix (also needs UI support), tracked separately.',
+  },
+};
+
+/** Unwrap ZodEffects/optional/default wrappers down to the underlying object. 
*/
+const toObject = (schema: ZodTypeAny): ZodObject<ZodRawShape> => {
+  let cur: ZodTypeAny = schema;
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  const def = () => (cur as any)._def;
+  while (
+    def()?.typeName === 'ZodEffects' ||
+    def()?.typeName === 'ZodOptional' ||
+    def()?.typeName === 'ZodDefault'
+  ) {
+    cur = def().typeName === 'ZodEffects' ? def().schema : def().innerType;
+  }
+  return cur as ZodObject<ZodRawShape>;
+};
+
+describe('gateway contract: zod is looser-or-equal to the APISIX schema', () 
=> {
+  it.each(Object.keys(SCHEMAS))('%s', (resource) => {
+    const gateway = loadFixture(resource);
+    const properties = gateway.properties ?? {};
+    const shape = toObject(SCHEMAS[resource]).shape;
+    const zodKeys = new Set(Object.keys(shape));
+    const allow = ALLOWLIST[resource] ?? {};
+    const enumAllow = ENUM_ALLOWLIST[resource] ?? {};
+
+    const notCovered: string[] = [];
+    const enumGaps: string[] = [];
+
+    for (const [field, spec] of Object.entries(properties)) {
+      if (!zodKeys.has(field)) {
+        if (!(field in allow)) notCovered.push(field);
+        continue;
+      }
+      const fieldSchema = shape[field] as ZodTypeAny;
+      // enum: every gateway-allowed value must pass the zod field.
+      if (Array.isArray(spec.enum)) {
+        for (const v of spec.enum) {
+          const key = `${field}=${JSON.stringify(v)}`;
+          if (!fieldSchema.safeParse(v).success && !(key in enumAllow)) {
+            enumGaps.push(key);
+          }
+        }
+      }
+    }
+
+    // Stale allowlist: a field-coverage exemption the zod schema now covers,
+    // or an exemption for a field the gateway no longer defines.
+    const staleAllow = Object.keys(allow).filter(
+      (f) => zodKeys.has(f) || !(f in properties)
+    );
+
+    expect(
+      { notCovered, enumGaps, staleAllow },
+      `${resource}: zod is stricter than the gateway (or the allowlist is 
stale)`
+    ).toEqual({ notCovered: [], enumGaps: [], staleAllow: [] });
+  });
+});

Reply via email to