add getting items in bank
This commit is contained in:
parent
409c6c9014
commit
7ac17313d1
@ -361,26 +361,32 @@ pub const FightResult = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const SkillDetails = struct {
|
pub const ItemQuantity = struct {
|
||||||
pub const Item = struct {
|
id: ItemId,
|
||||||
id: ItemId,
|
quantity: u64,
|
||||||
quantity: i64
|
|
||||||
};
|
|
||||||
|
|
||||||
|
pub fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !ItemQuantity {
|
||||||
|
const code = try json_utils.getStringRequired(obj, "code");
|
||||||
|
const quantity = try json_utils.getIntegerRequired(obj, "quantity");
|
||||||
|
|
||||||
|
return ItemQuantity{
|
||||||
|
.id = try api.getItemId(code),
|
||||||
|
.quantity = @intCast(quantity)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SkillDetails = struct {
|
||||||
xp: i64,
|
xp: i64,
|
||||||
items: std.BoundedArray(Item, 8),
|
items: std.BoundedArray(ItemQuantity, 8),
|
||||||
|
|
||||||
fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !SkillDetails {
|
fn parse(api: *ArtifactsAPI, obj: json.ObjectMap) !SkillDetails {
|
||||||
var items = std.BoundedArray(Item, 8).init(0) catch unreachable;
|
var items = std.BoundedArray(ItemQuantity, 8).init(0) catch unreachable;
|
||||||
const items_obj = json_utils.getArray(obj, "items") orelse return error.MissingProperty;
|
const items_obj = json_utils.getArray(obj, "items") orelse return error.MissingProperty;
|
||||||
for (items_obj.items) |item_value| {
|
for (items_obj.items) |item_value| {
|
||||||
const item_obj = json_utils.asObject(item_value) orelse return error.MissingProperty;
|
const item_obj = json_utils.asObject(item_value) orelse return error.MissingProperty;
|
||||||
const code = try json_utils.getStringRequired(item_obj, "code");
|
|
||||||
|
|
||||||
try items.append(Item{
|
try items.append(ItemQuantity.parse(api, item_obj));
|
||||||
.id = try api.getItemId(code),
|
|
||||||
.quantity = try json_utils.getIntegerRequired(item_obj, "quantity")
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return SkillDetails{
|
return SkillDetails{
|
||||||
@ -655,64 +661,142 @@ pub fn deinit(self: *ArtifactsAPI) void {
|
|||||||
const FetchOptions = struct {
|
const FetchOptions = struct {
|
||||||
method: std.http.Method,
|
method: std.http.Method,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
payload: ?[]const u8 = null
|
payload: ?[]const u8 = null,
|
||||||
|
|
||||||
|
page: ?u64 = null,
|
||||||
|
page_size: ?u64 = null,
|
||||||
|
paginated: bool = false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn allocPaginationParams(allocator: Allocator, page: u64, page_size: ?u64) ![]u8 {
|
||||||
|
if (page_size) |size| {
|
||||||
|
return try std.fmt.allocPrint(allocator, "page={}&size={}", .{page, size});
|
||||||
|
} else {
|
||||||
|
return try std.fmt.allocPrint(allocator, "page={}", .{page});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn fetch(self: *ArtifactsAPI, options: FetchOptions) APIError!ArtifactsFetchResult {
|
fn fetch(self: *ArtifactsAPI, options: FetchOptions) APIError!ArtifactsFetchResult {
|
||||||
const method = options.method;
|
const method = options.method;
|
||||||
const path = options.path;
|
const path = options.path;
|
||||||
const payload = options.payload;
|
const payload = options.payload;
|
||||||
|
|
||||||
std.log.debug("fetch {} {s}", .{method, path});
|
|
||||||
var uri = self.server_uri;
|
var uri = self.server_uri;
|
||||||
uri.path = .{ .raw = path };
|
uri.path = .{ .raw = path };
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
||||||
errdefer arena.deinit();
|
errdefer arena.deinit();
|
||||||
|
|
||||||
var response_storage = std.ArrayList(u8).init(arena.allocator());
|
var result_status: std.http.Status = .ok;
|
||||||
|
var result_body: ?json.Value = null;
|
||||||
|
|
||||||
var opts = std.http.Client.FetchOptions{
|
var current_page: u64 = options.page orelse 1;
|
||||||
.method = method,
|
var total_pages: u64 = 1;
|
||||||
.location = .{ .uri = uri },
|
var fetch_results = std.ArrayList(json.Value).init(arena.allocator());
|
||||||
.payload = payload,
|
|
||||||
.response_storage = .{ .dynamic = &response_storage },
|
|
||||||
};
|
|
||||||
|
|
||||||
var authorization_header: ?[]u8 = null;
|
while (true) : (current_page += 1) {
|
||||||
defer if (authorization_header) |str| self.allocator.free(str);
|
var query_params: ?[]u8 = null;
|
||||||
|
defer if (query_params) |str| self.allocator.free(str);
|
||||||
|
|
||||||
if (self.token) |token| {
|
if (options.paginated) {
|
||||||
authorization_header = std.fmt.allocPrint(self.allocator, "Bearer {s}", .{token}) catch return APIError.OutOfMemory;
|
query_params = try allocPaginationParams(self.allocator, current_page, options.page_size);
|
||||||
opts.headers.authorization = .{ .override = authorization_header.? };
|
uri.query = .{ .raw = query_params.? };
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = self.client.fetch(opts) catch return APIError.RequestFailed;
|
var response_storage = std.ArrayList(u8).init(arena.allocator());
|
||||||
const response_body = response_storage.items;
|
var opts = std.http.Client.FetchOptions{
|
||||||
|
.method = method,
|
||||||
std.log.debug("fetch result {}", .{result.status});
|
.location = .{ .uri = uri },
|
||||||
|
.payload = payload,
|
||||||
if (result.status == .service_unavailable) {
|
.response_storage = .{ .dynamic = &response_storage },
|
||||||
return APIError.ServerUnavailable;
|
|
||||||
} else if (result.status != .ok) {
|
|
||||||
return ArtifactsFetchResult{
|
|
||||||
.arena = arena,
|
|
||||||
.status = result.status
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var authorization_header: ?[]u8 = null;
|
||||||
|
defer if (authorization_header) |str| self.allocator.free(str);
|
||||||
|
|
||||||
|
if (self.token) |token| {
|
||||||
|
authorization_header = std.fmt.allocPrint(self.allocator, "Bearer {s}", .{token}) catch return APIError.OutOfMemory;
|
||||||
|
opts.headers.authorization = .{ .override = authorization_header.? };
|
||||||
|
}
|
||||||
|
|
||||||
|
std.log.debug("fetch {} {s}", .{method, path});
|
||||||
|
const result = self.client.fetch(opts) catch return APIError.RequestFailed;
|
||||||
|
const response_body = response_storage.items;
|
||||||
|
|
||||||
|
std.log.debug("fetch result {}", .{result.status});
|
||||||
|
|
||||||
|
if (result.status == .service_unavailable) {
|
||||||
|
return APIError.ServerUnavailable;
|
||||||
|
} else if (result.status != .ok) {
|
||||||
|
return ArtifactsFetchResult{
|
||||||
|
.arena = arena,
|
||||||
|
.status = result.status
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = json.parseFromSliceLeaky(json.Value, arena.allocator(), response_body, .{ .allocate = .alloc_if_needed }) catch return APIError.ParseFailed;
|
||||||
|
if (parsed != json.Value.object) {
|
||||||
|
return APIError.ParseFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
result_status = result.status;
|
||||||
|
|
||||||
|
if (options.paginated) {
|
||||||
|
const total_pages_i64 = json_utils.getInteger(parsed.object, "pages") orelse return APIError.ParseFailed;
|
||||||
|
total_pages = @intCast(total_pages_i64);
|
||||||
|
|
||||||
|
const page_results = json_utils.getArray(parsed.object, "data") orelse return APIError.ParseFailed;
|
||||||
|
fetch_results.appendSlice(page_results.items) catch return APIError.OutOfMemory;
|
||||||
|
|
||||||
|
if (current_page >= total_pages) break;
|
||||||
|
} else {
|
||||||
|
result_body = parsed.object.get("data");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed = json.parseFromSliceLeaky(json.Value, arena.allocator(), response_body, .{ .allocate = .alloc_if_needed }) catch return APIError.ParseFailed;
|
if (options.paginated) {
|
||||||
if (parsed != json.Value.object) {
|
result_body = json.Value{ .array = fetch_results };
|
||||||
return APIError.ParseFailed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ArtifactsFetchResult{
|
return ArtifactsFetchResult{
|
||||||
.status = result.status,
|
.status = result_status,
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.body = parsed.object.get("data")
|
.body = result_body
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handleFetchError(
|
||||||
|
status: std.http.Status,
|
||||||
|
Error: type,
|
||||||
|
parseError: ?fn (status: std.http.Status) ?Error,
|
||||||
|
) ?Error {
|
||||||
|
if (status != .ok) {
|
||||||
|
if (Error != APIError) {
|
||||||
|
if (parseError == null) {
|
||||||
|
@compileError("`parseError` must be defined, if `Error` is not `APIError`");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseError.?(status)) |error_value| {
|
||||||
|
return error_value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (parseError != null) {
|
||||||
|
@compileError("`parseError` must be null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == .not_found) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (status != .ok) {
|
||||||
|
return APIError.RequestFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
fn fetchOptionalObject(
|
fn fetchOptionalObject(
|
||||||
self: *ArtifactsAPI,
|
self: *ArtifactsAPI,
|
||||||
Error: type,
|
Error: type,
|
||||||
@ -729,28 +813,10 @@ fn fetchOptionalObject(
|
|||||||
const result = try self.fetch(fetchOptions);
|
const result = try self.fetch(fetchOptions);
|
||||||
defer result.deinit();
|
defer result.deinit();
|
||||||
|
|
||||||
if (result.status != .ok) {
|
if (handleFetchError(result.status, Error, parseError)) |error_value| {
|
||||||
if (Error != APIError) {
|
return error_value;
|
||||||
if (parseError == null) {
|
|
||||||
@compileError("`parseError` must be defined, if `Error` is not `APIError`");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parseError.?(result.status)) |error_value| {
|
|
||||||
return error_value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (parseError != null) {
|
|
||||||
@compileError("`parseError` must be null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.status == .not_found) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (result.status != .ok) {
|
|
||||||
return APIError.RequestFailed;
|
|
||||||
}
|
|
||||||
if (result.body == null) {
|
if (result.body == null) {
|
||||||
return APIError.ParseFailed;
|
return APIError.ParseFailed;
|
||||||
}
|
}
|
||||||
@ -772,6 +838,45 @@ fn fetchObject(
|
|||||||
return result orelse return APIError.RequestFailed;
|
return result orelse return APIError.RequestFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fetchArray(
|
||||||
|
self: *ArtifactsAPI,
|
||||||
|
allocator: Allocator,
|
||||||
|
Error: type,
|
||||||
|
parseError: ?fn (status: std.http.Status) ?Error,
|
||||||
|
Object: type,
|
||||||
|
parseObject: anytype,
|
||||||
|
parseObjectArgs: anytype,
|
||||||
|
fetchOptions: FetchOptions
|
||||||
|
) Error!std.ArrayList(Object) {
|
||||||
|
if (@typeInfo(@TypeOf(parseObject)) != .Fn) {
|
||||||
|
@compileError("`parseObject` must be a function");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = try self.fetch(fetchOptions);
|
||||||
|
defer result.deinit();
|
||||||
|
|
||||||
|
if (handleFetchError(result.status, Error, parseError)) |error_value| {
|
||||||
|
return error_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.body == null) {
|
||||||
|
return APIError.ParseFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var array = std.ArrayList(Object).init(allocator);
|
||||||
|
errdefer array.deinit();
|
||||||
|
|
||||||
|
const result_data = json_utils.asArray(result.body.?) orelse return APIError.ParseFailed;
|
||||||
|
for (result_data.items) |result_item| {
|
||||||
|
const item_obj = json_utils.asObject(result_item) orelse return APIError.ParseFailed;
|
||||||
|
|
||||||
|
const parsed_item = @call(.auto, parseObject, .{ self, item_obj } ++ parseObjectArgs) catch return APIError.ParseFailed;
|
||||||
|
array.append(parsed_item) catch return APIError.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setServer(self: *ArtifactsAPI, url: []const u8) !void {
|
pub fn setServer(self: *ArtifactsAPI, url: []const u8) !void {
|
||||||
const url_dupe = self.allocator.dupe(u8, url);
|
const url_dupe = self.allocator.dupe(u8, url);
|
||||||
errdefer self.allocator.free(url_dupe);
|
errdefer self.allocator.free(url_dupe);
|
||||||
@ -1082,6 +1187,17 @@ pub fn getBankGold(self: *ArtifactsAPI) APIError!u64 {
|
|||||||
return @intCast(quantity);
|
return @intCast(quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getBankItems(self: *ArtifactsAPI) APIError!std.ArrayList(ItemQuantity) {
|
||||||
|
return self.fetchArray(
|
||||||
|
self.allocator,
|
||||||
|
APIError,
|
||||||
|
null,
|
||||||
|
ItemQuantity,
|
||||||
|
ItemQuantity.parse, .{},
|
||||||
|
.{ .method = .GET, .path = "/my/bank/items", .paginated = true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
test "parse date time" {
|
test "parse date time" {
|
||||||
try std.testing.expectEqual(1723069394.105, parseDateTime("2024-08-07T22:23:14.105Z").?);
|
try std.testing.expectEqual(1723069394.105, parseDateTime("2024-08-07T22:23:14.105Z").?);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user