move parsing of attribute values into the value itself

This commit is contained in:
Emil Lerch 2024-02-20 18:31:45 -08:00
parent 685bae479b
commit 70330295d8
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
2 changed files with 430 additions and 277 deletions

View File

@ -6,132 +6,9 @@ const encryption = @import("encryption.zig");
const ddb = @import("ddb.zig");
const returnException = @import("main.zig").returnException;
const AttributeValue = union(ddb.AttributeTypeName) {
string: []const u8,
number: []const u8, // Floating point stored as string
binary: []const u8, // Base64-encoded binary data object
boolean: bool,
null: bool,
map: std.json.ObjectMap, // We're just holding the json...in the DB we probably just stringify this?
// "M": {"Name": {"S": "Joe"}, "Age": {"N": "35"}}
list: std.json.Array, // Again, just hoding json here:
// "L": [ {"S": "Cookies"} , {"S": "Coffee"}, {"N": "3.14159"}]
string_set: [][]const u8,
number_set: [][]const u8,
binary_set: [][]const u8,
const Self = @This();
pub fn jsonParse(
allocator: std.mem.Allocator,
source: *std.json.Scanner,
options: std.json.ParseOptions,
) !Self {
if (.object_begin != try source.next()) return error.UnexpectedToken;
const token = try source.nextAlloc(allocator, options.allocate.?);
if (token != .string) return error.UnexpectedToken;
var rc: Self = undefined;
if (std.mem.eql(u8, token.string, "string"))
rc = Self{ .string = try std.json.innerParse([]const u8, allocator, source, options) };
if (std.mem.eql(u8, token.string, "number"))
rc = Self{ .number = try std.json.innerParse([]const u8, allocator, source, options) };
if (std.mem.eql(u8, token.string, "binary"))
rc = Self{ .binary = try std.json.innerParse([]const u8, allocator, source, options) };
if (std.mem.eql(u8, token.string, "boolean"))
rc = Self{ .boolean = try std.json.innerParse(bool, allocator, source, options) };
if (std.mem.eql(u8, token.string, "null"))
rc = Self{ .null = try std.json.innerParse(bool, allocator, source, options) };
if (std.mem.eql(u8, token.string, "string_set"))
rc = Self{ .string_set = try std.json.innerParse([][]const u8, allocator, source, options) };
if (std.mem.eql(u8, token.string, "number_set"))
rc = Self{ .number_set = try std.json.innerParse([][]const u8, allocator, source, options) };
if (std.mem.eql(u8, token.string, "binary_set"))
rc = Self{ .binary_set = try std.json.innerParse([][]const u8, allocator, source, options) };
if (std.mem.eql(u8, token.string, "list")) {
var json = try std.json.Value.jsonParse(allocator, source, options);
rc = Self{ .list = json.array };
}
if (std.mem.eql(u8, token.string, "map")) {
var json = try std.json.Value.jsonParse(allocator, source, options);
rc = Self{ .map = json.object };
}
if (.object_end != try source.next()) return error.UnexpectedToken;
return rc;
}
pub fn jsonStringify(self: Self, jws: anytype) !void {
try jws.beginObject();
try jws.objectField(@tagName(self));
switch (self) {
.string, .number, .binary => |s| try jws.write(s),
.boolean, .null => |b| try jws.write(b),
.string_set, .number_set, .binary_set => |s| try jws.write(s),
.list => |l| try jws.write(l.items),
.map => |inner| {
try jws.beginObject();
var it = inner.iterator();
while (it.next()) |entry| {
try jws.objectField(entry.key_ptr.*);
try jws.write(entry.value_ptr.*);
}
try jws.endObject();
},
}
return try jws.endObject();
}
pub fn validate(self: Self) !void {
switch (self) {
.string, .string_set, .boolean, .null, .map, .list => {},
.number => |s| _ = try std.fmt.parseFloat(f64, s),
.binary => |s| try base64Validate(std.base64.standard.Decoder, s),
.number_set => |ns| for (ns) |s| {
_ = try std.fmt.parseFloat(f64, s);
},
.binary_set => |bs| for (bs) |s| try base64Validate(std.base64.standard.Decoder, s),
}
}
fn base64Validate(decoder: std.base64.Base64Decoder, source: []const u8) std.base64.Error!void {
const invalid_char = 0xff;
// This is taken from the stdlib decode function and modified to simply
// not write anything
if (decoder.pad_char != null and source.len % 4 != 0) return error.InvalidPadding;
var acc: u12 = 0;
var acc_len: u4 = 0;
var leftover_idx: ?usize = null;
for (source, 0..) |c, src_idx| {
const d = decoder.char_to_index[c];
if (d == invalid_char) {
if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter;
leftover_idx = src_idx;
break;
}
acc = (acc << 6) + d;
acc_len += 6;
if (acc_len >= 8) {
acc_len -= 8;
}
}
if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) {
return error.InvalidPadding;
}
if (leftover_idx == null) return;
var leftover = source[leftover_idx.?..];
if (decoder.pad_char) |pad_char| {
const padding_len = acc_len / 2;
var padding_chars: usize = 0;
for (leftover) |c| {
if (c != pad_char) {
return if (c == invalid_char) error.InvalidCharacter else error.InvalidPadding;
}
padding_chars += 1;
}
if (padding_chars != padding_len) return error.InvalidPadding;
}
}
};
const Attribute = struct {
name: []const u8,
value: AttributeValue,
value: ddb.AttributeValue,
};
const Request = struct {
@ -335,42 +212,12 @@ const Params = struct {
// {
// "DeleteRequest": {
// "Key": {
// "string" : {
// "B": blob,
// "BOOL": boolean,
// "BS": [ blob ],
// "L": [
// "AttributeValue"
// ],
// "M": {
// "string" : "AttributeValue"
// },
// "N": "string",
// "NS": [ "string" ],
// "NULL": boolean,
// "S": "string",
// "SS": [ "string" ]
// }
// "string" : {...attribute value...}
// }
// },
// "PutRequest": {
// "Item": {
// "string" : {
// "B": blob,
// "BOOL": boolean,
// "BS": [ blob ],
// "L": [
// "AttributeValue"
// ],
// "M": {
// "string" : "AttributeValue"
// },
// "N": "string",
// "NS": [ "string" ],
// "NULL": boolean,
// "S": "string",
// "SS": [ "string" ]
// }
// "string" : {...attribute value...}
// }
// }
// }
@ -387,22 +234,7 @@ const Params = struct {
writer: anytype,
) ![]Attribute {
// {
// "string" : {
// "B": blob,
// "BOOL": boolean,
// "BS": [ blob ],
// "L": [
// "AttributeValue"
// ],
// "M": {
// "string" : "AttributeValue"
// },
// "N": "string",
// "NS": [ "string" ],
// "NULL": boolean,
// "S": "string",
// "SS": [ "string" ]
// }
// "string" : {...attribute value...}
// }
var attribute_count = value.count();
if (attribute_count == 0)
@ -429,104 +261,10 @@ const Params = struct {
"Request in RequestItems found invalid attributes in object",
);
rc[inx].name = key; //try arena.dupe(u8, key);
var val_iterator = val.object.iterator();
var val_val = val_iterator.next().?;
const attribute_type = val_val.key_ptr.*; // This should be "S", "N", "NULL", "BOOL", etc
const attribute_value = val_val.value_ptr.*;
// Convert this to our enum
const attribute_type_enum = std.meta.stringToEnum(ddb.AttributeTypeDescriptor, attribute_type);
if (attribute_type_enum == null)
try returnException(
request,
.bad_request,
error.ValidationException,
writer,
"Request in RequestItems found attribute with invalid type",
);
// Now we need to get *THIS* enum over to our union, which uses the same values
// We'll just use a switch here, because each of these cases must
// be handled slightly differently
var final_attribute_value: AttributeValue = undefined;
switch (attribute_type_enum.?.toAttributeTypeName()) {
.string => {
try expectType(attribute_value, .string, request, writer);
final_attribute_value = .{ .string = attribute_value.string };
},
.number => {
// There is a .number_string, but I think that is for stringify?
try expectType(attribute_value, .string, request, writer);
final_attribute_value = .{ .number = attribute_value.string };
},
.binary => {
try expectType(attribute_value, .string, request, writer);
final_attribute_value = .{ .binary = attribute_value.string };
},
.boolean => {
try expectType(attribute_value, .bool, request, writer);
final_attribute_value = .{ .boolean = attribute_value.bool };
},
.null => {
try expectType(attribute_value, .bool, request, writer);
final_attribute_value = .{ .null = attribute_value.bool };
},
.map => {
try expectType(attribute_value, .object, request, writer);
final_attribute_value = .{ .map = attribute_value.object };
},
.list => {
try expectType(attribute_value, .array, request, writer);
final_attribute_value = .{ .list = attribute_value.array };
},
.string_set => {
try expectType(attribute_value, .array, request, writer);
final_attribute_value = .{ .string_set = try toStringArray(arena, attribute_value.array, request, writer) };
},
.number_set => {
try expectType(attribute_value, .array, request, writer);
final_attribute_value = .{ .number_set = try toStringArray(arena, attribute_value.array, request, writer) };
},
.binary_set => {
try expectType(attribute_value, .array, request, writer);
final_attribute_value = .{ .binary_set = try toStringArray(arena, attribute_value.array, request, writer) };
},
}
rc[inx].value = final_attribute_value;
rc[inx].value = try std.json.parseFromValueLeaky(ddb.AttributeValue, arena, val, .{});
}
return rc;
}
fn toStringArray(
arena: std.mem.Allocator,
arr: std.json.Array,
request: *AuthenticatedRequest,
writer: anytype,
) ![][]const u8 {
var rc = try arena.alloc([]const u8, arr.items.len);
for (arr.items, 0..) |item, inx| {
try expectType(item, .string, request, writer);
rc[inx] = item.string;
}
return rc;
}
fn expectType(actual: std.json.Value, comptime expected: @TypeOf(.enum_literal), request: *AuthenticatedRequest, writer: anytype) !void {
if (actual != expected)
try returnException(
request,
.bad_request,
error.ValidationException,
writer,
"Attribute type does not match expected type",
);
if (actual == .array and actual.array.items.len == 0)
try returnException(
request,
.bad_request,
error.ValidationException,
writer,
"Attribute array cannot be empty",
);
}
};
pub fn handler(request: *AuthenticatedRequest, writer: anytype) ![]const u8 {
@ -969,37 +707,37 @@ test "round trip attributes" {
\\ {
\\ "name": "foo",
\\ "value": {
\\ "string": "bar"
\\ "S": "bar"
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "number": "42"
\\ "N": "42"
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "binary": "YmFy"
\\ "B": "YmFy"
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "boolean": true
\\ "BOOL": true
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "null": false
\\ "NULL": false
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "string_set": [
\\ "SS": [
\\ "foo",
\\ "bar"
\\ ]
@ -1008,7 +746,7 @@ test "round trip attributes" {
\\ {
\\ "name": "foo",
\\ "value": {
\\ "number_set": [
\\ "NS": [
\\ "41",
\\ "42"
\\ ]
@ -1017,7 +755,7 @@ test "round trip attributes" {
\\ {
\\ "name": "foo",
\\ "value": {
\\ "binary_set": [
\\ "BS": [
\\ "Zm9v",
\\ "YmFy"
\\ ]
@ -1026,7 +764,7 @@ test "round trip attributes" {
\\ {
\\ "name": "foo",
\\ "value": {
\\ "map": {
\\ "M": {
\\ "Name": {
\\ "S": "Joe"
\\ },
@ -1039,7 +777,7 @@ test "round trip attributes" {
\\ {
\\ "name": "foo",
\\ "value": {
\\ "list": [
\\ "L": [
\\ {
\\ "S": "Cookies"
\\ },

View File

@ -56,6 +56,175 @@ pub const AttributeDefinition = struct {
type: AttributeTypeDescriptor,
};
pub const AttributeValue = union(AttributeTypeName) {
string: []const u8,
number: []const u8, // Floating point stored as string
binary: []const u8, // Base64-encoded binary data object
boolean: bool,
null: bool,
map: std.json.ObjectMap, // We're just holding the json...in the DB we probably just stringify this?
// "M": {"Name": {"S": "Joe"}, "Age": {"N": "35"}}
list: std.json.Array, // Again, just hoding json here:
// "L": [ {"S": "Cookies"} , {"S": "Coffee"}, {"N": "3.14159"}]
string_set: [][]const u8,
number_set: [][]const u8,
binary_set: [][]const u8,
const Self = @This();
pub fn jsonParse(
allocator: std.mem.Allocator,
source: *std.json.Scanner,
options: std.json.ParseOptions,
) !Self {
if (.object_begin != try source.next()) return error.UnexpectedToken;
const token = try source.nextAlloc(allocator, options.allocate.?);
if (token != .string) return error.UnexpectedToken;
var rc: ?Self = null;
if (std.mem.eql(u8, token.string, "string") or std.mem.eql(u8, token.string, "S"))
rc = Self{ .string = try std.json.innerParse([]const u8, allocator, source, options) };
if (std.mem.eql(u8, token.string, "number") or std.mem.eql(u8, token.string, "N"))
rc = Self{ .number = try std.json.innerParse([]const u8, allocator, source, options) };
if (std.mem.eql(u8, token.string, "binary") or std.mem.eql(u8, token.string, "B"))
rc = Self{ .binary = try std.json.innerParse([]const u8, allocator, source, options) };
if (std.mem.eql(u8, token.string, "boolean") or std.mem.eql(u8, token.string, "BOOL"))
rc = Self{ .boolean = try std.json.innerParse(bool, allocator, source, options) };
if (std.mem.eql(u8, token.string, "null") or std.mem.eql(u8, token.string, "NULL"))
rc = Self{ .null = try std.json.innerParse(bool, allocator, source, options) };
if (std.mem.eql(u8, token.string, "string_set") or std.mem.eql(u8, token.string, "SS"))
rc = Self{ .string_set = try std.json.innerParse([][]const u8, allocator, source, options) };
if (std.mem.eql(u8, token.string, "number_set") or std.mem.eql(u8, token.string, "NS"))
rc = Self{ .number_set = try std.json.innerParse([][]const u8, allocator, source, options) };
if (std.mem.eql(u8, token.string, "binary_set") or std.mem.eql(u8, token.string, "BS"))
rc = Self{ .binary_set = try std.json.innerParse([][]const u8, allocator, source, options) };
if (std.mem.eql(u8, token.string, "list") or std.mem.eql(u8, token.string, "L")) {
var json = try std.json.Value.jsonParse(allocator, source, options);
rc = Self{ .list = json.array };
}
if (std.mem.eql(u8, token.string, "map") or std.mem.eql(u8, token.string, "M")) {
var json = try std.json.Value.jsonParse(allocator, source, options);
rc = Self{ .map = json.object };
}
if (rc == null) return error.InvalidEnumTag;
if (.object_end != try source.next()) return error.UnexpectedToken;
rc.?.validate() catch return error.InvalidCharacter;
return rc.?;
}
pub fn jsonParseFromValue(allocator: std.mem.Allocator, source: std.json.Value, options: std.json.ParseOptions) !Self {
if (source != .object) return error.UnexpectedToken;
var rc: ?Self = null;
if (source.object.get("string") orelse source.object.get("S")) |attr|
rc = Self{ .string = try std.json.innerParseFromValue([]const u8, allocator, attr, options) };
if (source.object.get("number") orelse source.object.get("N")) |attr|
rc = Self{ .number = try std.json.innerParseFromValue([]const u8, allocator, attr, options) };
if (source.object.get("binary") orelse source.object.get("B")) |attr|
rc = Self{ .binary = try std.json.innerParseFromValue([]const u8, allocator, attr, options) };
if (source.object.get("boolean") orelse source.object.get("BOOL")) |attr|
rc = Self{ .boolean = try std.json.innerParseFromValue(bool, allocator, attr, options) };
if (source.object.get("null") orelse source.object.get("NULL")) |attr|
rc = Self{ .null = try std.json.innerParseFromValue(bool, allocator, attr, options) };
if (source.object.get("string_set") orelse source.object.get("SS")) |attr|
rc = Self{ .string_set = try std.json.innerParseFromValue([][]const u8, allocator, attr, options) };
if (source.object.get("number_set") orelse source.object.get("NS")) |attr|
rc = Self{ .number_set = try std.json.innerParseFromValue([][]const u8, allocator, attr, options) };
if (source.object.get("binary_set") orelse source.object.get("BS")) |attr|
rc = Self{ .binary_set = try std.json.innerParseFromValue([][]const u8, allocator, attr, options) };
if (source.object.get("list") orelse source.object.get("L")) |attr| {
var json = try std.json.Value.jsonParseFromValue(allocator, attr, options);
rc = Self{ .list = json.array };
}
if (source.object.get("map") orelse source.object.get("M")) |attr| {
var json = try std.json.Value.jsonParseFromValue(allocator, attr, options);
rc = Self{ .map = json.object };
}
if (rc == null) return error.InvalidEnumTag;
return rc.?;
}
pub fn jsonStringify(self: Self, jws: anytype) !void {
try jws.beginObject();
try jws.objectField(switch (self) {
.string => "S",
.number => "N",
.binary => "B",
.boolean => "BOOL",
.null => "NULL",
.string_set => "SS",
.number_set => "NS",
.binary_set => "BS",
.list => "L",
.map => "M",
});
switch (self) {
.string, .number, .binary => |s| try jws.write(s),
.boolean, .null => |b| try jws.write(b),
.string_set, .number_set, .binary_set => |s| try jws.write(s),
.list => |l| try jws.write(l.items),
.map => |inner| {
try jws.beginObject();
var it = inner.iterator();
while (it.next()) |entry| {
try jws.objectField(entry.key_ptr.*);
try jws.write(entry.value_ptr.*);
}
try jws.endObject();
},
}
return try jws.endObject();
}
pub fn validate(self: Self) !void {
switch (self) {
.string, .string_set, .boolean, .null, .map, .list => {},
.number => |s| _ = try std.fmt.parseFloat(f64, s),
.binary => |s| try base64Validate(std.base64.standard.Decoder, s),
.number_set => |ns| for (ns) |s| {
_ = try std.fmt.parseFloat(f64, s);
},
.binary_set => |bs| for (bs) |s| try base64Validate(std.base64.standard.Decoder, s),
}
}
fn base64Validate(decoder: std.base64.Base64Decoder, source: []const u8) std.base64.Error!void {
const invalid_char = 0xff;
// This is taken from the stdlib decode function and modified to simply
// not write anything
if (decoder.pad_char != null and source.len % 4 != 0) return error.InvalidPadding;
var acc: u12 = 0;
var acc_len: u4 = 0;
var leftover_idx: ?usize = null;
for (source, 0..) |c, src_idx| {
const d = decoder.char_to_index[c];
if (d == invalid_char) {
if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter;
leftover_idx = src_idx;
break;
}
acc = (acc << 6) + d;
acc_len += 6;
if (acc_len >= 8) {
acc_len -= 8;
}
}
if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) {
return error.InvalidPadding;
}
if (leftover_idx == null) return;
var leftover = source[leftover_idx.?..];
if (decoder.pad_char) |pad_char| {
const padding_len = acc_len / 2;
var padding_chars: usize = 0;
for (leftover) |c| {
if (c != pad_char) {
return if (c == invalid_char) error.InvalidCharacter else error.InvalidPadding;
}
padding_chars += 1;
}
if (padding_chars != padding_len) return error.InvalidPadding;
}
}
};
/// TableInfo is serialized directly into the underlying metadata table, along
/// with AttributeDefinition structure and types
pub const TableInfo = struct {
@ -480,3 +649,249 @@ test "can put an item in a table in an account" {
// TODO: this test should do getItem to verify data
// std.debug.print(" \n===\nKey: {s}\n===\n", .{std.fmt.fmtSliceHexLower(&table_list.items[0].table_key)});
}
test "can parse attribute values using slices" {
const allocator = std.testing.allocator;
const source =
\\ {
\\ "String": {
\\ "S": "Amazon DynamoDB"
\\ },
\\ "Number": {
\\ "N": "1.3"
\\ },
\\ "Binary": {
\\ "B": "dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk"
\\ },
\\ "Boolean": {
\\ "BOOL": true
\\ },
\\ "Null": {
\\ "NULL": true
\\ },
\\ "List": {
\\ "L": [ {"S": "Cookies"} , {"S": "Coffee"}, {"N": "3.14159"}]
\\ },
\\ "Map": {
\\ "M": {"Name": {"S": "Joe"}, "Age": {"N": "35"}}
\\ },
\\ "Number Set": {
\\ "NS": ["42.2", "-19", "7.5", "3.14"]
\\ },
\\ "Binary Set": {
\\ "BS": ["U3Vubnk=", "UmFpbnk=", "U25vd3k="]
\\ },
\\ "String Set": {
\\ "SS": ["Giraffe", "Hippo" ,"Zebra"]
\\ }
\\ }
;
const source_value = try std.json.parseFromSlice(std.json.Value, allocator, source, .{});
defer source_value.deinit();
var val = source_value.value.object.get("String").?;
{
const attribute_value_string = try std.json.stringifyAlloc(allocator, val, .{});
defer allocator.free(attribute_value_string);
const attribute_value = try std.json.parseFromSlice(AttributeValue, allocator, attribute_value_string, .{});
// const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqualStrings("Amazon DynamoDB", attribute_value.value.string);
}
val = source_value.value.object.get("Number").?;
{
const attribute_value_string = try std.json.stringifyAlloc(allocator, val, .{});
defer allocator.free(attribute_value_string);
const attribute_value = try std.json.parseFromSlice(AttributeValue, allocator, attribute_value_string, .{});
// const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqualStrings("1.3", attribute_value.value.number);
}
val = source_value.value.object.get("Binary").?;
{
const attribute_value_string = try std.json.stringifyAlloc(allocator, val, .{});
defer allocator.free(attribute_value_string);
const attribute_value = try std.json.parseFromSlice(AttributeValue, allocator, attribute_value_string, .{});
// const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqualStrings("dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk", attribute_value.value.binary);
}
val = source_value.value.object.get("Boolean").?;
{
const attribute_value_string = try std.json.stringifyAlloc(allocator, val, .{});
defer allocator.free(attribute_value_string);
const attribute_value = try std.json.parseFromSlice(AttributeValue, allocator, attribute_value_string, .{});
// const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(true, attribute_value.value.boolean);
}
val = source_value.value.object.get("Null").?;
{
const attribute_value_string = try std.json.stringifyAlloc(allocator, val, .{});
defer allocator.free(attribute_value_string);
const attribute_value = try std.json.parseFromSlice(AttributeValue, allocator, attribute_value_string, .{});
// const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(true, attribute_value.value.null);
}
val = source_value.value.object.get("List").?;
{
const attribute_value_string = try std.json.stringifyAlloc(allocator, val, .{});
defer allocator.free(attribute_value_string);
const attribute_value = try std.json.parseFromSlice(AttributeValue, allocator, attribute_value_string, .{});
// const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(@as(usize, 3), attribute_value.value.list.items.len);
}
val = source_value.value.object.get("Map").?;
{
const attribute_value_string = try std.json.stringifyAlloc(allocator, val, .{});
defer allocator.free(attribute_value_string);
const attribute_value = try std.json.parseFromSlice(AttributeValue, allocator, attribute_value_string, .{});
// const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(@as(usize, 2), attribute_value.value.map.keys().len);
}
val = source_value.value.object.get("Number Set").?;
{
const attribute_value_string = try std.json.stringifyAlloc(allocator, val, .{});
defer allocator.free(attribute_value_string);
const attribute_value = try std.json.parseFromSlice(AttributeValue, allocator, attribute_value_string, .{});
// const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(@as(usize, 4), attribute_value.value.number_set.len);
try std.testing.expectEqualStrings("7.5", attribute_value.value.number_set[2]);
}
val = source_value.value.object.get("Binary Set").?;
{
const attribute_value_string = try std.json.stringifyAlloc(allocator, val, .{});
defer allocator.free(attribute_value_string);
const attribute_value = try std.json.parseFromSlice(AttributeValue, allocator, attribute_value_string, .{});
// const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(@as(usize, 3), attribute_value.value.binary_set.len);
try std.testing.expectEqualStrings("U25vd3k=", attribute_value.value.binary_set[2]);
}
val = source_value.value.object.get("String Set").?;
{
const attribute_value_string = try std.json.stringifyAlloc(allocator, val, .{});
defer allocator.free(attribute_value_string);
const attribute_value = try std.json.parseFromSlice(AttributeValue, allocator, attribute_value_string, .{});
// const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(@as(usize, 3), attribute_value.value.string_set.len);
try std.testing.expectEqualStrings("Zebra", attribute_value.value.string_set[2]);
}
}
test "can parse attribute values using jsonvalue" {
const allocator = std.testing.allocator;
const source =
\\ {
\\ "String": {
\\ "S": "Amazon DynamoDB"
\\ },
\\ "Number": {
\\ "N": "1.3"
\\ },
\\ "Binary": {
\\ "B": "dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk"
\\ },
\\ "Boolean": {
\\ "BOOL": true
\\ },
\\ "Null": {
\\ "NULL": true
\\ },
\\ "List": {
\\ "L": [ {"S": "Cookies"} , {"S": "Coffee"}, {"N": "3.14159"}]
\\ },
\\ "Map": {
\\ "M": {"Name": {"S": "Joe"}, "Age": {"N": "35"}}
\\ },
\\ "Number Set": {
\\ "NS": ["42.2", "-19", "7.5", "3.14"]
\\ },
\\ "Binary Set": {
\\ "BS": ["U3Vubnk=", "UmFpbnk=", "U25vd3k="]
\\ },
\\ "String Set": {
\\ "SS": ["Giraffe", "Hippo" ,"Zebra"]
\\ }
\\ }
;
const source_value = try std.json.parseFromSlice(std.json.Value, allocator, source, .{});
defer source_value.deinit();
var val = source_value.value.object.get("String").?;
{
const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqualStrings("Amazon DynamoDB", attribute_value.value.string);
}
val = source_value.value.object.get("Number").?;
{
const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqualStrings("1.3", attribute_value.value.number);
}
val = source_value.value.object.get("Binary").?;
{
const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqualStrings("dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk", attribute_value.value.binary);
}
val = source_value.value.object.get("Boolean").?;
{
const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(true, attribute_value.value.boolean);
}
val = source_value.value.object.get("Null").?;
{
const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(true, attribute_value.value.null);
}
val = source_value.value.object.get("List").?;
{
const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(@as(usize, 3), attribute_value.value.list.items.len);
}
val = source_value.value.object.get("Map").?;
{
const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(@as(usize, 2), attribute_value.value.map.keys().len);
}
val = source_value.value.object.get("Number Set").?;
{
const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(@as(usize, 4), attribute_value.value.number_set.len);
try std.testing.expectEqualStrings("7.5", attribute_value.value.number_set[2]);
}
val = source_value.value.object.get("Binary Set").?;
{
const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(@as(usize, 3), attribute_value.value.binary_set.len);
try std.testing.expectEqualStrings("U25vd3k=", attribute_value.value.binary_set[2]);
}
val = source_value.value.object.get("String Set").?;
{
const attribute_value = try std.json.parseFromValue(AttributeValue, allocator, val, .{});
defer attribute_value.deinit();
try std.testing.expectEqual(@as(usize, 3), attribute_value.value.string_set.len);
try std.testing.expectEqualStrings("Zebra", attribute_value.value.string_set[2]);
}
}