bindings/zig/src/opendal.zig (399 lines of code) (raw):
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
pub const c = @import("opendal_c_header");
pub const Operator = struct {
inner: *c.opendal_operator,
pub fn init(scheme: []const u8, options: ?*c.opendal_operator_options) !Operator {
const result = c.opendal_operator_new(scheme.ptr, options);
if (result.op == null) {
if (result.@"error") |err| {
c.opendal_error_free(err);
}
return error.OperatorInitFailed;
}
return .{
.inner = result.op.?,
};
}
pub fn deinit(self: *Operator) void {
c.opendal_operator_free(self.inner);
}
pub fn write(self: *const Operator, path: []const u8, data: []const u8) !void {
const bytes = c.opendal_bytes{
.data = @constCast(data.ptr),
.len = data.len,
.capacity = data.len,
};
if (c.opendal_operator_write(self.inner, path.ptr, &bytes)) |err| {
errdefer c.opendal_error_free(err);
try codeToError(err.*.code);
}
}
pub fn read(self: *const Operator, path: []const u8) ![]const u8 {
const result = c.opendal_operator_read(self.inner, path.ptr);
if (result.@"error") |err| {
errdefer c.opendal_error_free(err);
try codeToError(err.*.code);
}
return result.data.data[0..result.data.len];
}
pub fn delete(self: *const Operator, path: []const u8) !void {
if (c.opendal_operator_delete(self.inner, path.ptr)) |err| {
errdefer c.opendal_error_free(err);
try codeToError(err.*.code);
}
}
pub fn stat(self: *const Operator, path: []const u8) !Metadata {
const result = c.opendal_operator_stat(self.inner, path.ptr);
if (result.@"error") |err| {
errdefer c.opendal_error_free(err);
try codeToError(err.*.code);
}
return .{ .inner = result.meta.? };
}
pub fn exists(self: *const Operator, path: []const u8) !bool {
const result = c.opendal_operator_exists(self.inner, path.ptr);
if (result.@"error") |err| {
errdefer c.opendal_error_free(err);
try codeToError(err.*.code);
}
return result.exists;
}
pub fn list(self: *const Operator, path: []const u8) !Lister {
const result = c.opendal_operator_list(self.inner, path.ptr);
if (result.@"error") |err| {
errdefer c.opendal_error_free(err);
try codeToError(err.*.code);
}
return .{ .inner = result.lister.? };
}
pub fn createDir(self: *const Operator, path: []const u8) !void {
if (c.opendal_operator_create_dir(self.inner, path.ptr)) |err| {
errdefer c.opendal_error_free(err);
try codeToError(err.*.code);
}
}
pub fn rename(self: *const Operator, src: []const u8, dest: []const u8) !void {
if (c.opendal_operator_rename(self.inner, src.ptr, dest.ptr)) |err| {
errdefer c.opendal_error_free(err);
try codeToError(err.*.code);
}
}
pub fn copy(self: *const Operator, src: []const u8, dest: []const u8) !void {
if (c.opendal_operator_copy(self.inner, src.ptr, dest.ptr)) |err| {
errdefer c.opendal_error_free(err);
try codeToError(err.*.code);
}
}
pub fn info(self: *const Operator) !OperatorInfo {
const info_new = c.opendal_operator_info_new(self.inner);
if (info_new == null) return error.InfoFailed;
return .{ .inner = info.? };
}
};
pub const OperatorInfo = struct {
inner: *c.opendal_operator_info,
pub fn deinit(self: *OperatorInfo) void {
c.opendal_operator_info_free(self.inner);
}
pub fn scheme(self: *const OperatorInfo) []const u8 {
const ptr = c.opendal_operator_info_get_scheme(self.inner);
defer std.c.free(ptr);
return std.mem.span(ptr);
}
pub fn root(self: *const OperatorInfo) []const u8 {
const ptr = c.opendal_operator_info_get_root(self.inner);
defer std.c.free(ptr);
return std.mem.span(ptr);
}
pub fn name(self: *const OperatorInfo) []const u8 {
const ptr = c.opendal_operator_info_get_name(self.inner);
defer std.c.free(ptr);
return std.mem.span(ptr);
}
pub fn fullCapability(self: *const OperatorInfo) c.opendal_capability {
return c.opendal_operator_info_get_full_capability(self.inner);
}
pub fn nativeCapability(self: *const OperatorInfo) c.opendal_capability {
return c.opendal_operator_info_get_native_capability(self.inner);
}
};
pub const Lister = struct {
inner: *c.opendal_lister,
pub fn deinit(self: *Lister) void {
c.opendal_lister_free(self.inner);
}
pub fn next(self: *const Lister) !?Entry {
const result = c.opendal_lister_next(self.inner);
if (result.@"error") |err| {
errdefer c.opendal_error_free(err);
try codeToError(err.*.code);
}
if (result.entry) |entry| {
return Entry{ .inner = entry };
}
return null;
}
};
pub const Entry = struct {
inner: *c.opendal_entry,
pub fn deinit(self: *Entry) void {
c.opendal_entry_free(self.inner);
}
pub fn path(self: *const Entry) []const u8 {
const ptr = c.opendal_entry_path(self.inner);
return std.mem.span(ptr);
}
pub fn name(self: *const Entry) []const u8 {
const ptr = c.opendal_entry_name(self.inner);
return std.mem.span(ptr);
}
};
pub const Metadata = struct {
inner: *c.opendal_metadata,
pub fn deinit(self: *Metadata) void {
c.opendal_metadata_free(self.inner);
}
pub fn mode(self: *const Metadata) u32 {
return c.opendal_metadata_mode(self.inner);
}
pub fn isDir(self: *const Metadata) bool {
return c.opendal_metadata_is_dir(self.inner);
}
pub fn isFile(self: *const Metadata) bool {
return c.opendal_metadata_is_file(self.inner);
}
pub fn contentLength(self: *const Metadata) u64 {
return c.opendal_metadata_content_length(self.inner);
}
pub fn contentType(self: *const Metadata) ?[]const u8 {
var len: usize = undefined;
const ptr = c.opendal_metadata_content_type(self.inner, &len);
if (ptr == null) return null;
return ptr[0..len];
}
pub fn etag(self: *const Metadata) ?[]const u8 {
var len: usize = undefined;
const ptr = c.opendal_metadata_etag(self.inner, &len);
if (ptr == null) return null;
return ptr[0..len];
}
pub fn lastModified(self: *const Metadata) i64 {
return c.opendal_metadata_last_modified(self.inner);
}
};
pub const Code = enum(c.opendal_code) {
UNEXPECTED = c.OPENDAL_UNEXPECTED,
UNSUPPORTED = c.OPENDAL_UNSUPPORTED,
CONFIG_INVALID = c.OPENDAL_CONFIG_INVALID,
NOT_FOUND = c.OPENDAL_NOT_FOUND,
PERMISSION_DENIED = c.OPENDAL_PERMISSION_DENIED,
IS_A_DIRECTORY = c.OPENDAL_IS_A_DIRECTORY,
NOT_A_DIRECTORY = c.OPENDAL_NOT_A_DIRECTORY,
ALREADY_EXISTS = c.OPENDAL_ALREADY_EXISTS,
RATE_LIMITED = c.OPENDAL_RATE_LIMITED,
IS_SAME_FILE = c.OPENDAL_IS_SAME_FILE,
CONDITION_NOT_MATCH = c.OPENDAL_CONDITION_NOT_MATCH,
RANGE_NOT_SATISFIED = c.OPENDAL_RANGE_NOT_SATISFIED,
};
pub const OpendalError = error{
Unexpected,
Unsupported,
ConfigInvalid,
NotFound,
PermissionDenied,
IsDirectory,
IsNotDirectory,
AlreadyExists,
RateLimited,
IsSameFile,
ConditionNotMatch,
RangeNotSatisfied,
};
pub fn codeToError(code: c.opendal_code) OpendalError!void {
return switch (code) {
c.OPENDAL_UNEXPECTED => error.Unexpected,
c.OPENDAL_UNSUPPORTED => error.Unsupported,
c.OPENDAL_NOT_FOUND => error.NotFound,
c.OPENDAL_CONFIG_INVALID => error.ConfigInvalid,
c.OPENDAL_PERMISSION_DENIED => error.PermissionDenied,
c.OPENDAL_IS_A_DIRECTORY => error.IsDirectory,
c.OPENDAL_NOT_A_DIRECTORY => error.IsNotDirectory,
c.OPENDAL_ALREADY_EXISTS => error.AlreadyExists,
c.OPENDAL_RATE_LIMITED => error.RateLimited,
c.OPENDAL_IS_SAME_FILE => error.IsSameFile,
c.OPENDAL_CONDITION_NOT_MATCH => error.ConditionNotMatch,
c.OPENDAL_RANGE_NOT_SATISFIED => error.RangeNotSatisfied,
else => {},
};
}
pub fn errorToCode(err: OpendalError) c_int {
return switch (err) {
error.Unexpected => c.OPENDAL_UNEXPECTED,
error.Unsupported => c.OPENDAL_UNSUPPORTED,
error.ConfigInvalid => c.OPENDAL_CONFIG_INVALID,
error.NotFound => c.OPENDAL_NOT_FOUND,
error.PermissionDenied => c.OPENDAL_PERMISSION_DENIED,
error.IsDirectory => c.OPENDAL_IS_A_DIRECTORY,
error.IsNotDirectory => c.OPENDAL_NOT_A_DIRECTORY,
error.AlreadyExists => c.OPENDAL_ALREADY_EXISTS,
error.RateLimited => c.OPENDAL_RATE_LIMITED,
error.IsSameFile => c.OPENDAL_IS_SAME_FILE,
error.ConditionNotMatch => c.OPENDAL_CONDITION_NOT_MATCH,
error.RangeNotSatisfied => c.OPENDAL_RANGE_NOT_SATISFIED,
};
}
const std = @import("std");
const testing = std.testing;
test "Error Tests" {
// C code to Zig error
try testing.expectError(error.Unexpected, codeToError(c.OPENDAL_UNEXPECTED));
try testing.expectError(error.Unsupported, codeToError(c.OPENDAL_UNSUPPORTED));
try testing.expectError(error.ConfigInvalid, codeToError(c.OPENDAL_CONFIG_INVALID));
try testing.expectError(error.NotFound, codeToError(c.OPENDAL_NOT_FOUND));
try testing.expectError(error.PermissionDenied, codeToError(c.OPENDAL_PERMISSION_DENIED));
try testing.expectError(error.IsDirectory, codeToError(c.OPENDAL_IS_A_DIRECTORY));
try testing.expectError(error.IsNotDirectory, codeToError(c.OPENDAL_NOT_A_DIRECTORY));
try testing.expectError(error.AlreadyExists, codeToError(c.OPENDAL_ALREADY_EXISTS));
try testing.expectError(error.RateLimited, codeToError(c.OPENDAL_RATE_LIMITED));
try testing.expectError(error.IsSameFile, codeToError(c.OPENDAL_IS_SAME_FILE));
try testing.expectError(error.ConditionNotMatch, codeToError(c.OPENDAL_CONDITION_NOT_MATCH));
try testing.expectError(error.RangeNotSatisfied, codeToError(c.OPENDAL_RANGE_NOT_SATISFIED));
// Zig error to C code
try testing.expectEqual(c.OPENDAL_UNEXPECTED, errorToCode(error.Unexpected));
try testing.expectEqual(c.OPENDAL_UNSUPPORTED, errorToCode(error.Unsupported));
try testing.expectEqual(c.OPENDAL_CONFIG_INVALID, errorToCode(error.ConfigInvalid));
try testing.expectEqual(c.OPENDAL_NOT_FOUND, errorToCode(error.NotFound));
try testing.expectEqual(c.OPENDAL_PERMISSION_DENIED, errorToCode(error.PermissionDenied));
try testing.expectEqual(c.OPENDAL_IS_A_DIRECTORY, errorToCode(error.IsDirectory));
try testing.expectEqual(c.OPENDAL_NOT_A_DIRECTORY, errorToCode(error.IsNotDirectory));
try testing.expectEqual(c.OPENDAL_ALREADY_EXISTS, errorToCode(error.AlreadyExists));
try testing.expectEqual(c.OPENDAL_RATE_LIMITED, errorToCode(error.RateLimited));
try testing.expectEqual(c.OPENDAL_IS_SAME_FILE, errorToCode(error.IsSameFile));
try testing.expectEqual(c.OPENDAL_CONDITION_NOT_MATCH, errorToCode(error.ConditionNotMatch));
try testing.expectEqual(c.OPENDAL_RANGE_NOT_SATISFIED, errorToCode(error.RangeNotSatisfied));
}
test "Semantic Analyzer" {
testing.refAllDecls(@This());
}
test "operator basic operations" {
// Initialize a operator for "memory" backend, with no options
var op = try Operator.init("memory", null);
defer op.deinit();
// Prepare some data to be written
const data = "this_string_length_is_24";
// Write this into path "/testpath"
try op.write("/testpath", data);
defer _ = op.delete("/testpath") catch |err| {
std.debug.panic("Error deleting file: {}\n", .{err});
};
// We can read it out, make sure the data is the same
const read_bytes = try op.read("/testpath");
try testing.expectEqual(read_bytes.len, 24);
try testing.expectEqualStrings(read_bytes, data);
}
test "operator advanced operations" {
var op = try Operator.init("memory", null);
defer op.deinit();
// Test directory creation
try op.createDir("/testdir/");
try testing.expect(try op.exists("/testdir/"));
// Test file operations in directory
const data = "hello world";
try op.write("/testdir/file.txt", data);
try testing.expect(try op.exists("/testdir/file.txt"));
// Test metadata
var meta = try op.stat("/testdir/file.txt");
defer meta.deinit();
try testing.expect(meta.isFile());
try testing.expect(!meta.isDir());
try testing.expectEqual(meta.contentLength(), data.len);
// ================================================================================
// "operator advanced operations" - Unsupported
// ================================================================================
// /home/kassane/opendal/bindings/zig/src/opendal.zig:269:5: 0x103d9a0 in codeToError (test)
// return switch (code) {
// ^
// /home/kassane/opendal/bindings/zig/src/opendal.zig:113:13: 0x103f725 in copy (test)
// try codeToError(err.*.code);
// ^
// /home/kassane/opendal/bindings/zig/src/opendal.zig:385:5: 0x1040031 in test.operator advanced operations (test)
// try op.copy("/testdir/renamed.txt", "/testdir/copied.txt");
// ^
// operator advanced operations (0.99ms)
// 3 of 4 tests passed
// Test rename operation
// try op.rename("/testdir/file.txt", "/testdir/renamed.txt");
// try testing.expect(!try op.exists("/testdir/file.txt"));
// try testing.expect(try op.exists("/testdir/renamed.txt"));
// Test copy operation
// try op.copy("/testdir/renamed.txt", "/testdir/copied.txt");
// try testing.expect(try op.exists("/testdir/renamed.txt"));
// try testing.expect(try op.exists("/testdir/copied.txt"));
//============================================
// Test list operation
var lister = try op.list("/testdir/");
defer lister.deinit();
var count: usize = 0;
while (try lister.next()) |entry| {
// defer entry.deinit();
count += 1;
try testing.expect(entry.path().len > 0);
try testing.expect(entry.name().len > 0);
// std.log.debug("entry name: {s}", .{entry.name()});
// std.log.debug("entry path: {s}", .{entry.path()});
}
try testing.expectEqual(count, 2);
// Test delete operation
// try op.delete("/testdir/renamed.txt");
try testing.expect(!try op.exists("/testdir/renamed.txt"));
}
test "sync operations" {
var op = try Operator.init("memory", null);
defer op.deinit();
const data = [_]u8{ 1, 2, 3, 4, 5 };
// Test first path
try writeData(&op, "test_path", &data);
const read_bytes1 = try readData(&op, "test_path");
try testing.expectEqualSlices(u8, &data, read_bytes1);
}
// FIXME: test async operations
// test "async operations" {
// const coro = @import("libcoro");
// const allocator = std.testing.allocator;
// const stack = try coro.stackAlloc(allocator, null);
// defer allocator.free(stack);
// var op = try Operator.init("memory", null);
// defer op.deinit();
// const data = [_]u8{ 1, 2, 3, 4, 5 };
// // Test first path
// const write_frame1 = try coro.xasync(writeData, .{ &op, "test_path", &data }, stack);
// const read_frame1 = try coro.xasync(readData, .{ &op, "test_path" }, stack);
// try testing.expectEqual(write_frame1.status(), .Suspended);
// try testing.expectEqual(read_frame1.status(), .Suspended);
// try coro.xawait(write_frame1);
// const read_bytes1 = try coro.xawait(read_frame1);
// try testing.expectEqual(write_frame1.status(), .Done);
// try testing.expectEqual(read_frame1.status(), .Done);
// try testing.expectEqualSlices(u8, &data, read_bytes1);
// }
fn writeData(op: *Operator, path: []const u8, data: []const u8) !void {
try op.write(path, data);
}
fn readData(op: *Operator, path: []const u8) ![]const u8 {
return try op.read(path);
}