move parsing of attribute values into the value itself
This commit is contained in:
parent
685bae479b
commit
70330295d8
|
@ -6,132 +6,9 @@ const encryption = @import("encryption.zig");
|
||||||
const ddb = @import("ddb.zig");
|
const ddb = @import("ddb.zig");
|
||||||
const returnException = @import("main.zig").returnException;
|
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 {
|
const Attribute = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
value: AttributeValue,
|
value: ddb.AttributeValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Request = struct {
|
const Request = struct {
|
||||||
|
@ -335,42 +212,12 @@ const Params = struct {
|
||||||
// {
|
// {
|
||||||
// "DeleteRequest": {
|
// "DeleteRequest": {
|
||||||
// "Key": {
|
// "Key": {
|
||||||
// "string" : {
|
// "string" : {...attribute value...}
|
||||||
// "B": blob,
|
|
||||||
// "BOOL": boolean,
|
|
||||||
// "BS": [ blob ],
|
|
||||||
// "L": [
|
|
||||||
// "AttributeValue"
|
|
||||||
// ],
|
|
||||||
// "M": {
|
|
||||||
// "string" : "AttributeValue"
|
|
||||||
// },
|
|
||||||
// "N": "string",
|
|
||||||
// "NS": [ "string" ],
|
|
||||||
// "NULL": boolean,
|
|
||||||
// "S": "string",
|
|
||||||
// "SS": [ "string" ]
|
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// "PutRequest": {
|
// "PutRequest": {
|
||||||
// "Item": {
|
// "Item": {
|
||||||
// "string" : {
|
// "string" : {...attribute value...}
|
||||||
// "B": blob,
|
|
||||||
// "BOOL": boolean,
|
|
||||||
// "BS": [ blob ],
|
|
||||||
// "L": [
|
|
||||||
// "AttributeValue"
|
|
||||||
// ],
|
|
||||||
// "M": {
|
|
||||||
// "string" : "AttributeValue"
|
|
||||||
// },
|
|
||||||
// "N": "string",
|
|
||||||
// "NS": [ "string" ],
|
|
||||||
// "NULL": boolean,
|
|
||||||
// "S": "string",
|
|
||||||
// "SS": [ "string" ]
|
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
@ -387,22 +234,7 @@ const Params = struct {
|
||||||
writer: anytype,
|
writer: anytype,
|
||||||
) ![]Attribute {
|
) ![]Attribute {
|
||||||
// {
|
// {
|
||||||
// "string" : {
|
// "string" : {...attribute value...}
|
||||||
// "B": blob,
|
|
||||||
// "BOOL": boolean,
|
|
||||||
// "BS": [ blob ],
|
|
||||||
// "L": [
|
|
||||||
// "AttributeValue"
|
|
||||||
// ],
|
|
||||||
// "M": {
|
|
||||||
// "string" : "AttributeValue"
|
|
||||||
// },
|
|
||||||
// "N": "string",
|
|
||||||
// "NS": [ "string" ],
|
|
||||||
// "NULL": boolean,
|
|
||||||
// "S": "string",
|
|
||||||
// "SS": [ "string" ]
|
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
var attribute_count = value.count();
|
var attribute_count = value.count();
|
||||||
if (attribute_count == 0)
|
if (attribute_count == 0)
|
||||||
|
@ -429,104 +261,10 @@ const Params = struct {
|
||||||
"Request in RequestItems found invalid attributes in object",
|
"Request in RequestItems found invalid attributes in object",
|
||||||
);
|
);
|
||||||
rc[inx].name = key; //try arena.dupe(u8, key);
|
rc[inx].name = key; //try arena.dupe(u8, key);
|
||||||
var val_iterator = val.object.iterator();
|
rc[inx].value = try std.json.parseFromValueLeaky(ddb.AttributeValue, arena, val, .{});
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
return rc;
|
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 {
|
pub fn handler(request: *AuthenticatedRequest, writer: anytype) ![]const u8 {
|
||||||
|
@ -969,37 +707,37 @@ test "round trip attributes" {
|
||||||
\\ {
|
\\ {
|
||||||
\\ "name": "foo",
|
\\ "name": "foo",
|
||||||
\\ "value": {
|
\\ "value": {
|
||||||
\\ "string": "bar"
|
\\ "S": "bar"
|
||||||
\\ }
|
\\ }
|
||||||
\\ },
|
\\ },
|
||||||
\\ {
|
\\ {
|
||||||
\\ "name": "foo",
|
\\ "name": "foo",
|
||||||
\\ "value": {
|
\\ "value": {
|
||||||
\\ "number": "42"
|
\\ "N": "42"
|
||||||
\\ }
|
\\ }
|
||||||
\\ },
|
\\ },
|
||||||
\\ {
|
\\ {
|
||||||
\\ "name": "foo",
|
\\ "name": "foo",
|
||||||
\\ "value": {
|
\\ "value": {
|
||||||
\\ "binary": "YmFy"
|
\\ "B": "YmFy"
|
||||||
\\ }
|
\\ }
|
||||||
\\ },
|
\\ },
|
||||||
\\ {
|
\\ {
|
||||||
\\ "name": "foo",
|
\\ "name": "foo",
|
||||||
\\ "value": {
|
\\ "value": {
|
||||||
\\ "boolean": true
|
\\ "BOOL": true
|
||||||
\\ }
|
\\ }
|
||||||
\\ },
|
\\ },
|
||||||
\\ {
|
\\ {
|
||||||
\\ "name": "foo",
|
\\ "name": "foo",
|
||||||
\\ "value": {
|
\\ "value": {
|
||||||
\\ "null": false
|
\\ "NULL": false
|
||||||
\\ }
|
\\ }
|
||||||
\\ },
|
\\ },
|
||||||
\\ {
|
\\ {
|
||||||
\\ "name": "foo",
|
\\ "name": "foo",
|
||||||
\\ "value": {
|
\\ "value": {
|
||||||
\\ "string_set": [
|
\\ "SS": [
|
||||||
\\ "foo",
|
\\ "foo",
|
||||||
\\ "bar"
|
\\ "bar"
|
||||||
\\ ]
|
\\ ]
|
||||||
|
@ -1008,7 +746,7 @@ test "round trip attributes" {
|
||||||
\\ {
|
\\ {
|
||||||
\\ "name": "foo",
|
\\ "name": "foo",
|
||||||
\\ "value": {
|
\\ "value": {
|
||||||
\\ "number_set": [
|
\\ "NS": [
|
||||||
\\ "41",
|
\\ "41",
|
||||||
\\ "42"
|
\\ "42"
|
||||||
\\ ]
|
\\ ]
|
||||||
|
@ -1017,7 +755,7 @@ test "round trip attributes" {
|
||||||
\\ {
|
\\ {
|
||||||
\\ "name": "foo",
|
\\ "name": "foo",
|
||||||
\\ "value": {
|
\\ "value": {
|
||||||
\\ "binary_set": [
|
\\ "BS": [
|
||||||
\\ "Zm9v",
|
\\ "Zm9v",
|
||||||
\\ "YmFy"
|
\\ "YmFy"
|
||||||
\\ ]
|
\\ ]
|
||||||
|
@ -1026,7 +764,7 @@ test "round trip attributes" {
|
||||||
\\ {
|
\\ {
|
||||||
\\ "name": "foo",
|
\\ "name": "foo",
|
||||||
\\ "value": {
|
\\ "value": {
|
||||||
\\ "map": {
|
\\ "M": {
|
||||||
\\ "Name": {
|
\\ "Name": {
|
||||||
\\ "S": "Joe"
|
\\ "S": "Joe"
|
||||||
\\ },
|
\\ },
|
||||||
|
@ -1039,7 +777,7 @@ test "round trip attributes" {
|
||||||
\\ {
|
\\ {
|
||||||
\\ "name": "foo",
|
\\ "name": "foo",
|
||||||
\\ "value": {
|
\\ "value": {
|
||||||
\\ "list": [
|
\\ "L": [
|
||||||
\\ {
|
\\ {
|
||||||
\\ "S": "Cookies"
|
\\ "S": "Cookies"
|
||||||
\\ },
|
\\ },
|
||||||
|
|
415
src/ddb.zig
415
src/ddb.zig
|
@ -56,6 +56,175 @@ pub const AttributeDefinition = struct {
|
||||||
type: AttributeTypeDescriptor,
|
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
|
/// TableInfo is serialized directly into the underlying metadata table, along
|
||||||
/// with AttributeDefinition structure and types
|
/// with AttributeDefinition structure and types
|
||||||
pub const TableInfo = struct {
|
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
|
// 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)});
|
// 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user