Merge branch '8784-dir-listings'
[arvados.git] / services / api / lib / serializers.rb
index d7a81e12f9a31db0dc433fc6b9c6dc27a6a1baa2..ec6fe199729cdd663eda72497290a5e269b120cf 100644 (file)
@@ -1,40 +1,64 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 require 'safe_json'
 
-class HashSerializer
+class Serializer
+  class TypeMismatch < ArgumentError
+  end
+
+  def self.dump(val)
+    if !val.is_a?(object_class)
+      raise TypeMismatch.new("cannot serialize #{val.class} as #{object_class}")
+    end
+    SafeJSON.dump(val)
+  end
+
+  def self.legacy_load(s)
+    val = Psych.safe_load(s)
+    if val.is_a? String
+      # If apiserver was downgraded to a YAML-only version after
+      # storing JSON in the database, the old code would have loaded
+      # the JSON document as a plain string, and then YAML-encoded
+      # it when saving it back to the database. It's too late now to
+      # make the old code behave better, but at least we can
+      # gracefully handle the mess it leaves in the database by
+      # double-decoding on the way out.
+      return SafeJSON.load(val)
+    else
+      return val
+    end
+  end
+
   def self.load(s)
     if s.nil?
-      {}
-    elsif s[0] == "{"
+      object_class.new()
+    elsif s[0] == first_json_char
       SafeJSON.load(s)
     elsif s[0..2] == "---"
-      Psych.safe_load(s)
+      legacy_load(s)
     else
       raise "invalid serialized data #{s[0..5].inspect}"
     end
   end
-  def self.dump(h)
-    SafeJSON.dump(h)
+end
+
+class HashSerializer < Serializer
+  def self.first_json_char
+    "{"
   end
+
   def self.object_class
     ::Hash
   end
 end
 
-class ArraySerializer
-  def self.load(s)
-    if s.nil?
-      []
-    elsif s[0] == "["
-      SafeJSON.load(s)
-    elsif s[0..2] == "---"
-      Psych.safe_load(s)
-    else
-      raise "invalid serialized data #{s[0..5].inspect}"
-    end
-  end
-  def self.dump(a)
-    SafeJSON.dump(a)
+class ArraySerializer < Serializer
+  def self.first_json_char
+    "["
   end
+
   def self.object_class
     ::Array
   end