move more common code into ddb.zig

This commit is contained in:
Emil Lerch 2024-02-20 19:15:42 -08:00
parent 70330295d8
commit 1a86c307d5
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
2 changed files with 214 additions and 211 deletions

View File

@ -6,28 +6,18 @@ 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 Attribute = struct {
name: []const u8,
value: ddb.AttributeValue,
};
const Request = struct { const Request = struct {
put_request: ?[]Attribute, put_request: ?[]ddb.Attribute,
delete_request: ?[]Attribute, delete_request: ?[]ddb.Attribute,
}; };
const RequestItem = struct { const RequestItem = struct {
table_name: []const u8, table_name: []const u8,
requests: []Request, requests: []Request,
}; };
const ReturnConsumedCapacity = enum {
indexes,
total,
none,
};
const Params = struct { const Params = struct {
request_items: []RequestItem, request_items: []RequestItem,
return_consumed_capacity: ReturnConsumedCapacity = .none, return_consumed_capacity: ddb.ReturnConsumedCapacity = .none,
return_item_collection_metrics: bool = false, return_item_collection_metrics: bool = false,
arena: *std.heap.ArenaAllocator, arena: *std.heap.ArenaAllocator,
@ -87,7 +77,7 @@ const Params = struct {
"ReturnConsumedCapacity value invalid. Valid values are INDEXES | TOTAL | NONE", "ReturnConsumedCapacity value invalid. Valid values are INDEXES | TOTAL | NONE",
); );
const val = try std.ascii.allocLowerString(aa, rcc.string); const val = try std.ascii.allocLowerString(aa, rcc.string);
rc.return_consumed_capacity = std.meta.stringToEnum(ReturnConsumedCapacity, val).?; rc.return_consumed_capacity = std.meta.stringToEnum(ddb.ReturnConsumedCapacity, val).?;
} }
if (parsed.object.get("ReturnItemCollectionMetrics")) |rcm| { if (parsed.object.get("ReturnItemCollectionMetrics")) |rcm| {
if (rcm != .string or if (rcm != .string or
@ -187,7 +177,7 @@ const Params = struct {
"PutRequest in RequestItems found without Item object", "PutRequest in RequestItems found without Item object",
); );
// Parse item object and assign to array // Parse item object and assign to array
table_request.put_request = try parseAttributes(aa, put_val.?.object, request, writer); table_request.put_request = try ddb.Attribute.parseAttributes(aa, put_val.?.object, request, writer);
} else { } else {
const del_val = pod_val.object.get("Keys"); const del_val = pod_val.object.get("Keys");
if (del_val == null or del_val.? != .object) if (del_val == null or del_val.? != .object)
@ -199,7 +189,7 @@ const Params = struct {
"DeleteRequest in RequestItems found without Key object", "DeleteRequest in RequestItems found without Key object",
); );
// Parse key object and assign to array // Parse key object and assign to array
table_request.delete_request = try parseAttributes(aa, del_val.?.object, request, writer); table_request.delete_request = try ddb.Attribute.parseAttributes(aa, del_val.?.object, request, writer);
} }
} }
rc.request_items[inx].requests[jnx] = table_request.*; rc.request_items[inx].requests[jnx] = table_request.*;
@ -227,44 +217,6 @@ const Params = struct {
// "ReturnItemCollectionMetrics": "string" // "ReturnItemCollectionMetrics": "string"
// } // }
} }
fn parseAttributes(
arena: std.mem.Allocator,
value: anytype,
request: *AuthenticatedRequest,
writer: anytype,
) ![]Attribute {
// {
// "string" : {...attribute value...}
// }
var attribute_count = value.count();
if (attribute_count == 0)
try returnException(
request,
.bad_request,
error.ValidationException,
writer,
"Request in RequestItems found without any attributes in object",
);
var rc = try arena.alloc(Attribute, attribute_count);
var iterator = value.iterator();
var inx: usize = 0;
while (iterator.next()) |att| : (inx += 1) {
const key = att.key_ptr.*;
const val = att.value_ptr.*;
// std.debug.print(" \n====\nkey = \"{s}\"\nval = {any}\n====\n", .{ key, val.object.count() });
if (val != .object or val.object.count() != 1)
try returnException(
request,
.bad_request,
error.ValidationException,
writer,
"Request in RequestItems found invalid attributes in object",
);
rc[inx].name = key; //try arena.dupe(u8, key);
rc[inx].value = try std.json.parseFromValueLeaky(ddb.AttributeValue, arena, val, .{});
}
return rc;
}
}; };
pub fn handler(request: *AuthenticatedRequest, writer: anytype) ![]const u8 { pub fn handler(request: *AuthenticatedRequest, writer: anytype) ![]const u8 {
@ -343,14 +295,14 @@ fn process_request(
db: anytype, db: anytype,
table: *ddb.Table, table: *ddb.Table,
req_type: RequestType, req_type: RequestType,
req_attributes: []Attribute, req_attributes: []ddb.Attribute,
) !void { ) !void {
_ = db; _ = db;
// 1. Find the hash values of put and delete requests in the request // 1. Find the hash values of put and delete requests in the request
const hash_key_attribute_name = table.info.value.hash_key_attribute_name; const hash_key_attribute_name = table.info.value.hash_key_attribute_name;
const range_key_attribute_name = table.info.value.range_key_attribute_name; const range_key_attribute_name = table.info.value.range_key_attribute_name;
var hash_attribute: ?Attribute = null; var hash_attribute: ?ddb.Attribute = null;
var range_attribute: ?Attribute = null; var range_attribute: ?ddb.Attribute = null;
for (req_attributes) |*att| { for (req_attributes) |*att| {
if (std.mem.eql(u8, att.name, hash_key_attribute_name)) { if (std.mem.eql(u8, att.name, hash_key_attribute_name)) {
hash_attribute = att.*; hash_attribute = att.*;
@ -642,157 +594,3 @@ test "write item" {
var writer = al.writer(); var writer = al.writer();
_ = try handler(&request, writer); _ = try handler(&request, writer);
} }
test "round trip attributes" {
const allocator = std.testing.allocator;
var json_stuff = try std.json.parseFromSlice(std.json.Value, allocator,
\\ {
\\ "M": {"Name": {"S": "Joe"}, "Age": {"N": "35"}},
\\ "L": [ {"S": "Cookies"} , {"S": "Coffee"}, {"N": "3.14159"}]
\\ }
, .{});
defer json_stuff.deinit();
const map = json_stuff.value.object.get("M").?.object;
const list = json_stuff.value.object.get("L").?.array;
const attributes = &[_]Attribute{
.{
.name = "foo",
.value = .{ .string = "bar" },
},
.{
.name = "foo",
.value = .{ .number = "42" },
},
.{
.name = "foo",
.value = .{ .binary = "YmFy" }, // "bar"
},
.{
.name = "foo",
.value = .{ .boolean = true },
},
.{
.name = "foo",
.value = .{ .null = false },
},
.{
.name = "foo",
.value = .{ .string_set = @constCast(&[_][]const u8{ "foo", "bar" }) },
},
.{
.name = "foo",
.value = .{ .number_set = @constCast(&[_][]const u8{ "41", "42" }) },
},
.{
.name = "foo",
.value = .{ .binary_set = @constCast(&[_][]const u8{ "Zm9v", "YmFy" }) }, // foo, bar
},
.{
.name = "foo",
.value = .{ .map = map },
},
.{
.name = "foo",
.value = .{ .list = list },
},
};
const attributes_as_string = try std.json.stringifyAlloc(
allocator,
attributes,
.{ .whitespace = .indent_2 },
);
defer allocator.free(attributes_as_string);
try std.testing.expectEqualStrings(
\\[
\\ {
\\ "name": "foo",
\\ "value": {
\\ "S": "bar"
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "N": "42"
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "B": "YmFy"
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "BOOL": true
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "NULL": false
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "SS": [
\\ "foo",
\\ "bar"
\\ ]
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "NS": [
\\ "41",
\\ "42"
\\ ]
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "BS": [
\\ "Zm9v",
\\ "YmFy"
\\ ]
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "M": {
\\ "Name": {
\\ "S": "Joe"
\\ },
\\ "Age": {
\\ "N": "35"
\\ }
\\ }
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "L": [
\\ {
\\ "S": "Cookies"
\\ },
\\ {
\\ "S": "Coffee"
\\ },
\\ {
\\ "N": "3.14159"
\\ }
\\ ]
\\ }
\\ }
\\]
, attributes_as_string);
var round_tripped = try std.json.parseFromSlice([]Attribute, allocator, attributes_as_string, .{});
defer round_tripped.deinit();
}

View File

@ -4,6 +4,7 @@ const AuthenticatedRequest = @import("AuthenticatedRequest.zig");
const Account = @import("Account.zig"); const Account = @import("Account.zig");
const encryption = @import("encryption.zig"); const encryption = @import("encryption.zig");
const builtin = @import("builtin"); const builtin = @import("builtin");
const returnException = @import("main.zig").returnException;
// We need our enryption to be able to store/retrieve and otherwise work like // We need our enryption to be able to store/retrieve and otherwise work like
// a database. So the use of a nonce here defeats these use cases // a database. So the use of a nonce here defeats these use cases
@ -56,6 +57,56 @@ pub const AttributeDefinition = struct {
type: AttributeTypeDescriptor, type: AttributeTypeDescriptor,
}; };
pub const ReturnConsumedCapacity = enum {
indexes,
total,
none,
};
pub const Attribute = struct {
name: []const u8,
value: AttributeValue,
pub fn parseAttributes(
arena: std.mem.Allocator,
value: std.json.ObjectMap,
request: *AuthenticatedRequest,
writer: anytype,
) ![]Attribute {
// {
// "string" : {...attribute value...}
// }
var attribute_count = value.count();
if (attribute_count == 0)
try returnException(
request,
.bad_request,
error.ValidationException,
writer,
"Request in RequestItems found without any attributes in object",
);
var rc = try arena.alloc(Attribute, attribute_count);
var iterator = value.iterator();
var inx: usize = 0;
while (iterator.next()) |att| : (inx += 1) {
const key = att.key_ptr.*;
const val = att.value_ptr.*;
// std.debug.print(" \n====\nkey = \"{s}\"\nval = {any}\n====\n", .{ key, val.object.count() });
if (val != .object or val.object.count() != 1)
try returnException(
request,
.bad_request,
error.ValidationException,
writer,
"Request in RequestItems found invalid attributes in object",
);
rc[inx].name = key; //try arena.dupe(u8, key);
rc[inx].value = try std.json.parseFromValueLeaky(AttributeValue, arena, val, .{});
}
return rc;
}
};
pub const AttributeValue = union(AttributeTypeName) { pub const AttributeValue = union(AttributeTypeName) {
string: []const u8, string: []const u8,
number: []const u8, // Floating point stored as string number: []const u8, // Floating point stored as string
@ -895,3 +946,157 @@ test "can parse attribute values using jsonvalue" {
try std.testing.expectEqualStrings("Zebra", attribute_value.value.string_set[2]); try std.testing.expectEqualStrings("Zebra", attribute_value.value.string_set[2]);
} }
} }
test "round trip attributes" {
const allocator = std.testing.allocator;
var json_stuff = try std.json.parseFromSlice(std.json.Value, allocator,
\\ {
\\ "M": {"Name": {"S": "Joe"}, "Age": {"N": "35"}},
\\ "L": [ {"S": "Cookies"} , {"S": "Coffee"}, {"N": "3.14159"}]
\\ }
, .{});
defer json_stuff.deinit();
const map = json_stuff.value.object.get("M").?.object;
const list = json_stuff.value.object.get("L").?.array;
const attributes = &[_]Attribute{
.{
.name = "foo",
.value = .{ .string = "bar" },
},
.{
.name = "foo",
.value = .{ .number = "42" },
},
.{
.name = "foo",
.value = .{ .binary = "YmFy" }, // "bar"
},
.{
.name = "foo",
.value = .{ .boolean = true },
},
.{
.name = "foo",
.value = .{ .null = false },
},
.{
.name = "foo",
.value = .{ .string_set = @constCast(&[_][]const u8{ "foo", "bar" }) },
},
.{
.name = "foo",
.value = .{ .number_set = @constCast(&[_][]const u8{ "41", "42" }) },
},
.{
.name = "foo",
.value = .{ .binary_set = @constCast(&[_][]const u8{ "Zm9v", "YmFy" }) }, // foo, bar
},
.{
.name = "foo",
.value = .{ .map = map },
},
.{
.name = "foo",
.value = .{ .list = list },
},
};
const attributes_as_string = try std.json.stringifyAlloc(
allocator,
attributes,
.{ .whitespace = .indent_2 },
);
defer allocator.free(attributes_as_string);
try std.testing.expectEqualStrings(
\\[
\\ {
\\ "name": "foo",
\\ "value": {
\\ "S": "bar"
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "N": "42"
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "B": "YmFy"
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "BOOL": true
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "NULL": false
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "SS": [
\\ "foo",
\\ "bar"
\\ ]
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "NS": [
\\ "41",
\\ "42"
\\ ]
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "BS": [
\\ "Zm9v",
\\ "YmFy"
\\ ]
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "M": {
\\ "Name": {
\\ "S": "Joe"
\\ },
\\ "Age": {
\\ "N": "35"
\\ }
\\ }
\\ }
\\ },
\\ {
\\ "name": "foo",
\\ "value": {
\\ "L": [
\\ {
\\ "S": "Cookies"
\\ },
\\ {
\\ "S": "Coffee"
\\ },
\\ {
\\ "N": "3.14159"
\\ }
\\ ]
\\ }
\\ }
\\]
, attributes_as_string);
var round_tripped = try std.json.parseFromSlice([]Attribute, allocator, attributes_as_string, .{});
defer round_tripped.deinit();
}