Module: Ruby::Rego::Builtins::Codecs
- Extended by:
- RegistryHelpers
- Defined in:
- lib/ruby/rego/builtins/codecs.rb,
lib/ruby/rego/builtins/codecs/url_query.rb,
lib/ruby/rego/builtins/codecs/json_format.rb,
lib/ruby/rego/builtins/codecs/json_schema.rb,
lib/ruby/rego/builtins/codecs/json_decoder.rb,
lib/ruby/rego/builtins/codecs/json_schema_email.rb,
lib/ruby/rego/builtins/codecs/json_schema_match.rb,
lib/ruby/rego/builtins/codecs/json_schema_formats.rb
Overview
Built-in encoding/decoding helpers.
Defined Under Namespace
Modules: JsonDecoder, JsonSchema
Constant Summary collapse
- CODEC_FUNCTIONS =
{ "json.marshal" => { arity: 1, handler: :json_marshal }, "json.marshal_with_options" => { arity: 2, handler: :json_marshal_with_options }, "json.unmarshal" => { arity: 1, handler: :json_unmarshal }, "json.is_valid" => { arity: 1, handler: :json_is_valid }, "json.verify_schema" => { arity: 1, handler: :json_verify_schema }, "json.match_schema" => { arity: 2, handler: :json_match_schema }, "base64.encode" => { arity: 1, handler: :base64_encode }, "base64.decode" => { arity: 1, handler: :base64_decode }, "base64.is_valid" => { arity: 1, handler: :base64_is_valid }, "base64url.encode" => { arity: 1, handler: :base64url_encode }, "base64url.encode_no_pad" => { arity: 1, handler: :base64url_encode_no_pad }, "base64url.decode" => { arity: 1, handler: :base64url_decode }, "hex.encode" => { arity: 1, handler: :hex_encode }, "hex.decode" => { arity: 1, handler: :hex_decode }, "urlquery.encode" => { arity: 1, handler: :urlquery_encode }, "urlquery.encode_object" => { arity: 1, handler: :urlquery_encode_object }, "urlquery.decode" => { arity: 1, handler: :urlquery_decode }, "urlquery.decode_object" => { arity: 1, handler: :urlquery_decode_object } }.freeze
- MALFORMED_PERCENT =
A
%not followed by two hex digits — a malformed percent-escape that OPA (Go’s url.QueryUnescape) rejects but CGI.unescape would pass through. /%(?![0-9a-fA-F]{2})/- INDENT_SENTINEL =
A NUL indent sentinel for json.marshal_with_options: JSON.generate never emits a literal NUL inside a value (it escapes to \u0000) and escape_html ignores it, so it can carry the structural indent through HTML-escaping and be swapped for the real indent.
"\u0000"
Class Method Summary collapse
-
.base64_decode(value) ⇒ Ruby::Rego::StringValue
-
.base64_encode(value) ⇒ Ruby::Rego::StringValue
-
.base64_is_valid(value) ⇒ Ruby::Rego::BooleanValue
-
.base64url_decode(value) ⇒ Ruby::Rego::StringValue
OPA accepts padded and unpadded URL-safe base64 but rejects the standard-base64 ‘+’/’/’ and non-canonical padding; Base64Url.strict_decode enforces that (shared with io.jwt.decode), and the
decodedblock maps its ArgumentError to undefined. -
.base64url_encode(value) ⇒ Ruby::Rego::StringValue
-
.base64url_encode_no_pad(value) ⇒ Ruby::Rego::StringValue
-
.canonical_json(value) ⇒ String
The exact bytes OPA’s io.jwt.encode_sign signs for a header/payload object — json.marshal’s serialization (sorted keys, sets as sorted arrays, Go HTML escaping of <>& and U+2028/U+2029) as a raw String.
-
.hex_decode(value) ⇒ Ruby::Rego::StringValue
-
.hex_encode(value) ⇒ Ruby::Rego::StringValue
-
.json_is_valid(value) ⇒ Ruby::Rego::BooleanValue
-
.json_marshal(value) ⇒ Ruby::Rego::StringValue
Compact JSON with object keys sorted, sets rendered as sorted arrays, and Go-style HTML escaping, matching OPA’s json.marshal output.
-
.json_marshal_with_options(value, options_value) ⇒ Ruby::Rego::StringValue
Compact or pretty-printed JSON, matching OPA’s json.marshal_with_options.
-
.json_match_schema(document_value, schema_value) ⇒ Ruby::Rego::Value
json.match_schema(document, schema): [match, errors] — whether
document(a JSON string or object/array) validates againstschema. -
.json_unmarshal(value) ⇒ Ruby::Rego::Value
-
.json_verify_schema(value) ⇒ Ruby::Rego::ArrayValue
json.verify_schema(schema): [valid, error] — true/null when
schema(a JSON string or object) is a well-formed JSON schema, else false and an error string. -
.register! ⇒ Ruby::Rego::Builtins::BuiltinRegistry
-
.urlquery_decode(value) ⇒ Ruby::Rego::StringValue
OPA (Go’s url.QueryUnescape) rejects malformed percent-escapes; CGI.unescape passes them through, so they are validated and rejected to match OPA.
-
.urlquery_decode_object(value) ⇒ Ruby::Rego::ObjectValue
Decodes a query string to an object mapping each key to its array of values, matching OPA (Go’s url.ParseQuery).
-
.urlquery_encode(value) ⇒ Ruby::Rego::StringValue
-
.urlquery_encode_object(value) ⇒ Ruby::Rego::StringValue
Encodes an object as a query string, matching OPA (Go’s url.Values.Encode): keys are sorted; a string value emits one pair, an array/set value one pair per element (arrays keep order; sets are sorted and de-duplicated).
Methods included from RegistryHelpers
Class Method Details
.base64_decode(value) ⇒ Ruby::Rego::StringValue
229 230 231 232 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 229 def self.base64_decode(value) string = string_arg(value, "base64.decode") decoded("base64.decode") { StringValue.new(Base64.strict_decode64(string)) } end |
.base64_encode(value) ⇒ Ruby::Rego::StringValue
223 224 225 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 223 def self.base64_encode(value) StringValue.new(Base64.strict_encode64(string_arg(value, "base64.encode"))) end |
.base64_is_valid(value) ⇒ Ruby::Rego::BooleanValue
236 237 238 239 240 241 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 236 def self.base64_is_valid(value) Base64.strict_decode64(string_arg(value, "base64.is_valid")) BooleanValue.new(true) rescue ArgumentError BooleanValue.new(false) end |
.base64url_decode(value) ⇒ Ruby::Rego::StringValue
OPA accepts padded and unpadded URL-safe base64 but rejects the standard-base64 ‘+’/’/’ and
non-canonical padding; Base64Url.strict_decode enforces that (shared with io.jwt.decode), and
the decoded block maps its ArgumentError to undefined.
255 256 257 258 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 255 def self.base64url_decode(value) string = string_arg(value, "base64url.decode") decoded("base64url.decode") { StringValue.new(Base64Url.strict_decode(string)) } end |
.base64url_encode(value) ⇒ Ruby::Rego::StringValue
245 246 247 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 245 def self.base64url_encode(value) StringValue.new(Base64.urlsafe_encode64(string_arg(value, "base64url.encode"))) end |
.base64url_encode_no_pad(value) ⇒ Ruby::Rego::StringValue
299 300 301 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 299 def self.base64url_encode_no_pad(value) StringValue.new(Base64.urlsafe_encode64(string_arg(value, "base64url.encode_no_pad"), padding: false)) end |
.canonical_json(value) ⇒ String
The exact bytes OPA’s io.jwt.encode_sign signs for a header/payload object — json.marshal’s serialization (sorted keys, sets as sorted arrays, Go HTML escaping of <>& and U+2028/U+2029) as a raw String. Raises BuiltinArgumentError (-> undefined) on a non-marshalable value.
Rego numbers (Number / Integer) serialise byte-exactly with OPA: a Number emits its canonical
text (1.50 stays 1.50, 1e308 stays 1e308) and integers are exact via Bignum. A Float
only reaches here from a not-yet-migrated builtin output, where it falls back to Ruby’s
Float#to_s (the residual JSON-number divergence tracked for the builtin number sweep); a
non-finite Float raises JSON::JSONError and is mapped to undefined by the rescue below.
77 78 79 80 81 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 77 def self.canonical_json(value) escape_html(JSON.generate(jsonify(value.to_ruby))) rescue JSON::JSONError, ArgumentError => e raise_marshal_error(e, "json.marshal") end |
.hex_decode(value) ⇒ Ruby::Rego::StringValue
268 269 270 271 272 273 274 275 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 268 def self.hex_decode(value) string = string_arg(value, "hex.decode") decoded("hex.decode") do raise ArgumentError, "invalid hex string" unless string.match?(/\A(?:[0-9a-fA-F]{2})*\z/) StringValue.new([string].pack("H*")) end end |
.hex_encode(value) ⇒ Ruby::Rego::StringValue
262 263 264 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 262 def self.hex_encode(value) StringValue.new(string_arg(value, "hex.encode").unpack1("H*").to_s) end |
.json_is_valid(value) ⇒ Ruby::Rego::BooleanValue
195 196 197 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 195 def self.json_is_valid(value) BooleanValue.new(JsonDecoder.valid?(string_arg(value, "json.is_valid"))) end |
.json_marshal(value) ⇒ Ruby::Rego::StringValue
Compact JSON with object keys sorted, sets rendered as sorted arrays, and Go-style HTML escaping, matching OPA’s json.marshal output.
88 89 90 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 88 def self.json_marshal(value) StringValue.new(canonical_json(value)) end |
.json_marshal_with_options(value, options_value) ⇒ Ruby::Rego::StringValue
Compact or pretty-printed JSON, matching OPA’s json.marshal_with_options. The options
object takes prefix and indent strings and a pretty boolean; pretty-printing is
enabled by pretty: true, or — when pretty is absent — implicitly by supplying a
prefix or indent. Pretty output uses Go’s json.MarshalIndent indent-per-depth layout;
OPA then prepends the prefix to every line, including the first (MarshalIndent itself
omits it on line one). A non-object options argument, an unknown option key, a
wrongly-typed option value, or an unmarshalable document yields undefined.
:reek:TooManyStatements
104 105 106 107 108 109 110 111 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 104 def self.(value, ) prefix, indent, pretty = () ruby = jsonify(value.to_ruby) output = pretty ? pretty_json(ruby, prefix, indent) : escape_html(JSON.generate(ruby)) StringValue.new(output) rescue JSON::JSONError, ArgumentError => e raise_marshal_error(e, "json.marshal_with_options") end |
.json_match_schema(document_value, schema_value) ⇒ Ruby::Rego::Value
json.match_schema(document, schema): [match, errors] — whether document (a JSON string or
object/array) validates against schema. Undefined when an argument is unusable or the schema is
not well-formed, matching OPA’s gojsonschema. The boolean match is byte-exact; the errors array
is best-effort (presence only). See Codecs::JsonSchema.match.
216 217 218 219 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 216 def self.json_match_schema(document_value, schema_value) result = JsonSchema.match(document_value, schema_value) result == :undefined ? UndefinedValue.new : Value.from_ruby(result) end |
.json_unmarshal(value) ⇒ Ruby::Rego::Value
188 189 190 191 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 188 def self.json_unmarshal(value) string = string_arg(value, "json.unmarshal") decoded("json.unmarshal") { Value.from_ruby(JsonDecoder.parse(string)) } end |
.json_verify_schema(value) ⇒ Ruby::Rego::ArrayValue
json.verify_schema(schema): [valid, error] — true/null when schema (a JSON string or object) is
a well-formed JSON schema, else false and an error string. Matches OPA’s gojsonschema on the
boolean; the error wording is best-effort (a documented divergence). See Codecs::JsonSchema.
204 205 206 207 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 204 def self.json_verify_schema(value) valid, error = JsonSchema.verify(value) Value.from_ruby([valid, error]) end |
.register! ⇒ Ruby::Rego::Builtins::BuiltinRegistry
57 58 59 60 61 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 57 def self.register! registry = BuiltinRegistry.instance register_configured_functions(registry, CODEC_FUNCTIONS) registry end |
.urlquery_decode(value) ⇒ Ruby::Rego::StringValue
OPA (Go’s url.QueryUnescape) rejects malformed percent-escapes; CGI.unescape passes them through, so they are validated and rejected to match OPA.
288 289 290 291 292 293 294 295 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 288 def self.urlquery_decode(value) string = string_arg(value, "urlquery.decode") decoded("urlquery.decode") do raise ArgumentError, "invalid percent-encoding" if string.match?(MALFORMED_PERCENT) StringValue.new(CGI.unescape(string)) end end |
.urlquery_decode_object(value) ⇒ Ruby::Rego::ObjectValue
Decodes a query string to an object mapping each key to its array of values,
matching OPA (Go’s url.ParseQuery). Reuses urlquery.decode’s percent validation
and unescaping; a malformed percent-escape (in any key or value) yields undefined,
as does a literal ; — Go’s ParseQuery rejects it as a separator (a ; in a value
must be percent-encoded).
90 91 92 93 94 95 96 97 98 |
# File 'lib/ruby/rego/builtins/codecs/url_query.rb', line 90 def self.urlquery_decode_object(value) string = string_arg(value, "urlquery.decode_object") decoded("urlquery.decode_object") do raise ArgumentError, "invalid percent-encoding" if string.match?(MALFORMED_PERCENT) raise ArgumentError, "semicolon separator" if string.include?(";") ObjectValue.new(grouped_query(string)) end end |
.urlquery_encode(value) ⇒ Ruby::Rego::StringValue
279 280 281 |
# File 'lib/ruby/rego/builtins/codecs.rb', line 279 def self.urlquery_encode(value) StringValue.new(CGI.escape(string_arg(value, "urlquery.encode"))) end |
.urlquery_encode_object(value) ⇒ Ruby::Rego::StringValue
Encodes an object as a query string, matching OPA (Go’s url.Values.Encode): keys are sorted; a string value emits one pair, an array/set value one pair per element (arrays keep order; sets are sorted and de-duplicated). A non-object, a non-string key, or a value that is not a string or string array/set yields undefined.
15 16 17 18 19 20 21 22 |
# File 'lib/ruby/rego/builtins/codecs/url_query.rb', line 15 def self.urlquery_encode_object(value) Base.assert_type(value, expected: ObjectValue, context: "urlquery.encode_object") StringValue.new(query_pairs(value.value).join("&")) rescue EncodingError # A non-ASCII-compatible key/value (Ruby API only) makes CGI.escape raise; treat # it as an invalid object rather than letting it escape. raise_invalid_object end |