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

dump(message) click to toggle source
# File lib/active_record/encryption/message_serializer.rb, line 29
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 22
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 79
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 71
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 65
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 58
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 35
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 50
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 40
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