class ActiveRecord::Encryption::MessageSerializer

A message serializer that serializes Messages with JSON.

The generated structure is pretty simple:

{
  p: <payload>,
  h: {
    header1: value1,
    header2: value2,
    ...
  }
}

Both the payload and the header values are encoded with Base64 to prevent JSON parsing errors and encoding issues when storing the resulting serialized data.

Public Instance Methods

binary?() click to toggle source
# File lib/active_record/encryption/message_serializer.rb, line 36
def binary?
  false
end
dump(message) click to toggle source
# File lib/active_record/encryption/message_serializer.rb, line 31
def dump(message)
  raise ActiveRecord::Encryption::Errors::ForbiddenClass unless message.is_a?(ActiveRecord::Encryption::Message)
  JSON.dump message_to_json(message)
end
load(serialized_content) click to toggle source
# File lib/active_record/encryption/message_serializer.rb, line 24
def load(serialized_content)
  data = JSON.parse(serialized_content)
  parse_message(data, 1)
rescue JSON::ParserError
  raise ActiveRecord::Encryption::Errors::Encoding
end

Private Instance Methods

decode_if_needed(value) click to toggle source
# File lib/active_record/encryption/message_serializer.rb, line 85
def decode_if_needed(value)
  if value.is_a?(String)
    ::Base64.strict_decode64(value)
  else
    value
  end
rescue ArgumentError, TypeError
  raise Errors::Encoding
end
encode_if_needed(value) click to toggle source
# File lib/active_record/encryption/message_serializer.rb, line 77
def encode_if_needed(value)
  if value.is_a?(String)
    ::Base64.strict_encode64 value
  else
    value
  end
end
headers_to_json(headers) click to toggle source
# File lib/active_record/encryption/message_serializer.rb, line 71
def headers_to_json(headers)
  headers.transform_values do |value|
    value.is_a?(ActiveRecord::Encryption::Message) ? message_to_json(value) : encode_if_needed(value)
  end
end
message_to_json(message) click to toggle source
# File lib/active_record/encryption/message_serializer.rb, line 64
def message_to_json(message)
  {
    p: encode_if_needed(message.payload),
    h: headers_to_json(message.headers)
  }
end
parse_message(data, level) click to toggle source
# File lib/active_record/encryption/message_serializer.rb, line 41
def parse_message(data, level)
  validate_message_data_format(data, level)
  ActiveRecord::Encryption::Message.new(payload: decode_if_needed(data["p"]), headers: parse_properties(data["h"], level))
end
parse_properties(headers, level) click to toggle source
# File lib/active_record/encryption/message_serializer.rb, line 56
def parse_properties(headers, level)
  ActiveRecord::Encryption::Properties.new.tap do |properties|
    headers&.each do |key, value|
      properties[key] = value.is_a?(Hash) ? parse_message(value, level + 1) : decode_if_needed(value)
    end
  end
end
validate_message_data_format(data, level) click to toggle source
# File lib/active_record/encryption/message_serializer.rb, line 46
def validate_message_data_format(data, level)
  if level > 2
    raise ActiveRecord::Encryption::Errors::Decryption, "More than one level of hash nesting in headers is not supported"
  end

  unless data.is_a?(Hash) && data.has_key?("p")
    raise ActiveRecord::Encryption::Errors::Decryption, "Invalid data format: hash without payload"
  end
end