diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1d549b..5709093 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,16 +10,16 @@ jobs: fail-fast: false matrix: platform: - - ubuntu-18.04 + - ubuntu-22.04 runs-on: ${{ matrix.platform }} steps: - name: Check out - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: submodules: recursive - name: Setup Lua - uses: leafo/gh-actions-lua@v8.0.0 + uses: leafo/gh-actions-lua@v10 with: luaVersion: "5.1.5" @@ -29,15 +29,16 @@ jobs: luarocksVersion: "3.8.0" - name: Linux Get dependencies - run: sudo apt install -y build-essential libncurses5-dev libreadline-dev libssl-dev perl + run: | + sudo apt-get update + sudo apt-get install -y build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3-dev - name: Linux Install run: | - wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add - - sudo apt-get -y install software-properties-common - sudo add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" + wget -qO - https://openresty.org/package/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/openresty.gpg + echo "deb [signed-by=/usr/share/keyrings/openresty.gpg] http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list sudo apt-get update - sudo apt-get install openresty + sudo apt-get install -y openresty - name: Linux Script run: | diff --git a/lib/jsonschema.lua b/lib/jsonschema.lua index a59558e..1f69b18 100644 --- a/lib/jsonschema.lua +++ b/lib/jsonschema.lua @@ -586,7 +586,11 @@ local reg_map = { ["email"] = [[^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$]], ["ipv4"] = [[^(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))$]], ["ipv6"] = [[^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$]], - ["hostname"] = [[^[a-zA-Z0-9\-\.]+$]] + ["hostname"] = [[^[a-zA-Z0-9\-\.]+$]], + ["uuid"] = [[^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$]], + ["date"] = [[^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$]], + ["date-time"] = [[^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])[Tt](?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:[Zz]|[+-](?:[01]\d|2[0-3]):[0-5]\d)$]], + ["uri"] = [[^[a-zA-Z][a-zA-Z0-9+\-.]*:[^\s]*$]], } generate_validator = function(ctx, schema) @@ -1123,7 +1127,7 @@ generate_validator = function(ctx, schema) else ctx:stmt(sformat('if type(%s) == "string" and not %s(%s, [[%s]]) then', ctx:param(1), ctx:libfunc('custom.match_pattern'), ctx:param(1), reg_map[schema.format])) end - ctx:stmt(sformat( ' return false, "expect valid %s address but got: " .. %s', schema.format, ctx:param(1))) + ctx:stmt(sformat( ' return false, "expect valid %s format but got: " .. %s', schema.format, ctx:param(1))) ctx:stmt( 'end') end diff --git a/spec/extra/format.json b/spec/extra/format.json index 30c06d3..5040b0c 100644 --- a/spec/extra/format.json +++ b/spec/extra/format.json @@ -107,5 +107,189 @@ "valid": false } ] + }, + { + "description": "validation of UUID", + "schema": {"format": "uuid"}, + "tests": [ + { + "description": "valid UUID", + "data": "550e8400-e29b-41d4-a716-446655440000", + "valid": true + }, + { + "description": "valid UUID with uppercase", + "data": "550E8400-E29B-41D4-A716-446655440000", + "valid": true + }, + { + "description": "invalid UUID: missing hyphens", + "data": "550e8400e29b41d4a716446655440000", + "valid": false + }, + { + "description": "invalid UUID: too short", + "data": "550e8400-e29b-41d4-a716", + "valid": false + }, + { + "description": "invalid UUID: invalid character", + "data": "550e8400-e29b-41d4-a716-44665544000g", + "valid": false + }, + { + "description": "non-string is valid", + "data": 123, + "valid": true + } + ] + }, + { + "description": "validation of date", + "schema": {"format": "date"}, + "tests": [ + { + "description": "valid date", + "data": "2024-01-15", + "valid": true + }, + { + "description": "valid date: last day of month", + "data": "2024-12-31", + "valid": true + }, + { + "description": "invalid date: month 13", + "data": "2024-13-01", + "valid": false + }, + { + "description": "invalid date: day 00", + "data": "2024-01-00", + "valid": false + }, + { + "description": "invalid date: day 32", + "data": "2024-01-32", + "valid": false + }, + { + "description": "invalid date: wrong format", + "data": "01-15-2024", + "valid": false + }, + { + "description": "non-string is valid", + "data": 20240115, + "valid": true + } + ] + }, + { + "description": "validation of date-time", + "schema": {"format": "date-time"}, + "tests": [ + { + "description": "valid date-time with Z", + "data": "2024-01-15T10:30:00Z", + "valid": true + }, + { + "description": "valid date-time with offset", + "data": "2024-01-15T10:30:00+08:00", + "valid": true + }, + { + "description": "valid date-time with negative offset", + "data": "2024-01-15T10:30:00-05:00", + "valid": true + }, + { + "description": "valid date-time with fractional seconds", + "data": "2024-01-15T10:30:00.123Z", + "valid": true + }, + { + "description": "valid date-time with lowercase t and z", + "data": "2024-01-15t10:30:00z", + "valid": true + }, + { + "description": "invalid date-time: missing time", + "data": "2024-01-15", + "valid": false + }, + { + "description": "invalid date-time: missing timezone", + "data": "2024-01-15T10:30:00", + "valid": false + }, + { + "description": "invalid date-time: invalid hour", + "data": "2024-01-15T25:30:00Z", + "valid": false + }, + { + "description": "non-string is valid", + "data": 123, + "valid": true + } + ] + }, + { + "description": "validation of URI", + "schema": {"format": "uri"}, + "tests": [ + { + "description": "valid URI: https", + "data": "https://example.com", + "valid": true + }, + { + "description": "valid URI: with path", + "data": "https://example.com/path/to/resource", + "valid": true + }, + { + "description": "valid URI: mailto", + "data": "mailto:user@example.com", + "valid": true + }, + { + "description": "valid URI: urn", + "data": "urn:isbn:0451450523", + "valid": true + }, + { + "description": "invalid URI: no scheme", + "data": "example.com", + "valid": false + }, + { + "description": "invalid URI: scheme starts with digit", + "data": "1http://example.com", + "valid": false + }, + { + "description": "invalid URI: scheme only no colon", + "data": "http", + "valid": false + }, + { + "description": "valid URI: empty path", + "data": "foo:", + "valid": true + }, + { + "description": "invalid URI: contains whitespace", + "data": "http://exa mple.com", + "valid": false + }, + { + "description": "non-string is valid", + "data": 123, + "valid": true + } + ] } ] \ No newline at end of file