[storage][volume_image]: Implement Serialization.

Implement and test serialization for VolumeImage.

Bug: 48256
TEST=storage-volume-image-tests

Change-Id: I6c1d66b7e9626e9a872794a2d1536bec8012c076
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/372553
Reviewed-by: Richard Chen <tianruichen@google.com>
Testability-Review: Gianfranco Valentino <gevalentino@google.com>
Commit-Queue: Gianfranco Valentino <gevalentino@google.com>
diff --git a/src/storage/volume_image/BUILD.gn b/src/storage/volume_image/BUILD.gn
index 0862b7a..7aa1b1c 100644
--- a/src/storage/volume_image/BUILD.gn
+++ b/src/storage/volume_image/BUILD.gn
@@ -17,6 +17,7 @@
   ]
   public_deps = [
     "utils:guid",
+    "//third_party/rapidjson",
     "//zircon/public/lib/fbl",
     "//zircon/public/lib/fit",
   ]
@@ -30,7 +31,10 @@
   ]
   deps = [
     ":volume-descriptor",
+    "serialization:schema",
     "//src/lib/fxl/test:gtest_main",
+    "//src/lib/json_parser",
+    "//third_party/googletest:gmock",
     "//third_party/googletest:gtest",
     "//zircon/public/lib/fdio",
   ]
@@ -39,6 +43,7 @@
 unittest_package("storage-volume-image-tests") {
   deps = [
     ":volume-descriptor-test",
+    "serialization:serialization-test",
     "utils:utils-test",
   ]
 
@@ -51,6 +56,18 @@
       name = "utils-test"
       environments = basic_envs
     },
+    {
+      name = "serialization-test"
+      environments = basic_envs
+    },
+  ]
+
+  resources = [
+    {
+      path = rebase_path(
+              "//src/storage/volume_image/serialization/volume_descriptor.schema.json")
+      dest = "schema/volume_descriptor.schema.json"
+    },
   ]
 }
 
diff --git a/src/storage/volume_image/serialization/BUILD.gn b/src/storage/volume_image/serialization/BUILD.gn
new file mode 100644
index 0000000..5eee566
--- /dev/null
+++ b/src/storage/volume_image/serialization/BUILD.gn
@@ -0,0 +1,42 @@
+# Copyright 2020 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file
+
+config("schema_path") {
+  if (!is_fuchsia) {
+    defines = [ "STORAGE_VOLUME_IMAGE_SCHEMA_PATH=\"schema/\"" ]
+  } else {
+    defines = [ "STORAGE_VOLUME_IMAGE_SCHEMA_PATH=\"pkg/data/schema/\"" ]
+  }
+}
+
+copy("schema_json") {
+  sources = [ "volume_descriptor.schema.json" ]
+  outputs = [ "$root_out_dir/schema/{{source_file_part}}" ]
+}
+
+source_set("schema") {
+  sources = [ "schema.cc" ]
+  public = [ "schema.h" ]
+
+  configs += [ ":schema_path" ]
+
+  if (!is_fuchsia) {
+    deps += [ ":schema_json" ]
+    metadata = {
+      runtime_deps = get_target_outputs(":copy")
+    }
+  }
+}
+
+executable("serialization-test") {
+  testonly = true
+  sources = [ "schema_test.cc" ]
+  deps = [
+    ":schema",
+    "//src/lib/fxl/test:gtest_main",
+    "//src/lib/json_parser",
+    "//third_party/googletest:gtest",
+    "//zircon/public/lib/fdio",
+  ]
+}
diff --git a/src/storage/volume_image/serialization/schema.cc b/src/storage/volume_image/serialization/schema.cc
new file mode 100644
index 0000000..fd5d49a
--- /dev/null
+++ b/src/storage/volume_image/serialization/schema.cc
@@ -0,0 +1,29 @@
+// Copyright 2020 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/storage/volume_image/serialization/schema.h"
+
+#include <fstream>
+#include <iterator>
+#include <streambuf>
+#include <string>
+
+namespace storage::volume_image {
+
+// Path to where the schema leaves. Left to be injected by a compile time definition.
+static constexpr char kPath[] = STORAGE_VOLUME_IMAGE_SCHEMA_PATH;
+
+std::string GetSchema(Schema schema) {
+  std::ifstream file;
+
+  switch (schema) {
+    case Schema::kVolumeDescriptor:
+      file.open(std::string(kPath).append("volume_descriptor.schema.json"));
+      return std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+    default:
+      return std::string();
+  }
+}
+
+}  // namespace storage::volume_image
diff --git a/src/storage/volume_image/serialization/schema.h b/src/storage/volume_image/serialization/schema.h
new file mode 100644
index 0000000..8352576
--- /dev/null
+++ b/src/storage/volume_image/serialization/schema.h
@@ -0,0 +1,24 @@
+// Copyright 2020 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_STORAGE_VOLUME_IMAGE_SERIALIZATION_SCHEMA_H_
+#define SRC_STORAGE_VOLUME_IMAGE_SERIALIZATION_SCHEMA_H_
+
+#include <string>
+
+namespace storage::volume_image {
+
+// Defines available schemas.
+enum Schema {
+  kVolumeDescriptor,
+};
+
+// Returns a string with the respective schema.
+//
+// On error, returns an empty string.
+std::string GetSchema(Schema schema);
+
+}  // namespace storage::volume_image
+
+#endif  // SRC_STORAGE_VOLUME_IMAGE_SERIALIZATION_SCHEMA_H_
diff --git a/src/storage/volume_image/serialization/schema_test.cc b/src/storage/volume_image/serialization/schema_test.cc
new file mode 100644
index 0000000..537104c
--- /dev/null
+++ b/src/storage/volume_image/serialization/schema_test.cc
@@ -0,0 +1,24 @@
+// Copyright 2020 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "schema.h"
+
+#include <gtest/gtest.h>
+
+#include "rapidjson/schema.h"
+#include "src/lib/json_parser/json_parser.h"
+
+namespace storage::volume_image {
+namespace {
+
+TEST(SchemaTest, VolumeDescriptorSchemaIsValid) {
+  auto schema_json = GetSchema(Schema::kVolumeDescriptor);
+
+  json_parser::JSONParser parser;
+  auto document = parser.ParseFromString(schema_json, "volume_descriptor.schema.json");
+  ASSERT_FALSE(parser.HasError()) << parser.error_str();
+}
+
+}  // namespace
+}  // namespace storage::volume_image
diff --git a/src/storage/volume_image/serialization/volume_descriptor.schema.json b/src/storage/volume_image/serialization/volume_descriptor.schema.json
index 5cc7e47..a681d25 100644
--- a/src/storage/volume_image/serialization/volume_descriptor.schema.json
+++ b/src/storage/volume_image/serialization/volume_descriptor.schema.json
@@ -54,13 +54,14 @@
                 },
                 "options": {
                     "type": "array",
-                    "items": {
+                    "contains": {
                         "type": "integer",
                         "minimum": 0
                     },
                     "uniqueItems": true
                 }
             },
+            "additionalProperties": false,
             "required": [
                 "magic",
                 "instance_guid",
@@ -70,4 +71,6 @@
             ]
         }
     },
+    "type": "object",
+    "$ref": "#/definitions/volume_descriptor"
 }
\ No newline at end of file
diff --git a/src/storage/volume_image/utils/guid.cc b/src/storage/volume_image/utils/guid.cc
index ca3124f..1b472a9 100644
--- a/src/storage/volume_image/utils/guid.cc
+++ b/src/storage/volume_image/utils/guid.cc
@@ -107,7 +107,9 @@
 
 fit::result<std::string, std::string> Guid::ToString(fbl::Span<const uint8_t> guid) {
   if (guid.size() != kGuidLength) {
-    return fit::error("Input GUID size must be equal to |kGuidLength|.\n");
+    std::string error = "Input GUID size must be equal to |kGuidLength|. Input Size: ";
+    error.append(std::to_string(guid.size())).append(".\n");
+    return fit::error(error);
   }
   std::array<char, kGuidStrLength> out_guid;
 
@@ -135,7 +137,9 @@
 fit::result<std::array<uint8_t, kGuidLength>, std::string> Guid::FromString(
     fbl::Span<const char> guid) {
   if (guid.size() != kGuidStrLength) {
-    return fit::error("Input GUID size must be equal to |kGuidStrLength|.\n");
+    std::string error = "Input GUID size must be equal to |kGuidStrLength|. Input Size: ";
+    error.append(std::to_string(guid.size())).append(".\n");
+    return fit::error(error);
   }
   std::array<uint8_t, kGuidLength> out_guid;
 
diff --git a/src/storage/volume_image/volume_descriptor.cc b/src/storage/volume_image/volume_descriptor.cc
index fdce49f..2e936d0 100644
--- a/src/storage/volume_image/volume_descriptor.cc
+++ b/src/storage/volume_image/volume_descriptor.cc
@@ -4,4 +4,158 @@
 
 #include "src/storage/volume_image/volume_descriptor.h"
 
-namespace storage::volume_image {}
+#include <algorithm>
+#include <array>
+#include <cstdlib>
+#include <sstream>
+#include <string_view>
+#include <vector>
+
+#include "rapidjson/document.h"
+#include "rapidjson/error/en.h"
+#include "rapidjson/error/error.h"
+#include "rapidjson/stringbuffer.h"
+#include "rapidjson/writer.h"
+#include "src/storage/volume_image/options.h"
+#include "src/storage/volume_image/utils/guid.h"
+
+namespace storage::volume_image {
+
+fit::result<VolumeDescriptor, std::string> VolumeDescriptor::Deserialize(
+    fbl::Span<const uint8_t> serialized) {
+  rapidjson::Document document;
+  rapidjson::ParseResult result =
+      document.Parse(reinterpret_cast<const char*>(serialized.data()), serialized.size());
+
+  if (result.IsError()) {
+    std::ostringstream error;
+    error << "Error parsing serialized VolumeDescriptor. "
+          << rapidjson::GetParseError_En(result.Code()) << std::endl;
+    return fit::error(error.str());
+  }
+
+  uint64_t magic = document["magic"].GetUint64();
+  if (magic != kMagic) {
+    return fit::error("Invalid Magic\n");
+  }
+
+  VolumeDescriptor descriptor = {};
+  const std::string& instance_guid = document["instance_guid"].GetString();
+  // The stringified version includes 4 Hyphens.
+  if (instance_guid.length() != kGuidStrLength) {
+    return fit::error("instance_guid length must be 36 bytes.\n");
+  }
+  auto instance_bytes = Guid::FromString(instance_guid);
+  if (instance_bytes.is_error()) {
+    return instance_bytes.take_error_result();
+  }
+  descriptor.instance = instance_bytes.take_value();
+
+  const std::string& type_guid = document["type_guid"].GetString();
+  // The stringified version includes 4 Hyphens.
+  if (type_guid.length() != kGuidStrLength) {
+    return fit::error("type_guid length must be 36 bytes.\n");
+  }
+
+  auto type_bytes = Guid::FromString(type_guid);
+  if (type_bytes.is_error()) {
+    return type_bytes.take_error_result();
+  }
+  descriptor.type = type_bytes.take_value();
+
+  const std::string& name = document["name"].GetString();
+  memcpy(descriptor.name.data(), name.c_str(), name.length());
+
+  descriptor.block_size = document["block_size"].GetUint64();
+  auto& compression = descriptor.compression;
+  auto compression_enum =
+      StringAsEnum<CompressionSchema>(document["compression_schema"].GetString());
+  if (compression_enum.is_error()) {
+    return compression_enum.take_error_result();
+  }
+  compression.schema = compression_enum.take_value();
+
+  if (document.HasMember("compression_options")) {
+    const auto& option_map = document["compression_options"].GetObject();
+    for (auto& option : option_map) {
+      compression.options[option.name.GetString()] = option.value.GetUint64();
+    }
+  }
+  auto encryption_enum = StringAsEnum<EncryptionType>(document["encryption_type"].GetString());
+  if (encryption_enum.is_error()) {
+    return encryption_enum.take_error_result();
+  }
+  descriptor.encryption = encryption_enum.take_value();
+
+  if (document.HasMember("options")) {
+    const auto& option_set = document["options"].GetArray();
+    for (auto& option : option_set) {
+      auto option_enum = StringAsEnum<Option>(option.GetString());
+      if (option_enum.is_error()) {
+        return option_enum.take_error_result();
+      }
+      descriptor.options.insert(option_enum.take_value());
+    }
+  }
+
+  return fit::ok(descriptor);
+}
+
+fit::result<std::vector<uint8_t>, std::string> VolumeDescriptor::Serialize() const {
+  rapidjson::Document document;
+  document.SetObject();
+
+  document.AddMember("magic", kMagic, document.GetAllocator());
+  auto instance_str = Guid::ToString(instance);
+  if (instance_str.is_error()) {
+    return instance_str.take_error_result();
+  }
+  document.AddMember("instance_guid", instance_str.take_value(), document.GetAllocator());
+
+  auto type_str = Guid::ToString(type);
+  if (type_str.is_error()) {
+    return type_str.take_error_result();
+  }
+  document.AddMember("type_guid", type_str.take_value(), document.GetAllocator());
+  document.AddMember("name", std::string(reinterpret_cast<const char*>(name.data())),
+                     document.GetAllocator());
+  document.AddMember("block_size", block_size, document.GetAllocator());
+  document.AddMember("encryption_type", EnumAsString(encryption), document.GetAllocator());
+  document.AddMember("compression_schema", EnumAsString(compression.schema),
+                     document.GetAllocator());
+
+  if (!compression.options.empty()) {
+    rapidjson::Value option_map;
+    option_map.SetObject();
+    for (const auto& option : compression.options) {
+      rapidjson::Value key(option.first.c_str(), document.GetAllocator());
+      rapidjson::Value value(option.second);
+      option_map.AddMember(key, value, document.GetAllocator());
+    }
+    document.AddMember("compression_options", option_map, document.GetAllocator());
+  }
+
+  if (!options.empty()) {
+    rapidjson::Value option_set;
+    option_set.SetArray();
+    for (const auto& option : options) {
+      rapidjson::Value value(EnumAsString(option).c_str(), document.GetAllocator());
+      option_set.PushBack(value, document.GetAllocator());
+    }
+    document.AddMember("options", option_set, document.GetAllocator());
+  }
+
+  rapidjson::StringBuffer buffer;
+  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+  if (!document.Accept(writer)) {
+    return fit::error("Failed to obtain string representation of VolumeDescriptor.\n");
+  }
+
+  const auto* serialized_content = reinterpret_cast<const uint8_t*>(buffer.GetString());
+  std::vector<uint8_t> data(serialized_content, serialized_content + buffer.GetLength());
+  data.push_back('\0');
+
+  return fit::ok(data);
+}
+
+}  // namespace storage::volume_image
diff --git a/src/storage/volume_image/volume_descriptor.h b/src/storage/volume_image/volume_descriptor.h
index abfef65..a7910e5 100644
--- a/src/storage/volume_image/volume_descriptor.h
+++ b/src/storage/volume_image/volume_descriptor.h
@@ -14,10 +14,10 @@
 
 #include "src/storage/volume_image/block_io.h"
 #include "src/storage/volume_image/options.h"
+#include "src/storage/volume_image/utils/guid.h"
 
 namespace storage::volume_image {
 
-constexpr uint64_t kGuidLength = 16;
 constexpr uint64_t kNameLength = 40;
 
 // Metadata describing the block image to be generated.
@@ -25,7 +25,14 @@
   static constexpr uint64_t kMagic = 0xB10C14;
 
   // On success returns the VolumeDescriptor with the deserialized contents of |serialized|.
-  static fit::result<VolumeDescriptor, std::string> Deserialize(fbl::Span<uint8_t> serialized);
+  static fit::result<VolumeDescriptor, std::string> Deserialize(
+      fbl::Span<const uint8_t> serialized);
+
+  // On success returns the VolumeDescriptor with the deserialized contents of |serialized|.
+  static fit::result<VolumeDescriptor, std::string> Deserialize(fbl::Span<const char> serialized) {
+    return Deserialize(fbl::Span<const uint8_t>(reinterpret_cast<const uint8_t*>(serialized.data()),
+                                                serialized.size() * sizeof(char)));
+  }
 
   // Returns a byte vector containing the serialized version data.
   // The serialization is meant to be human readable.
diff --git a/src/storage/volume_image/volume_descriptor_test.cc b/src/storage/volume_image/volume_descriptor_test.cc
index 4329a80..224fce0 100644
--- a/src/storage/volume_image/volume_descriptor_test.cc
+++ b/src/storage/volume_image/volume_descriptor_test.cc
@@ -4,16 +4,175 @@
 
 #include "src/storage/volume_image/volume_descriptor.h"
 
-#include <gtest/gtest.h>
+#include <lib/fit/function.h>
+
+#include <limits>
+#include <string_view>
+#include <type_traits>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "rapidjson/document.h"
+#include "rapidjson/schema.h"
+#include "rapidjson/stringbuffer.h"
+#include "rapidjson/writer.h"
+#include "src/lib/fxl/strings/string_view.h"
+#include "src/lib/json_parser/json_parser.h"
+#include "src/lib/json_parser/rapidjson_validation.h"
+#include "src/storage/volume_image/options.h"
+#include "src/storage/volume_image/serialization/schema.h"
 
 namespace storage::volume_image {
 namespace {
 
-TEST(VolumeDescriptorTest, SerializeReturnsValidData) {}
+TEST(VolumeDescriptorTest, SerializeReturnsSchemaValidData) {
+  VolumeDescriptor descriptor = {};
+  descriptor.compression.schema = CompressionSchema::kLz4;
+  descriptor.compression.options = {{"option_1", 1}};
+  descriptor.options = {Option::kNone};
+  descriptor.encryption = EncryptionType::kZxcrypt;
+  auto schema_json = GetSchema(Schema::kVolumeDescriptor);
 
-TEST(VolumeDescriptorTest, DeserializeFromValidDataReturns) {}
+  json_parser::JSONParser parser;
+  auto result = descriptor.Serialize();
+  ASSERT_TRUE(result.is_ok()) << result.error();
+  auto value = result.take_value();
+  auto document =
+      parser.ParseFromString(std::string(value.begin(), value.end()), "serialized.json");
+  ASSERT_FALSE(parser.HasError()) << parser.error_str();
+  std::unique_ptr<rapidjson::SchemaDocument> schema = json_parser::InitSchema(schema_json);
+  EXPECT_TRUE(json_parser::ValidateSchema(document, *schema, "VolumeDescriptor::Serialize output"));
+}
 
-TEST(VolumeDescriptorTest, DeserializeWithInvalidDataReturnsError) {}
+std::string GetSerializedJson(fit::function<void(rapidjson::Document*)> mutator = nullptr) {
+  // A Valid JSON being serialized.
+  static constexpr std::string_view kSerializedVolumeDescriptor = R"(
+    {
+      "magic": 11602964,
+      "instance_guid": "04030201-0605-0807-1009-111213141516",
+      "type_guid": "A4A3A2A1-B6B5-C8C7-D0D1-E0E1E2E3E4E5",
+      "name": "i-have-a-name",
+      "block_size": 512,
+      "encryption_type": "ENCRYPTION_TYPE_ZXCRYPT",
+      "compression_schema": "COMPRESSION_SCHEMA_LZ4",
+      "compression_options": {
+        "random_option": 24,
+        "random_option_2": 25
+      },
+      "options" : [
+        "OPTION_NONE",
+        "OPTION_EMPTY"
+      ]
+    }
+  )";
+  json_parser::JSONParser parser;
+  rapidjson::Document parsed_document = parser.ParseFromString(kSerializedVolumeDescriptor.data(),
+                                                               "serialized_volume_descriptor.json");
+  ZX_ASSERT_MSG(!parser.HasError(), "%s\n", parser.error_str().c_str());
+  if (mutator != nullptr) {
+    mutator(&parsed_document);
+  }
+  rapidjson::StringBuffer buffer;
+  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+  parsed_document.Accept(writer);
+  return buffer.GetString();
+}
+
+TEST(VolumeDescriptorTest, DeserializeSerializedDataIsOk) {
+  const auto deserialized_result = VolumeDescriptor::Deserialize(GetSerializedJson());
+  ASSERT_TRUE(deserialized_result.is_ok());
+
+  const auto serialized_result = deserialized_result.value().Serialize();
+  ASSERT_TRUE(serialized_result.is_ok());
+
+  const auto redeserialized_result = VolumeDescriptor::Deserialize(serialized_result.value());
+  ASSERT_TRUE(redeserialized_result.is_ok());
+
+  const auto& deserialized_1 = deserialized_result.value();
+  const auto& deserialized_2 = deserialized_result.value();
+
+  ASSERT_EQ(deserialized_1.type, deserialized_2.type);
+  ASSERT_EQ(deserialized_1.block_size, deserialized_2.block_size);
+  ASSERT_EQ(deserialized_1.instance, deserialized_2.instance);
+  ASSERT_EQ(deserialized_1.name, deserialized_2.name);
+  ASSERT_EQ(deserialized_1.compression.schema, deserialized_2.compression.schema);
+  ASSERT_EQ(deserialized_1.compression.options, deserialized_2.compression.options);
+  ASSERT_EQ(deserialized_1.encryption, deserialized_2.encryption);
+  ASSERT_EQ(deserialized_1.options, deserialized_2.options);
+}
+
+TEST(VolumeDescriptorTest, DeserializeFromValidDataReturnsVolumeDescriptor) {
+  constexpr std::string_view kTypeGuid = "A4A3A2A1-B6B5-C8C7-D0D1-E0E1E2E3E4E5";
+  constexpr std::string_view kInstanceGuid = "04030201-0605-0807-1009-111213141516";
+  constexpr std::string_view kName = "i-have-a-name";
+  const std::string kSerializedVolumeDescriptor = GetSerializedJson();
+
+  auto descriptor_result = VolumeDescriptor::Deserialize(kSerializedVolumeDescriptor);
+  ASSERT_TRUE(descriptor_result.is_ok()) << descriptor_result.take_error();
+  auto descriptor = descriptor_result.take_value();
+  auto expected_type_guid = Guid::FromString(kTypeGuid);
+  auto expected_instance_guid = Guid::FromString(kInstanceGuid);
+  ASSERT_TRUE(expected_type_guid.is_ok());
+  ASSERT_TRUE(expected_instance_guid.is_ok());
+
+  EXPECT_EQ(expected_type_guid.value(), descriptor.type);
+  EXPECT_EQ(expected_instance_guid.value(), descriptor.instance);
+  EXPECT_EQ(kName, std::string(reinterpret_cast<const char*>(descriptor.name.data())));
+  EXPECT_EQ(512u, descriptor.block_size);
+  EXPECT_EQ(EncryptionType::kZxcrypt, descriptor.encryption);
+  EXPECT_EQ(CompressionSchema::kLz4, descriptor.compression.schema);
+  std::map<std::string, uint64_t> kExpectedCompressionOptions = {{"random_option", 24},
+                                                                 {"random_option_2", 25}};
+  EXPECT_THAT(descriptor.compression.options,
+              ::testing::UnorderedElementsAreArray(kExpectedCompressionOptions));
+  EXPECT_THAT(descriptor.options, ::testing::UnorderedElementsAre(Option::kNone, Option::kEmpty));
+}
+
+TEST(VolumeDescriptorTest, DeserializeWithBadTypeGuidIsError) {
+  ASSERT_TRUE(VolumeDescriptor::Deserialize(GetSerializedJson([](auto* document) {
+                (*document)["type_guid"] = "012345678";
+              })).is_error());
+}
+
+TEST(VolumeDescriptorTest, DeserializeWithBadInstanceGuidIsError) {
+  ASSERT_TRUE(VolumeDescriptor::Deserialize(GetSerializedJson([](auto* document) {
+                (*document)["instance_guid"] = "012345678";
+              })).is_error());
+}
+
+TEST(VolumeDescriptorTest, DeserializeWithLongNameIsTruncated) {
+  constexpr std::string_view kName = "01234567890123456789012345678901234567891";
+  auto descriptor_result = VolumeDescriptor::Deserialize(GetSerializedJson(
+      [kName](auto* document) { (*document)["name"] = rapidjson::StringRef(kName.data()); }));
+  ASSERT_TRUE(descriptor_result.is_ok());
+  ASSERT_EQ(kName.substr(0, kNameLength),
+            std::string(reinterpret_cast<const char*>(descriptor_result.value().name.data())));
+}
+
+TEST(VolumeDescriptorTest, DeserializeWithBadMagicIsError) {
+  ASSERT_TRUE(VolumeDescriptor::Deserialize(GetSerializedJson([](auto* document) {
+                (*document)["magic"] = 0xB201C4;
+              })).is_error());
+}
+
+TEST(VolumeDescriptorTest, DeserializeWithBadCompressionSchemaIsError) {
+  ASSERT_TRUE(VolumeDescriptor::Deserialize(GetSerializedJson([](auto* document) {
+                (*document)["compression_schema"] = "BAD_OR_UNKNOWN_SCHEMA";
+              })).is_error());
+}
+
+TEST(VolumeDescriptorTest, DeserializeWithBadEncryptionTypeIsError) {
+  ASSERT_TRUE(VolumeDescriptor::Deserialize(GetSerializedJson([](auto* document) {
+                (*document)["encryption_type"] = "BAD_OR_UNKNOWN_ENCRYPTION";
+              })).is_error());
+}
+
+TEST(VolumeDescriptorTest, DeserializeWithBadOptionIsError) {
+  ASSERT_TRUE(VolumeDescriptor::Deserialize(GetSerializedJson([](auto* document) {
+                (*document)["options"].GetArray().PushBack("BAD_OR_UNKNOWN_OPTION",
+                                                           document->GetAllocator());
+              })).is_error());
+}
 
 }  // namespace
 }  // namespace storage::volume_image