Module: Ruby::Rego::Builtins::Codecs::JsonSchema::Formats
- Defined in:
- lib/ruby/rego/builtins/codecs/json_schema_formats.rb,
lib/ruby/rego/builtins/codecs/json_schema_email.rb
Overview
The gojsonschema format checkers (format_checkers.go) OPA 1.17 enforces. See the file header.
Defined Under Namespace
Modules: Rfc2047 Classes: MailAddress
Constant Summary collapse
- HOST_LABEL =
gojsonschema’s hostname label, then the dotted regex anchored \A..\z for Go’s whole-text ^..$.
"[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]"- HOSTNAME =
/\A(#{HOST_LABEL})(\.(#{HOST_LABEL}))*\z/- UUID =
/\A[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\z/i- JSON_POINTER =
%r{\A(?:/(?:[^~/]|~0|~1)*)*\z}- RELATIVE_JSON_POINTER =
%r{\A(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)\z}- DATE =
Go time.Parse layouts: hour is “15” (1-2 digits, <=23), minute/second are “04”/”05” (exactly 2); fractional seconds are an optional run of digits after a period OR comma (Go accepts both, per ISO 8601 §5.6); a zone is “Z” or “+hh:mm”/”-hh:mm” where Go range-checks the offset (hour <=24, minute <=60 — wider than the time-of-day fields). The offset hh:mm is captured so it can be range-checked; an absent zone / “Z” leaves those groups nil (->0, which passes). date-time additionally accepts a bare date or bare time (gojsonschema tries five layouts), and full RFC3339 requires an uppercase “T” and a zone.
/\A(\d{4})-(\d{2})-(\d{2})\z/- TIME =
/\A(\d{1,2}):(\d{2}):(\d{2})(?:[.,]\d+)?(?:Z|[+-](\d{2}):(\d{2}))?\z/- RFC3339 =
/\A(\d{4})-(\d{2})-(\d{2})T(\d{1,2}):(\d{2}):(\d{2})(?:[.,]\d+)?(?:Z|[+-](\d{2}):(\d{2}))\z/- URI_TEMPLATE_PATH =
gojsonschema’s uri-template path check, verbatim, compiled with the re2 engine (Go’s regexp) so it is byte-exact and linear (Ruby’s Onigmo would risk backtracking on the nested quantifier).
RE2::Regexp.new("^([^{]*({[^}]*})?)*$", log_errors: false)
Class Method Summary collapse
-
.absolute_uri?(value) ⇒ Boolean
uri / iri: a parseable URL with a non-empty scheme.
-
.date?(value) ⇒ Boolean
-
.email?(value) ⇒ Boolean
email / idn-email: gojsonschema runs both through Go’s net/mail.ParseAddress (idn-email is the same checker – RFC 6532 UTF-8 is allowed in atoms either way).
-
.hostname?(value) ⇒ Boolean
gojsonschema: the hostname regex AND len(input) < 256 (bytes).
-
.ip?(value, separator) ⇒ Boolean
Go net.ParseIP + a separator check.
-
.match?(name, value) ⇒ bool
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength :reek:ControlParameter :reek:TooManyStatements :reek:DuplicateMethodCall.
-
.re_match?(value, regexp) ⇒ Boolean
A Ruby-regex format match, total on binary / invalid-encoding input.
-
.rfc3339?(value) ⇒ Boolean
rubocop:disable Metrics/ParameterLists – named date/time/offset fields read clearer than parts[].
-
.scannable?(value) ⇒ Boolean
A string the Ruby regex engine can scan without raising: valid in its encoding and either pure ASCII or UTF-8 (a binary high-byte string would raise on
match?). -
.time?(value) ⇒ Boolean
-
.uri_reference?(value) ⇒ Boolean
uri-reference / iri-reference: a parseable URL (scheme optional).
-
.uri_template?(value) ⇒ Boolean
uri-template: a parseable URL whose path matches gojsonschema’s template regex.
-
.url_components(value) ⇒ Object
Go net/url.Parse (via Uri::Parser) of a value with no backslash.
-
.valid_clock?(hour, minute, second) ⇒ Boolean
-
.valid_date?(year, month, day) ⇒ Boolean
Proleptic-Gregorian calendar validity (Go’s time package is proleptic Gregorian; Ruby’s default Date switches to Julian before 1582, so pin Date::GREGORIAN).
-
.valid_offset?(offset_hour, offset_minute) ⇒ Boolean
Go time.Parse range-checks the numeric zone offset more loosely than the clock: hour <=24, minute <=60.
-
.with_parts(value, regexp) {|match.captures.map { |group| group.to_s.to_i }| ... } ⇒ Object
Yield the numeric capture groups of
regexpmatched against a scannablevalue(absent optional groups arrive as 0) so the caller can range-check named fields; false if it does not match.
Class Method Details
.absolute_uri?(value) ⇒ Boolean
uri / iri: a parseable URL with a non-empty scheme. :reek:NilCheck
178 179 180 181 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 178 def self.absolute_uri?(value) components = url_components(value) !components.nil? && !components.scheme.to_s.empty? end |
.date?(value) ⇒ Boolean
105 106 107 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 105 def self.date?(value) with_parts(value, DATE) { |year, month, day| valid_date?(year, month, day) } end |
.email?(value) ⇒ Boolean
email / idn-email: gojsonschema runs both through Go’s net/mail.ParseAddress (idn-email is the same checker – RFC 6532 UTF-8 is allowed in atoms either way). See MailAddress (json_schema_email). The scannable? guard ensures the parser only sees valid UTF-8 (matching Go, which rejects invalid-UTF-8 addresses anyway).
203 204 205 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 203 def self.email?(value) scannable?(value) && MailAddress.valid?(value) end |
.hostname?(value) ⇒ Boolean
gojsonschema: the hostname regex AND len(input) < 256 (bytes).
101 102 103 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 101 def self.hostname?(value) value.bytesize < 256 && re_match?(value, HOSTNAME) end |
.ip?(value, separator) ⇒ Boolean
Go net.ParseIP + a separator check. net.ParseIP rejects a zone id (%) and CIDR (/) that Ruby’s IPAddr would otherwise accept; otherwise IPAddr matches net.ParseIP exactly (verified differentially). ipv4 also requires a “.”, ipv6 a “:”. Total: any parse failure -> false.
155 156 157 158 159 160 161 162 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 155 def self.ip?(value, separator) return false if value.include?("%") || value.include?("/") IPAddr.new(value) value.include?(separator) rescue StandardError false end |
.match?(name, value) ⇒ bool
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength :reek:ControlParameter :reek:TooManyStatements :reek:DuplicateMethodCall
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 67 def self.match?(name, value) case name when "hostname" then hostname?(value) when "uuid" then re_match?(value, UUID) when "json-pointer" then re_match?(value, JSON_POINTER) when "relative-json-pointer" then re_match?(value, RELATIVE_JSON_POINTER) when "regex" then scannable?(value) && JsonSchema.re2_valid?(value) when "date" then date?(value) when "time" then time?(value) when "date-time" then date?(value) || time?(value) || rfc3339?(value) when "ipv4" then ip?(value, ".") when "ipv6" then ip?(value, ":") when "uri", "iri" then absolute_uri?(value) when "uri-reference", "iri-reference" then uri_reference?(value) when "uri-template" then uri_template?(value) when "email", "idn-email" then email?(value) else true # idn-hostname / duration / unknown -> annotation only end end |
.re_match?(value, regexp) ⇒ Boolean
A Ruby-regex format match, total on binary / invalid-encoding input.
96 97 98 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 96 def self.re_match?(value, regexp) scannable?(value) && regexp.match?(value) end |
.rfc3339?(value) ⇒ Boolean
rubocop:disable Metrics/ParameterLists – named date/time/offset fields read clearer than parts[]
116 117 118 119 120 121 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 116 def self.rfc3339?(value) with_parts(value, RFC3339) do |year, month, day, hour, minute, second, offset_hour, offset_minute| valid_date?(year, month, day) && valid_clock?(hour, minute, second) && valid_offset?(offset_hour, offset_minute) end end |
.scannable?(value) ⇒ Boolean
A string the Ruby regex engine can scan without raising: valid in its encoding and either pure
ASCII or UTF-8 (a binary high-byte string would raise on match?). Rego strings reaching OPA are
always UTF-8, so a non-scannable value is unreachable in practice; rejecting it keeps totality.
91 92 93 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 91 def self.scannable?(value) value.valid_encoding? && (value.ascii_only? || value.encoding == Encoding::UTF_8) end |
.time?(value) ⇒ Boolean
109 110 111 112 113 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 109 def self.time?(value) with_parts(value, TIME) do |hour, minute, second, offset_hour, offset_minute| valid_clock?(hour, minute, second) && valid_offset?(offset_hour, offset_minute) end end |
.uri_reference?(value) ⇒ Boolean
uri-reference / iri-reference: a parseable URL (scheme optional). :reek:NilCheck
185 186 187 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 185 def self.uri_reference?(value) !url_components(value).nil? end |
.uri_template?(value) ⇒ Boolean
uri-template: a parseable URL whose path matches gojsonschema’s template regex. The path is
percent-DECODED by the parser, so it can hold bytes that are invalid UTF-8 (e.g. %FF); Go’s
regexp engine substitutes U+FFFD for each undecodable byte and keeps matching, whereas re2 won’t
match an undecodable subject — so scrub the path the same way Go’s decoder would first.
:reek:NilCheck
194 195 196 197 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 194 def self.uri_template?(value) components = url_components(value) !components.nil? && URI_TEMPLATE_PATH.match?(components.path.to_s.scrub("\uFFFD")) end |
.url_components(value) ⇒ Object
Go net/url.Parse (via Uri::Parser) of a value with no backslash. nil if it does not parse, is not
scannable (Uri::Parser raises on invalid-UTF-8), or contains a backslash (gojsonschema rejects
\ explicitly, separate from url.Parse). The rescue keeps it total against any other parse error.
:reek:NilCheck
168 169 170 171 172 173 174 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 168 def self.url_components(value) return nil unless scannable?(value) && !value.include?("\\") Uri::Parser.parse(value) rescue StandardError nil end |
.valid_clock?(hour, minute, second) ⇒ Boolean
142 143 144 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 142 def self.valid_clock?(hour, minute, second) hour <= 23 && minute <= 59 && second <= 59 end |
.valid_date?(year, month, day) ⇒ Boolean
Proleptic-Gregorian calendar validity (Go’s time package is proleptic Gregorian; Ruby’s default Date switches to Julian before 1582, so pin Date::GREGORIAN).
138 139 140 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 138 def self.valid_date?(year, month, day) Date.valid_date?(year, month, day, Date::GREGORIAN) end |
.valid_offset?(offset_hour, offset_minute) ⇒ Boolean
Go time.Parse range-checks the numeric zone offset more loosely than the clock: hour <=24, minute <=60. Absent zone / “Z” arrive as 0, which passes.
148 149 150 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 148 def self.valid_offset?(offset_hour, offset_minute) offset_hour <= 24 && offset_minute <= 60 end |
.with_parts(value, regexp) {|match.captures.map { |group| group.to_s.to_i }| ... } ⇒ Object
Yield the numeric capture groups of regexp matched against a scannable value (absent optional
groups arrive as 0) so the caller can range-check named fields; false if it does not match.
:reek:NilCheck
127 128 129 130 131 132 133 134 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_formats.rb', line 127 def self.with_parts(value, regexp) return false unless scannable?(value) match = regexp.match(value) return false if match.nil? yield(*match.captures.map { |group| group.to_s.to_i }) end |