Module: PaprikaClient::Hashing

Defined in:
lib/paprika_client/hashing.rb

Overview

Reproduces the recipe hash that Paprika clients compute for change detection during sync.

The value is SHA-256 of the recipe serialized the way Python's json.dumps(obj, sort_keys=True) does it: keys sorted, ", " between items, ": " between key and value, and non-ASCII escaped as \uXXXX.

The Paprika sync server stores whatever hash a client sends (it does not validate it against a secret algorithm), so any deterministic scheme works for round-tripping; matching the community reference implementation keeps us consistent with other tools.

Class Method Summary collapse

Class Method Details

.digest(attributes) ⇒ Object

Compute the sync hash for a recipe attributes hash. The existing "hash" key (if any) is excluded from the computation.



22
23
24
25
# File 'lib/paprika_client/hashing.rb', line 22

def digest(attributes)
  without_hash = attributes.reject { |k, _| k.to_s == "hash" }
  Digest::SHA256.hexdigest(python_json_dumps(without_hash))
end

.escape_string(str) ⇒ Object



61
62
63
64
65
# File 'lib/paprika_client/hashing.rb', line 61

def escape_string(str)
  str.gsub(/[\\"\x00-\x1f]|[^\x00-\x7f]/) do |ch|
    ESCAPES[ch] || ch.each_char.map { |c| format('\\u%04x', c.ord) }.join
  end
end

.python_json_dumps(obj) ⇒ Object

Serialize a Ruby object the way Python's json.dumps(sort_keys=True) does.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/paprika_client/hashing.rb', line 28

def python_json_dumps(obj)
  case obj
  when Hash
    pairs = obj.keys.sort_by(&:to_s).map do |key|
      "#{python_json_dumps(key.to_s)}: #{python_json_dumps(obj[key])}"
    end
    "{#{pairs.join(", ")}}"
  when Array
    "[#{obj.map { |v| python_json_dumps(v) }.join(", ")}]"
  when String
    %("#{escape_string(obj)}")
  when nil
    "null"
  when true
    "true"
  when false
    "false"
  else
    obj.to_s
  end
end