From 2c28efdcad6eccd9a3a34e7201d909ccc260a8ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 18 Sep 2026 20:27:14 +0800 Subject: [PATCH] Validate IP addresses and prefixes in JSON schema --- docs/schema.json | 420 +++++++++++++++++++++++--------------------- option/rule.go | 4 +- option/rule_dns.go | 4 +- option/rule_set.go | 4 +- schema/generator.go | 47 +++-- schema/schema.go | 33 ++++ 6 files changed, 288 insertions(+), 224 deletions(-) diff --git a/docs/schema.json b/docs/schema.json index a26e3639a..2e36c9516 100644 --- a/docs/schema.json +++ b/docs/schema.json @@ -728,10 +728,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -881,7 +881,7 @@ ] }, "client_subnet": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } }, "additionalProperties": false @@ -1075,12 +1075,12 @@ "query_client_subnet": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -1214,12 +1214,12 @@ "source_ip_cidr": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -1431,12 +1431,12 @@ "additionalProperties": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -1450,12 +1450,12 @@ "additionalProperties": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -1464,12 +1464,12 @@ "default_interface_address": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -1544,12 +1544,12 @@ "ip_cidr": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -1685,7 +1685,7 @@ "maximum": 4294967295 }, "client_subnet": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, "remove_client_subnet": { "type": "boolean" @@ -1726,7 +1726,7 @@ "maximum": 4294967295 }, "client_subnet": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, "remove_client_subnet": { "type": "boolean" @@ -1774,7 +1774,7 @@ "maximum": 4294967295 }, "client_subnet": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, "remove_client_subnet": { "type": "boolean" @@ -1887,10 +1887,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -2013,13 +2013,13 @@ "type": "string" }, "inet4_range": { - "type": "string", + "$ref": "#/$defs/IPPrefix", "examples": [ "198.18.0.0/15" ] }, "inet6_range": { - "type": "string", + "$ref": "#/$defs/IPPrefix", "examples": [ "fc00::/18" ] @@ -2047,10 +2047,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -2191,12 +2191,12 @@ "additionalProperties": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddress" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddress" } } ] @@ -2225,10 +2225,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -2359,10 +2359,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -2489,10 +2489,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -2680,10 +2680,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -2850,10 +2850,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -2972,10 +2972,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -3097,10 +3097,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -3253,10 +3253,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -3378,7 +3378,7 @@ "maximum": 4294967295 }, "client_subnet": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } }, "required": [ @@ -3422,10 +3422,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -3702,10 +3702,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -3865,21 +3865,21 @@ "address": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPPrefix" } } ] }, "peer_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "peer_address_ipv6": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "topology": { "type": "string", @@ -4023,11 +4023,11 @@ "routes": { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPPrefix" } }, "route_gateway": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "route_metric": { "type": "integer" @@ -4103,7 +4103,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -4219,21 +4219,21 @@ "address": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPPrefix" } } ] }, "peer_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "peer_address_ipv6": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "topology": { "type": "string", @@ -4365,10 +4365,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -4483,7 +4483,7 @@ "advertise_routes": { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPPrefix" } }, "advertise_exit_node": { @@ -4585,12 +4585,12 @@ "address": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPPrefix" } } ] @@ -4649,10 +4649,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -4805,10 +4805,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -4978,10 +4978,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -5273,12 +5273,12 @@ "source_ip_cidr": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -5286,12 +5286,12 @@ "ip_cidr": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -5474,12 +5474,12 @@ "additionalProperties": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -5488,12 +5488,12 @@ "default_interface_address": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -5678,6 +5678,18 @@ }, "additionalProperties": false }, + "IPAddress": { + "type": "string", + "pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)(?:%.+)?)$" + }, + "IPAddressOrPrefix": { + "type": "string", + "pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}(?:/(?:3[0-2]|[12]?[0-9]))?|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)(?:/(?:12[0-8]|1[01][0-9]|[1-9]?[0-9])|%.+)?)$" + }, + "IPPrefix": { + "type": "string", + "pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}/(?:3[0-2]|[12]?[0-9])|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)/(?:12[0-8]|1[01][0-9]|[1-9]?[0-9]))$" + }, "Inbound": { "oneOf": [ { @@ -5690,7 +5702,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -5842,7 +5854,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -5941,7 +5953,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -6026,7 +6038,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -6173,7 +6185,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -6417,7 +6429,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -6502,7 +6514,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -6610,7 +6622,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -6680,7 +6692,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -6806,7 +6818,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -6919,7 +6931,7 @@ "const": 5 }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -7010,7 +7022,7 @@ "const": 6 }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -7100,7 +7112,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -7245,10 +7257,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -7352,7 +7364,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -7466,7 +7478,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -7560,7 +7572,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -7707,7 +7719,7 @@ "address": { "anyOf": [ { - "type": "string", + "$ref": "#/$defs/IPPrefix", "examples": [ "172.19.0.1/30", "fdfe:dcba:9876::1/126" @@ -7716,7 +7728,7 @@ { "type": "array", "items": { - "type": "string", + "$ref": "#/$defs/IPPrefix", "examples": [ "172.19.0.1/30", "fdfe:dcba:9876::1/126" @@ -7736,12 +7748,12 @@ "dns_address": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddress" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddress" } } ] @@ -7820,12 +7832,12 @@ "loopback_address": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddress" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddress" } } ] @@ -7836,12 +7848,12 @@ "route_address": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPPrefix" } } ] @@ -7862,12 +7874,12 @@ "route_exclude_address": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPPrefix" } } ] @@ -8084,7 +8096,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -8169,7 +8181,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -8305,10 +8317,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -8693,10 +8705,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -8837,12 +8849,12 @@ "query_client_subnet": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -8976,12 +8988,12 @@ "source_ip_cidr": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -9193,12 +9205,12 @@ "additionalProperties": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -9212,12 +9224,12 @@ "additionalProperties": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -9226,12 +9238,12 @@ "default_interface_address": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -9306,12 +9318,12 @@ "ip_cidr": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -9577,12 +9589,12 @@ "source_ip_cidr": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -9593,12 +9605,12 @@ "ip_cidr": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -9810,12 +9822,12 @@ "additionalProperties": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -9829,12 +9841,12 @@ "additionalProperties": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -9843,12 +9855,12 @@ "default_interface_address": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -10709,18 +10721,18 @@ "routes": { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPPrefix" } }, "dns": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddress" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddress" } } ] @@ -10808,10 +10820,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -10990,10 +11002,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -11104,10 +11116,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -11241,10 +11253,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -11482,10 +11494,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -11743,10 +11755,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -11968,10 +11980,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -12171,10 +12183,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -12312,10 +12324,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -12479,10 +12491,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -12642,10 +12654,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -12824,10 +12836,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -13091,10 +13103,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -13205,10 +13217,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -13337,10 +13349,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -13492,10 +13504,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -13744,10 +13756,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -13905,10 +13917,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -14664,12 +14676,12 @@ "source_ip_cidr": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -14680,12 +14692,12 @@ "ip_cidr": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -14897,12 +14909,12 @@ "additionalProperties": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -14916,12 +14928,12 @@ "additionalProperties": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -14930,12 +14942,12 @@ "default_interface_address": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" }, { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } } ] @@ -15186,10 +15198,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -15462,7 +15474,7 @@ "maximum": 4294967295 }, "client_subnet": { - "type": "string" + "$ref": "#/$defs/IPAddressOrPrefix" } }, "required": [ @@ -15617,7 +15629,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -15748,7 +15760,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -15836,7 +15848,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -15955,10 +15967,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -16137,10 +16149,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -16338,7 +16350,7 @@ "type": "boolean" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -16413,7 +16425,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -16523,7 +16535,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -16654,7 +16666,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -16724,7 +16736,7 @@ "type": "string" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -16813,10 +16825,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -16943,7 +16955,7 @@ ] }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -17022,7 +17034,7 @@ "const": "dynamic" }, "listen": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "listen_port": { "type": "integer", @@ -17106,10 +17118,10 @@ "type": "string" }, "inet4_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "inet6_bind_address": { - "type": "string" + "$ref": "#/$defs/IPAddress" }, "bind_address_no_port": { "type": "boolean" @@ -17567,7 +17579,7 @@ "allowed_ips": { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/IPPrefix" } }, "persistent_keepalive_interval": { diff --git a/option/rule.go b/option/rule.go index 15f86a4d6..c8369f368 100644 --- a/option/rule.go +++ b/option/rule.go @@ -151,9 +151,9 @@ type RawDefaultRule struct { Geosite badoption.Listable[string] `json:"geosite,omitempty" schema:"omit"` SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty" schema:"omit"` GeoIP badoption.Listable[string] `json:"geoip,omitempty" schema:"omit"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty" schema:"prefixable"` SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty" schema:"prefixable"` IPIsPrivate bool `json:"ip_is_private,omitempty"` SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` diff --git a/option/rule_dns.go b/option/rule_dns.go index 9124a587e..3a48b0ce7 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -147,7 +147,7 @@ type RawDefaultDNSRule struct { DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty" schema:"prefixable"` SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` @@ -176,7 +176,7 @@ type RawDefaultDNSRule struct { RuleSet badoption.Listable[string] `json:"rule_set,omitempty" reference:"rule_set"` RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` MatchResponse *DNSRuleMatchResponse `json:"match_response,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty" schema:"prefixable"` IPIsPrivate bool `json:"ip_is_private,omitempty"` IPAcceptAny bool `json:"ip_accept_any,omitempty"` ResponseRcode *DNSRCode `json:"response_rcode,omitempty"` diff --git a/option/rule_set.go b/option/rule_set.go index 305de0dd2..23b3cfe65 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -254,8 +254,8 @@ type DefaultHeadlessRule struct { DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty" schema:"prefixable"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty" schema:"prefixable"` SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` Port badoption.Listable[uint16] `json:"port,omitempty"` diff --git a/schema/generator.go b/schema/generator.go index 357392e47..1a7682585 100644 --- a/schema/generator.go +++ b/schema/generator.go @@ -4,6 +4,7 @@ import ( "context" "encoding" stdjson "encoding/json" + "net/netip" "reflect" "slices" "strconv" @@ -25,6 +26,8 @@ var ( addrType = reflect.TypeFor[badoption.Addr]() prefixType = reflect.TypeFor[badoption.Prefix]() prefixableType = reflect.TypeFor[badoption.Prefixable]() + netipAddrType = reflect.TypeFor[netip.Addr]() + netipPrefixType = reflect.TypeFor[netip.Prefix]() httpHeaderType = reflect.TypeFor[badoption.HTTPHeader]() memoryBytesType = reflect.TypeFor[byteformats.MemoryBytes]() networkBytesCompatType = reflect.TypeFor[byteformats.NetworkBytesCompat]() @@ -77,8 +80,18 @@ func (g *generator) Describe(valueType reflect.Type) (*Node, error) { return g.Define("Duration", func() (*Node, error) { return DurationNode(), nil }) - case addrType, prefixType, prefixableType: - return StringNode(), nil + case addrType, netipAddrType: + return g.Define("IPAddress", func() (*Node, error) { + return IPAddressNode(), nil + }) + case prefixType, netipPrefixType: + return g.Define("IPPrefix", func() (*Node, error) { + return IPPrefixNode(), nil + }) + case prefixableType: + return g.Define("IPAddressOrPrefix", func() (*Node, error) { + return IPAddressOrPrefixNode(), nil + }) case httpHeaderType: return g.Define("HTTPHeader", func() (*Node, error) { return &Node{Type: "object", AdditionalProperties: ListableOf(StringNode())}, nil @@ -208,7 +221,8 @@ func (g *generator) FlattenStruct(node *Node, structType reflect.Type) error { for fieldType.Kind() == reflect.Pointer { fieldType = fieldType.Elem() } - if field.Tag.Get("schema") == "omit" { + schemaTag := field.Tag.Get("schema") + if schemaTag == "omit" { continue } if field.Anonymous && tagName == "" { @@ -227,8 +241,8 @@ func (g *generator) FlattenStruct(node *Node, structType reflect.Type) error { g.path = append(g.path, structType.Name()+"."+tagName) var fieldNode *Node var err error - if enumTag != "" || examplesTag != "" || referenceTag != "" { - fieldNode, err = taggedFieldNode(fieldType, enumTag, examplesTag, referenceTag) + if enumTag != "" || examplesTag != "" || referenceTag != "" || schemaTag != "" { + fieldNode, err = g.taggedFieldNode(fieldType, enumTag, examplesTag, referenceTag, schemaTag) } else { fieldNode, err = g.Describe(fieldType) } @@ -241,7 +255,7 @@ func (g *generator) FlattenStruct(node *Node, structType reflect.Type) error { return nil } -func taggedFieldNode(fieldType reflect.Type, enumTag string, examplesTag string, referenceTag string) (*Node, error) { +func (g *generator) taggedFieldNode(fieldType reflect.Type, enumTag string, examplesTag string, referenceTag string, schemaTag string) (*Node, error) { elementType := fieldType for elementType.Kind() == reflect.Pointer { elementType = elementType.Elem() @@ -255,16 +269,21 @@ func taggedFieldNode(fieldType reflect.Type, enumTag string, examplesTag string, } var element *Node var err error - if enumTag != "" { + switch { + case enumTag != "": element, err = taggedValueNode(elementType, strings.Split(enumTag, ",")) - if err != nil { - return nil, err - } - } else { - element, err = taggedValueNode(elementType, nil) - if err != nil { - return nil, err + case schemaTag == "prefixable": + if elementType.Kind() != reflect.String { + return nil, E.New("prefixable schema tags require a string field, got ", fieldType.String()) } + element, err = g.Describe(prefixableType) + case schemaTag != "": + return nil, E.New("unknown schema tag ", schemaTag, " on ", fieldType.String()) + default: + element, err = g.Describe(elementType) + } + if err != nil { + return nil, err } if examplesTag != "" { examples, parseErr := taggedValues(elementType, strings.Split(examplesTag, ",")) diff --git a/schema/schema.go b/schema/schema.go index 9c879d49d..34c42efcc 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -76,6 +76,39 @@ func DurationNode() *Node { return &Node{Type: "string", Pattern: durationPattern} } +const ( + ipv4Octet = `(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])` + ipv4Address = ipv4Octet + `(?:\.` + ipv4Octet + `){3}` + ipv6Group = `[0-9A-Fa-f]{1,4}` + ipv6Low32Bits = `(?:` + ipv6Group + `:` + ipv6Group + `|` + ipv4Address + `)` + ipv6Address = `(?:` + + `(?:` + ipv6Group + `:){6}` + ipv6Low32Bits + + `|::(?:` + ipv6Group + `:){5}` + ipv6Low32Bits + + `|(?:` + ipv6Group + `)?::(?:` + ipv6Group + `:){4}` + ipv6Low32Bits + + `|(?:(?:` + ipv6Group + `:)?` + ipv6Group + `)?::(?:` + ipv6Group + `:){3}` + ipv6Low32Bits + + `|(?:(?:` + ipv6Group + `:){0,2}` + ipv6Group + `)?::(?:` + ipv6Group + `:){2}` + ipv6Low32Bits + + `|(?:(?:` + ipv6Group + `:){0,3}` + ipv6Group + `)?::` + ipv6Group + `:` + ipv6Low32Bits + + `|(?:(?:` + ipv6Group + `:){0,4}` + ipv6Group + `)?::` + ipv6Low32Bits + + `|(?:(?:` + ipv6Group + `:){0,5}` + ipv6Group + `)?::` + ipv6Group + + `|(?:(?:` + ipv6Group + `:){0,6}` + ipv6Group + `)?::` + + `)` + ipv6Zone = `%.+` + ipv4PrefixBits = `/(?:3[0-2]|[12]?[0-9])` + ipv6PrefixBits = `/(?:12[0-8]|1[01][0-9]|[1-9]?[0-9])` +) + +func IPAddressNode() *Node { + return &Node{Type: "string", Pattern: `^(?:` + ipv4Address + `|` + ipv6Address + `(?:` + ipv6Zone + `)?)$`} +} + +func IPPrefixNode() *Node { + return &Node{Type: "string", Pattern: `^(?:` + ipv4Address + ipv4PrefixBits + `|` + ipv6Address + ipv6PrefixBits + `)$`} +} + +func IPAddressOrPrefixNode() *Node { + return &Node{Type: "string", Pattern: `^(?:` + ipv4Address + `(?:` + ipv4PrefixBits + `)?|` + ipv6Address + `(?:` + ipv6PrefixBits + `|` + ipv6Zone + `)?)$`} +} + func StringEnum(values ...string) *Node { anyValues := make([]any, 0, len(values)) for _, value := range values {