diff --git a/.github/actions/test_behavior_binding_dotnet/action.yaml b/.github/actions/test_behavior_binding_dotnet/action.yaml new file mode 100644 index 000000000000..5f43a0004afc --- /dev/null +++ b/.github/actions/test_behavior_binding_dotnet/action.yaml @@ -0,0 +1,51 @@ +# 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. + +name: Test Binding Dotnet +description: 'Test Dotnet binding with given setup and service' +inputs: + setup: + description: "The setup action for test" + service: + description: "The service to test" + feature: + description: "The feature to test" + +runs: + using: "composite" + steps: + - name: Setup + shell: bash + run: | + mkdir -p ./dynamic_test_binding_dotnet && + cat <./dynamic_test_binding_dotnet/action.yml + runs: + using: composite + steps: + - name: Setup Test + uses: ./.github/services/${{ inputs.service }}/${{ inputs.setup }} + - name: Run Test Binding Dotnet + shell: bash + working-directory: bindings/dotnet + run: | + cargo build + dotnet test -f net10.0 + env: + OPENDAL_TEST: ${{ inputs.service }} + EOF + - name: Run + uses: ./dynamic_test_binding_dotnet diff --git a/.github/scripts/test_behavior/plan.py b/.github/scripts/test_behavior/plan.py index 65651553eeb4..e8b6af4beb1a 100755 --- a/.github/scripts/test_behavior/plan.py +++ b/.github/scripts/test_behavior/plan.py @@ -31,7 +31,7 @@ # The project dir for opendal. PROJECT_DIR = GITHUB_DIR.parent -LANGUAGE_BINDING = ["java", "python", "nodejs", "go", "c", "cpp"] +LANGUAGE_BINDING = ["java", "python", "nodejs", "go", "c", "cpp", "dotnet"] INTEGRATIONS = ["object_store"] @@ -90,6 +90,8 @@ class Hint: binding_c: bool = field(default=False, init=False) # Is binding cpp affected? binding_cpp: bool = field(default=False, init=False) + # Is binding dotnet affected? + binding_dotnet: bool = field(default=False, init=False) # Is integration object_store affected ? integration_object_store: bool = field(default=False, init=False) @@ -153,12 +155,8 @@ def mark_service_affected(service: str) -> None: and not p.startswith("core/core/src/docs/") ): hint.core = True - hint.binding_java = True - hint.binding_python = True - hint.binding_nodejs = True - hint.binding_go = True - hint.binding_c = True - hint.binding_cpp = True + for language in LANGUAGE_BINDING: + setattr(hint, f"binding_{language}", True) for integration in INTEGRATIONS: setattr(hint, f"integration_{integration}", True) hint.all_service = True @@ -277,6 +275,14 @@ def generate_language_binding_cases( "rocksdb", ]] + # Remove invalid cases for dotnet. + if language == "dotnet": + cases = [v for v in cases if v["service"] not in [ + "hdfs", + "hdfs_native", + "rocksdb", + ]] + if os.getenv("GITHUB_IS_PUSH") == "true": return cases diff --git a/.github/workflows/ci_bindings_dotnet.yml b/.github/workflows/ci_bindings_dotnet.yml index 5ef5c6101228..1aa8e477da8c 100644 --- a/.github/workflows/ci_bindings_dotnet.yml +++ b/.github/workflows/ci_bindings_dotnet.yml @@ -92,3 +92,5 @@ jobs: run: | cargo build dotnet test -f ${{ matrix.dotnet-version == '8.0.x' && 'net8.0' || 'net10.0' }} + env: + OPENDAL_TEST: memory diff --git a/.github/workflows/test_behavior.yml b/.github/workflows/test_behavior.yml index 6d71a9a6bfb9..c7d2b52a1203 100644 --- a/.github/workflows/test_behavior.yml +++ b/.github/workflows/test_behavior.yml @@ -190,6 +190,20 @@ jobs: os: ${{ matrix.os }} cases: ${{ toJson(matrix.cases) }} + test_binding_dotnet: + name: binding_dotnet / ${{ matrix.os }} + needs: [ plan ] + if: fromJson(needs.plan.outputs.plan).components.binding_dotnet + secrets: inherit + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.plan.outputs.plan).binding_dotnet }} + uses: ./.github/workflows/test_behavior_binding_dotnet.yml + with: + os: ${{ matrix.os }} + cases: ${{ toJson(matrix.cases) }} + test_integration_object_store: name: integration_object_store / ${{ matrix.os }} diff --git a/.github/workflows/test_behavior_binding_dotnet.yml b/.github/workflows/test_behavior_binding_dotnet.yml new file mode 100644 index 000000000000..868432e1f893 --- /dev/null +++ b/.github/workflows/test_behavior_binding_dotnet.yml @@ -0,0 +1,63 @@ +# 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. + +name: Behavior Test Binding Dotnet + +on: + workflow_call: + inputs: + os: + required: true + type: string + cases: + required: true + type: string + +jobs: + test: + name: ${{ matrix.cases.service }} / ${{ matrix.cases.setup }} + runs-on: ${{ inputs.os }} + strategy: + fail-fast: false + matrix: + cases: ${{ fromJson(inputs.cases) }} + steps: + - uses: actions/checkout@v6 + - name: Setup dotnet toolchain + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.x' + - name: Setup Rust toolchain + uses: ./.github/actions/setup + with: + need-nextest: true + need-protoc: true + need-rocksdb: true + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup 1Password Connect + uses: 1password/load-secrets-action/configure@v3 + with: + connect-host: ${{ secrets.OP_CONNECT_HOST }} + connect-token: ${{ secrets.OP_CONNECT_TOKEN }} + + - name: Test Core + uses: ./.github/actions/test_behavior_binding_dotnet + with: + setup: ${{ matrix.cases.setup }} + service: ${{ matrix.cases.service }} + feature: ${{ matrix.cases.feature }} diff --git a/bindings/dotnet/Cargo.toml b/bindings/dotnet/Cargo.toml index 88956e7c6e32..771854721c04 100644 --- a/bindings/dotnet/Cargo.toml +++ b/bindings/dotnet/Cargo.toml @@ -60,6 +60,7 @@ opendal = { version = ">=0", path = "../../core", features = [ "services-webhdfs", "services-aliyun-drive", "services-cacache", + "services-compfs", "services-dashmap", "services-dropbox", "services-etcd", @@ -100,3 +101,13 @@ opendal = { version = ">=0", path = "../../core", features = [ "services-yandex-disk", ] } tokio = { version = "1.49.0", features = ["full"] } + +# This is not optimal. See also the Cargo issue: +# https://github.com/rust-lang/cargo/issues/1197#issuecomment-1641086954 +[target.'cfg(unix)'.dependencies.opendal] +features = [ + # Depend on unix-only dependency stacks in current implementations. + "services-monoiofs", + "services-sftp", +] +path = "../../core" diff --git a/bindings/dotnet/DotOpenDAL.Tests/BlockingOperatorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/BehaviorOperatorCollection.cs similarity index 72% rename from bindings/dotnet/DotOpenDAL.Tests/BlockingOperatorTest.cs rename to bindings/dotnet/DotOpenDAL.Tests/Behavior/BehaviorOperatorCollection.cs index aa8f2f2d6bab..f2beb49df983 100644 --- a/bindings/dotnet/DotOpenDAL.Tests/BlockingOperatorTest.cs +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/BehaviorOperatorCollection.cs @@ -19,16 +19,7 @@ namespace DotOpenDAL.Tests; -public class BlockingOperatorTest +[CollectionDefinition("BehaviorOperator", DisableParallelization = false)] +public sealed class BehaviorOperatorCollection : ICollectionFixture { - [Fact] - public void TestReadWrite() - { - var op = new BlockingOperator(); - var content = "123456"; - Assert.NotEqual(op.Op, IntPtr.Zero); - op.Write("test", content); - var result = op.Read("test"); - Assert.Equal(content, result); - } } diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/BehaviorOperatorFixture.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/BehaviorOperatorFixture.cs new file mode 100644 index 000000000000..c4a30ed02e71 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/BehaviorOperatorFixture.cs @@ -0,0 +1,114 @@ +/* + * 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. + */ + +using System.Collections; + +namespace DotOpenDAL.Tests; + +public sealed class BehaviorOperatorFixture : IDisposable +{ + private readonly Operator? op; + + public string? Scheme { get; } + + public BehaviorOperatorFixture() + { + Scheme = Environment.GetEnvironmentVariable("OPENDAL_TEST"); + if (string.IsNullOrWhiteSpace(Scheme)) + { + return; + } + + var scheme = Scheme.ToLowerInvariant().Replace('_', '-'); + var options = BuildConfigFromEnvironment(Scheme); + + // Align with other bindings: isolate behavior tests with random roots by default. + if (!IsRandomRootDisabled()) + { + var baseRoot = options.TryGetValue("root", out var root) && !string.IsNullOrWhiteSpace(root) + ? root! + : "/"; + options["root"] = BuildRandomRoot(baseRoot); + } + + op = new Operator(scheme, options); + } + + public bool IsEnabled => op is not null; + + public Operator Op => op ?? throw new InvalidOperationException("Behavior operator is not initialized."); + + public Capability Capability => Op.Info.FullCapability; + + public void Dispose() + { + op?.Dispose(); + } + + private static Dictionary BuildConfigFromEnvironment(string service) + { + var variables = Environment.GetEnvironmentVariables(); + var prefix = $"opendal_{service.ToLowerInvariant()}_"; + var config = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (DictionaryEntry entry in variables) + { + var key = entry.Key?.ToString(); + var value = entry.Value?.ToString(); + if (string.IsNullOrWhiteSpace(key) || value is null) + { + continue; + } + + var normalized = key.ToLowerInvariant(); + if (!normalized.StartsWith(prefix, StringComparison.Ordinal)) + { + continue; + } + + config[normalized[prefix.Length..]] = value; + } + + return config; + } + + private static bool IsRandomRootDisabled() + { + return string.Equals( + Environment.GetEnvironmentVariable("OPENDAL_DISABLE_RANDOM_ROOT"), + "true", + StringComparison.OrdinalIgnoreCase); + } + + private static string BuildRandomRoot(string baseRoot) + { + var trimmed = baseRoot.Trim(); + if (trimmed.Length == 0) + { + trimmed = "/"; + } + + if (!trimmed.EndsWith('/')) + { + trimmed += "/"; + } + + return $"{trimmed}{Guid.NewGuid():N}/"; + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/BehaviorTestBase.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/BehaviorTestBase.cs new file mode 100644 index 000000000000..ef30c74df52e --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/BehaviorTestBase.cs @@ -0,0 +1,61 @@ +/* + * 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. + */ + +using System.Security.Cryptography; + +namespace DotOpenDAL.Tests; + +public abstract class BehaviorTestBase +{ + private readonly BehaviorOperatorFixture fixture; + + protected BehaviorTestBase(BehaviorOperatorFixture fixture) + { + this.fixture = fixture; + } + + protected bool IsEnabled => fixture.IsEnabled; + + protected Operator Op => fixture.Op; + + protected Capability Capability => fixture.Capability; + + protected bool Supports(Func predicate) + { + return IsEnabled && predicate(Capability); + } + + protected static byte[] RandomBytes(int size) + { + var bytes = new byte[size]; + RandomNumberGenerator.Fill(bytes); + return bytes; + } + + protected string NewPath(string prefix) + { + return $"dotnet-behavior/{prefix}-{Guid.NewGuid():N}"; + } + + protected static bool IsMissingError(OpenDALException ex) + { + return ex.Code == ErrorCode.NotFound || ex.Code == ErrorCode.NotADirectory; + } + +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/CopyBehaviorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/CopyBehaviorTest.cs new file mode 100644 index 000000000000..be0575dd07ff --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/CopyBehaviorTest.cs @@ -0,0 +1,67 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Tests; + +[Collection("BehaviorOperator")] +public sealed class CopyBehaviorTest : BehaviorTestBase +{ + private static CancellationToken CT => TestContext.Current.CancellationToken; + + public CopyBehaviorTest(BehaviorOperatorFixture fixture) + : base(fixture) + { + } + + [Fact] + public void CopyBehavior_CreatesTargetWithSameContent() + { + if (!Supports(c => c.Copy && c.Read && c.Write)) + { + return; + } + + var sourcePath = NewPath("copy-source"); + var targetPath = NewPath("copy-target"); + var content = RandomBytes(256); + + Op.Write(sourcePath, content); + Op.Copy(sourcePath, targetPath); + + Assert.Equal(content, Op.Read(targetPath)); + } + + [Fact] + public async Task CopyBehavior_CreatesTargetWithSameContentAsync() + { + if (!Supports(c => c.Copy && c.Read && c.Write)) + { + return; + } + + var sourcePath = NewPath("copy-source-async"); + var targetPath = NewPath("copy-target-async"); + var content = RandomBytes(256); + + await Op.WriteAsync(sourcePath, content, CT); + await Op.CopyAsync(sourcePath, targetPath, CT); + + Assert.Equal(content, await Op.ReadAsync(targetPath, CT)); + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/CreateDirBehaviorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/CreateDirBehaviorTest.cs new file mode 100644 index 000000000000..84aad59cdefa --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/CreateDirBehaviorTest.cs @@ -0,0 +1,63 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Tests; + +[Collection("BehaviorOperator")] +public sealed class CreateDirBehaviorTest : BehaviorTestBase +{ + private static CancellationToken CT => TestContext.Current.CancellationToken; + + public CreateDirBehaviorTest(BehaviorOperatorFixture fixture) + : base(fixture) + { + } + + [Fact] + public void CreateDirBehavior_CreatesDirectoryEntry() + { + if (!Supports(c => c.CreateDir && c.Stat)) + { + return; + } + + var dirPath = NewPath("mkdir") + "/"; + + Op.CreateDir(dirPath); + var meta = Op.Stat(dirPath); + + Assert.True(meta.IsDir); + } + + [Fact] + public async Task CreateDirBehavior_CreatesDirectoryEntryAsync() + { + if (!Supports(c => c.CreateDir && c.Stat)) + { + return; + } + + var dirPath = NewPath("mkdir-async") + "/"; + + await Op.CreateDirAsync(dirPath, CT); + var meta = await Op.StatAsync(dirPath, null, CT); + + Assert.True(meta.IsDir); + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/DeleteBehaviorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/DeleteBehaviorTest.cs new file mode 100644 index 000000000000..10f2a16e4a9f --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/DeleteBehaviorTest.cs @@ -0,0 +1,87 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Tests; + +[Collection("BehaviorOperator")] +public sealed class DeleteBehaviorTest : BehaviorTestBase +{ + private static CancellationToken CT => TestContext.Current.CancellationToken; + + public DeleteBehaviorTest(BehaviorOperatorFixture fixture) + : base(fixture) + { + } + + [Fact] + public void DeleteBehavior_RemovesObject() + { + if (!Supports(c => c.Delete && c.Read && c.Write)) + { + return; + } + + var path = NewPath("delete"); + + Op.Write(path, RandomBytes(12)); + Op.Delete(path); + + var ex = Assert.Throws(() => Op.Read(path)); + Assert.True(IsMissingError(ex)); + } + + [Fact] + public async Task DeleteBehavior_RemovesObjectAsync() + { + if (!Supports(c => c.Delete && c.Read && c.Write)) + { + return; + } + + var path = NewPath("delete-async"); + + await Op.WriteAsync(path, RandomBytes(12), CT); + await Op.DeleteAsync(path, CT); + + var ex = await Assert.ThrowsAsync(() => Op.ReadAsync(path, CT)); + Assert.True(IsMissingError(ex)); + } + + [Fact] + public void DeleteBehavior_DeletingMissingPath_IsAllowed() + { + if (!Supports(c => c.Delete)) + { + return; + } + + Op.Delete(NewPath("delete-missing")); + } + + [Fact] + public async Task DeleteBehavior_DeletingMissingPath_IsAllowedAsync() + { + if (!Supports(c => c.Delete)) + { + return; + } + + await Op.DeleteAsync(NewPath("delete-missing-async"), CT); + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/ListBehaviorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/ListBehaviorTest.cs new file mode 100644 index 000000000000..0fb4f1e884f1 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/ListBehaviorTest.cs @@ -0,0 +1,125 @@ +/* + * 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. + */ + +using DotOpenDAL.Options; + +namespace DotOpenDAL.Tests; + +[Collection("BehaviorOperator")] +public sealed class ListBehaviorTest : BehaviorTestBase +{ + private static CancellationToken CT => TestContext.Current.CancellationToken; + + public ListBehaviorTest(BehaviorOperatorFixture fixture) + : base(fixture) + { + } + + [Fact] + public void ListBehavior_ListsEntriesUnderPrefix() + { + if (!Supports(c => c.List && c.Write && c.CreateDir)) + { + return; + } + + var dir = NewPath("list") + "/"; + var a = $"{dir}a.txt"; + var b = $"{dir}nested/b.txt"; + + Op.CreateDir(dir); + Op.Write(a, RandomBytes(10)); + Op.Write(b, RandomBytes(20)); + + var entries = Op.List(dir, new ListOptions { Recursive = true }); + + Assert.Contains(entries, e => e.Path.EndsWith("a.txt", StringComparison.Ordinal)); + Assert.Contains(entries, e => e.Path.EndsWith("nested/b.txt", StringComparison.Ordinal)); + } + + [Fact] + public async Task ListBehavior_ListsEntriesUnderPrefixAsync() + { + if (!Supports(c => c.List && c.Write && c.CreateDir)) + { + return; + } + + var dir = NewPath("list-async") + "/"; + var a = $"{dir}a.txt"; + var b = $"{dir}nested/b.txt"; + + await Op.CreateDirAsync(dir, CT); + await Op.WriteAsync(a, RandomBytes(10), CT); + await Op.WriteAsync(b, RandomBytes(20), CT); + + var entries = await Op.ListAsync(dir, new ListOptions { Recursive = true }, CT); + + Assert.Contains(entries, e => e.Path.EndsWith("a.txt", StringComparison.Ordinal)); + Assert.Contains(entries, e => e.Path.EndsWith("nested/b.txt", StringComparison.Ordinal)); + } + + [Fact] + public async Task ListBehavior_WithLimit_ReturnsAtMostRequestedSizeAsync() + { + if (!Supports(c => c.List && c.ListWithLimit && c.Write)) + { + return; + } + + var dir = NewPath("list-limit") + "/"; + var files = new List(); + for (var i = 0; i < 6; i++) + { + var path = $"{dir}file-{i}.txt"; + files.Add(path); + await Op.WriteAsync(path, RandomBytes(8), CT); + } + + var entries = await Op.ListAsync(dir, new ListOptions { Recursive = true, Limit = 3 }, CT); + foreach (var file in files) + { + Assert.Contains(entries, e => e.Path == file); + } + } + + [Fact] + public void ListBehavior_WithLimit_ReturnsAtMostRequestedSize() + { + if (!Supports(c => c.List && c.ListWithLimit && c.Write)) + { + return; + } + + var dir = NewPath("list-limit-sync") + "/"; + var files = new List(); + for (var i = 0; i < 6; i++) + { + var path = $"{dir}file-{i}.txt"; + files.Add(path); + Op.Write(path, RandomBytes(8)); + } + + var entries = Op.List(dir, new ListOptions { Recursive = true, Limit = 3 }); + foreach (var file in files) + { + Assert.Contains(entries, e => e.Path == file); + } + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/OperatorBehaviorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/OperatorBehaviorTest.cs new file mode 100644 index 000000000000..1e352a48090c --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/OperatorBehaviorTest.cs @@ -0,0 +1,167 @@ +/* + * 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. + */ + +using DotOpenDAL.Options; + +namespace DotOpenDAL.Tests; + +[Collection("BehaviorOperator")] +public sealed class OperatorBehaviorTest : BehaviorTestBase +{ + private static CancellationToken CT => TestContext.Current.CancellationToken; + + public OperatorBehaviorTest(BehaviorOperatorFixture fixture) + : base(fixture) + { + } + + [Fact] + public void OperatorBehavior_Duplicate_SharesBackendState() + { + if (!Supports(c => c.Read && c.Write)) + { + return; + } + + using var duplicated = Op.Duplicate(); + + var path = NewPath("duplicate"); + var content = RandomBytes(32); + + duplicated.Write(path, content); + Assert.Equal(content, Op.Read(path)); + } + + [Fact] + public void OperatorBehavior_StreamRoundtrip_Works() + { + if (!Supports(c => c.Read && c.Write)) + { + return; + } + + var path = NewPath("stream-sync"); + var content = RandomBytes(64); + + using (var output = Op.OpenWriteStream(path)) + { + output.Write(content, 0, content.Length); + output.Flush(); + } + + using var input = Op.OpenReadStream(path); + var buffer = new byte[content.Length]; + var read = input.Read(buffer, 0, buffer.Length); + + Assert.Equal(content.Length, read); + Assert.Equal(content, buffer); + } + + [Fact] + public async Task OperatorBehavior_StreamRoundtripAsync_Works() + { + if (!Supports(c => c.Read && c.Write)) + { + return; + } + + var path = NewPath("stream-async"); + var content = RandomBytes(64); + + using (var output = Op.OpenWriteStream(path)) + { + await output.WriteAsync(content, 0, content.Length, CT); + await output.FlushAsync(CT); + } + + using var input = Op.OpenReadStream(path); + var buffer = new byte[content.Length]; + var read = await input.ReadAsync(buffer, 0, buffer.Length, CT); + + Assert.Equal(content.Length, read); + Assert.Equal(content, buffer); + } + + [Fact] + public void OperatorBehavior_OpenReadStream_WithRange_ReadsSelectedSlice() + { + if (!Supports(c => c.Read && c.Write)) + { + return; + } + + var path = NewPath("stream-range"); + Op.Write(path, System.Text.Encoding.UTF8.GetBytes("0123456789")); + + using var input = Op.OpenReadStream(path, new ReadOptions + { + Offset = 3, + Length = 4, + }); + + var buffer = new byte[8]; + var read = input.Read(buffer, 0, buffer.Length); + + Assert.Equal(4, read); + Assert.Equal("3456", System.Text.Encoding.UTF8.GetString(buffer, 0, read)); + } + + [Fact] + public async Task OperatorBehavior_CancelAfterDispatch_DoesNotBreakSubsequentOperations() + { + if (!Supports(c => c.Read && c.Write)) + { + return; + } + + var path = NewPath("cancel-seed"); + await Op.WriteAsync(path, System.Text.Encoding.UTF8.GetBytes("seed-content"), CT); + + using (var writeCts = new CancellationTokenSource()) + { + var writeTask = Op.WriteAsync(NewPath("cancel-write"), [1, 2, 3, 4], writeCts.Token); + writeCts.Cancel(); + + try + { + await writeTask; + } + catch (OperationCanceledException) + { + } + } + + using (var readCts = new CancellationTokenSource()) + { + var readTask = Op.ReadAsync(path, readCts.Token); + readCts.Cancel(); + + try + { + _ = await readTask; + } + catch (OperationCanceledException) + { + } + } + + var stableRead = await Op.ReadAsync(path, CT); + Assert.Equal("seed-content", System.Text.Encoding.UTF8.GetString(stableRead)); + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/PresignBehaviorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/PresignBehaviorTest.cs new file mode 100644 index 000000000000..f06315063d20 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/PresignBehaviorTest.cs @@ -0,0 +1,71 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Tests; + +[Collection("BehaviorOperator")] +public sealed class PresignBehaviorTest : BehaviorTestBase +{ + private static CancellationToken CT => TestContext.Current.CancellationToken; + + public PresignBehaviorTest(BehaviorOperatorFixture fixture) + : base(fixture) + { + } + + [Fact] + public async Task PresignBehavior_ReadWriteStatDelete_AreGeneratedWhenSupported() + { + if (!IsEnabled) + { + return; + } + + var path = NewPath("presign"); + var expire = TimeSpan.FromMinutes(10); + + if (Capability.PresignRead) + { + var req = await Op.PresignReadAsync(path, expire, CT); + Assert.False(string.IsNullOrWhiteSpace(req.Method)); + Assert.False(string.IsNullOrWhiteSpace(req.Uri)); + } + + if (Capability.PresignWrite) + { + var req = await Op.PresignWriteAsync(path, expire, CT); + Assert.False(string.IsNullOrWhiteSpace(req.Method)); + Assert.False(string.IsNullOrWhiteSpace(req.Uri)); + } + + if (Capability.PresignStat) + { + var req = await Op.PresignStatAsync(path, expire, CT); + Assert.False(string.IsNullOrWhiteSpace(req.Method)); + Assert.False(string.IsNullOrWhiteSpace(req.Uri)); + } + + if (Capability.PresignDelete) + { + var req = await Op.PresignDeleteAsync(path, expire, CT); + Assert.False(string.IsNullOrWhiteSpace(req.Method)); + Assert.False(string.IsNullOrWhiteSpace(req.Uri)); + } + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/ReadBehaviorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/ReadBehaviorTest.cs new file mode 100644 index 000000000000..e798b240c80f --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/ReadBehaviorTest.cs @@ -0,0 +1,127 @@ +/* + * 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. + */ + +using DotOpenDAL.Options; + +namespace DotOpenDAL.Tests; + +[Collection("BehaviorOperator")] +public sealed class ReadBehaviorTest : BehaviorTestBase +{ + private static CancellationToken CT => TestContext.Current.CancellationToken; + + public ReadBehaviorTest(BehaviorOperatorFixture fixture) + : base(fixture) + { + } + + [Fact] + public void ReadBehavior_ReadsWrittenData() + { + if (!Supports(c => c.Read && c.Write)) + { + return; + } + + var path = NewPath("read-sync"); + var content = RandomBytes(2048); + + Op.Write(path, content); + var actual = Op.Read(path); + + Assert.Equal(content, actual); + } + + [Fact] + public async Task ReadBehavior_ReadsWrittenDataAsync() + { + if (!Supports(c => c.Read && c.Write)) + { + return; + } + + var path = NewPath("read-async"); + var content = RandomBytes(2048); + + await Op.WriteAsync(path, content, CT); + var actual = await Op.ReadAsync(path, CT); + + Assert.Equal(content, actual); + } + + [Fact] + public void ReadBehavior_WithRange_ReturnsExpectedBytes() + { + if (!Supports(c => c.Read && c.Write)) + { + return; + } + + var path = NewPath("read-range"); + var content = RandomBytes(512); + + Op.Write(path, content); + var partial = Op.Read(path, new ReadOptions { Offset = 10, Length = 100 }); + + Assert.Equal(content.AsSpan(10, 100).ToArray(), partial); + } + + [Fact] + public async Task ReadBehavior_WithRange_ReturnsExpectedBytesAsync() + { + if (!Supports(c => c.Read && c.Write)) + { + return; + } + + var path = NewPath("read-range-async"); + var content = RandomBytes(512); + + await Op.WriteAsync(path, content, CT); + var partial = await Op.ReadAsync(path, new ReadOptions { Offset = 10, Length = 100 }, CT); + + Assert.Equal(content.AsSpan(10, 100).ToArray(), partial); + } + + [Fact] + public void ReadBehavior_MissingPath_ReturnsNotFound() + { + if (!Supports(c => c.Read)) + { + return; + } + + var ex = Assert.Throws(() => Op.Read(NewPath("missing"))); + + Assert.True(IsMissingError(ex)); + } + + [Fact] + public async Task ReadBehavior_MissingPath_ReturnsNotFoundAsync() + { + if (!Supports(c => c.Read)) + { + return; + } + + var ex = await Assert.ThrowsAsync(() => Op.ReadAsync(NewPath("missing-async"), CT)); + + Assert.True(IsMissingError(ex)); + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/RemoveAllBehaviorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/RemoveAllBehaviorTest.cs new file mode 100644 index 000000000000..64f9f2725c2d --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/RemoveAllBehaviorTest.cs @@ -0,0 +1,67 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Tests; + +[Collection("BehaviorOperator")] +public sealed class RemoveAllBehaviorTest : BehaviorTestBase +{ + private static CancellationToken CT => TestContext.Current.CancellationToken; + + public RemoveAllBehaviorTest(BehaviorOperatorFixture fixture) + : base(fixture) + { + } + + [Fact] + public void RemoveAllBehavior_RemovesRecursiveTree() + { + if (!Supports(c => c.DeleteWithRecursive && c.Write && c.List)) + { + return; + } + + var dir = NewPath("remove-all") + "/"; + + Op.Write($"{dir}a.txt", RandomBytes(16)); + Op.Write($"{dir}nested/b.txt", RandomBytes(16)); + Op.RemoveAll(dir); + + var entries = Op.List(dir); + Assert.Empty(entries); + } + + [Fact] + public async Task RemoveAllBehavior_RemovesRecursiveTreeAsync() + { + if (!Supports(c => c.DeleteWithRecursive && c.Write && c.List)) + { + return; + } + + var dir = NewPath("remove-all-async") + "/"; + + await Op.WriteAsync($"{dir}a.txt", RandomBytes(16), CT); + await Op.WriteAsync($"{dir}nested/b.txt", RandomBytes(16), CT); + await Op.RemoveAllAsync(dir, CT); + + var entries = await Op.ListAsync(dir, new DotOpenDAL.Options.ListOptions(), CT); + Assert.Empty(entries); + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/RenameBehaviorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/RenameBehaviorTest.cs new file mode 100644 index 000000000000..3b2ec719443f --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/RenameBehaviorTest.cs @@ -0,0 +1,71 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Tests; + +[Collection("BehaviorOperator")] +public sealed class RenameBehaviorTest : BehaviorTestBase +{ + private static CancellationToken CT => TestContext.Current.CancellationToken; + + public RenameBehaviorTest(BehaviorOperatorFixture fixture) + : base(fixture) + { + } + + [Fact] + public void RenameBehavior_MovesContentToTargetPath() + { + if (!Supports(c => c.Rename && c.Read && c.Write)) + { + return; + } + + var sourcePath = NewPath("rename-source"); + var targetPath = NewPath("rename-target"); + var content = RandomBytes(111); + + Op.Write(sourcePath, content); + Op.Rename(sourcePath, targetPath); + + Assert.Equal(content, Op.Read(targetPath)); + var ex = Assert.Throws(() => Op.Read(sourcePath)); + Assert.True(IsMissingError(ex)); + } + + [Fact] + public async Task RenameBehavior_MovesContentToTargetPathAsync() + { + if (!Supports(c => c.Rename && c.Read && c.Write)) + { + return; + } + + var sourcePath = NewPath("rename-source-async"); + var targetPath = NewPath("rename-target-async"); + var content = RandomBytes(111); + + await Op.WriteAsync(sourcePath, content, CT); + await Op.RenameAsync(sourcePath, targetPath, CT); + + Assert.Equal(content, await Op.ReadAsync(targetPath, CT)); + var ex = await Assert.ThrowsAsync(() => Op.ReadAsync(sourcePath, CT)); + Assert.True(IsMissingError(ex)); + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/StatBehaviorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/StatBehaviorTest.cs new file mode 100644 index 000000000000..4576a03e6873 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/StatBehaviorTest.cs @@ -0,0 +1,110 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Tests; + +[Collection("BehaviorOperator")] +public sealed class StatBehaviorTest : BehaviorTestBase +{ + private static CancellationToken CT => TestContext.Current.CancellationToken; + + public StatBehaviorTest(BehaviorOperatorFixture fixture) + : base(fixture) + { + } + + [Fact] + public void StatBehavior_ReturnsFileMetadata() + { + if (!Supports(c => c.Stat && c.Write)) + { + return; + } + + var path = NewPath("stat-file"); + var content = RandomBytes(333); + + Op.Write(path, content); + var meta = Op.Stat(path); + + Assert.True(meta.IsFile); + Assert.Equal((ulong)content.Length, meta.ContentLength); + } + + [Fact] + public async Task StatBehavior_ReturnsFileMetadataAsync() + { + if (!Supports(c => c.Stat && c.Write)) + { + return; + } + + var path = NewPath("stat-file-async"); + var content = RandomBytes(333); + + await Op.WriteAsync(path, content, CT); + var meta = await Op.StatAsync(path, null, CT); + + Assert.True(meta.IsFile); + Assert.Equal((ulong)content.Length, meta.ContentLength); + } + + [Fact] + public void StatBehavior_MissingPath_ReturnsNotFound() + { + if (!Supports(c => c.Stat)) + { + return; + } + + var ex = Assert.Throws(() => Op.Stat(NewPath("stat-missing"))); + + Assert.True(IsMissingError(ex)); + } + + [Fact] + public async Task StatBehavior_MissingPath_ReturnsNotFoundAsync() + { + if (!Supports(c => c.Stat)) + { + return; + } + + var ex = await Assert.ThrowsAsync(() => Op.StatAsync(NewPath("stat-missing-async"), null, CT)); + + Assert.True(IsMissingError(ex)); + } + + [Fact] + public void StatBehavior_IfModifiedSince_ReturnsConditionNotMatchWhenUnsupportedByTime() + { + if (!Supports(c => c.Stat && c.Write && c.StatWithIfModifiedSince)) + { + return; + } + + var path = NewPath("stat-if-modified-since"); + Op.Write(path, RandomBytes(10)); + + var ex = Assert.Throws(() => + Op.Stat(path, new DotOpenDAL.Options.StatOptions { IfModifiedSince = DateTimeOffset.UtcNow.AddDays(1) })); + + Assert.Equal(ErrorCode.ConditionNotMatch, ex.Code); + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/Behavior/WriteBehaviorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/Behavior/WriteBehaviorTest.cs new file mode 100644 index 000000000000..4dee357e44d0 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/Behavior/WriteBehaviorTest.cs @@ -0,0 +1,139 @@ +/* + * 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. + */ + +using DotOpenDAL.Options; + +namespace DotOpenDAL.Tests; + +[Collection("BehaviorOperator")] +public sealed class WriteBehaviorTest : BehaviorTestBase +{ + private static CancellationToken CT => TestContext.Current.CancellationToken; + + public WriteBehaviorTest(BehaviorOperatorFixture fixture) + : base(fixture) + { + } + + [Fact] + public void WriteBehavior_BasicRoundtrip() + { + if (!Supports(c => c.Write && c.Read)) + { + return; + } + + var path = NewPath("write"); + var content = RandomBytes(1024); + + Op.Write(path, content); + var actual = Op.Read(path); + + Assert.Equal(content, actual); + } + + [Fact] + public async Task WriteBehavior_BasicRoundtripAsync() + { + if (!Supports(c => c.Write && c.Read)) + { + return; + } + + var path = NewPath("write-async"); + var content = RandomBytes(1024); + + await Op.WriteAsync(path, content, CT); + var actual = await Op.ReadAsync(path, CT); + + Assert.Equal(content, actual); + } + + [Fact] + public void WriteBehavior_IfNotExists_RejectsOverwrite() + { + if (!Supports(c => c.Write && c.Read && c.WriteWithIfNotExists)) + { + return; + } + + var path = NewPath("write-if-not-exists"); + var first = RandomBytes(128); + var second = RandomBytes(64); + + Op.Write(path, first); + + var ex = Assert.Throws(() => + Op.Write(path, second, new WriteOptions { IfNotExists = true })); + + Assert.Contains(ex.Code, new[] { ErrorCode.ConditionNotMatch, ErrorCode.AlreadyExists }); + Assert.Equal(first, Op.Read(path)); + } + + [Fact] + public async Task WriteBehavior_IfNotExists_RejectsOverwriteAsync() + { + if (!Supports(c => c.Write && c.Read && c.WriteWithIfNotExists)) + { + return; + } + + var path = NewPath("write-if-not-exists-async"); + var first = RandomBytes(128); + var second = RandomBytes(64); + + await Op.WriteAsync(path, first, CT); + + var ex = await Assert.ThrowsAsync(() => + Op.WriteAsync(path, second, new WriteOptions { IfNotExists = true }, CT)); + + Assert.Contains(ex.Code, new[] { ErrorCode.ConditionNotMatch, ErrorCode.AlreadyExists }); + Assert.Equal(first, await Op.ReadAsync(path, CT)); + } + + [Fact] + public void WriteBehavior_Append_AppendsWhenSupported() + { + if (!Supports(c => c.Write && c.Read && c.WriteCanAppend)) + { + return; + } + + var path = NewPath("write-append"); + Op.Write(path, System.Text.Encoding.UTF8.GetBytes("a"), new WriteOptions { Append = true }); + Op.Write(path, System.Text.Encoding.UTF8.GetBytes("b"), new WriteOptions { Append = true }); + + Assert.Equal("ab", System.Text.Encoding.UTF8.GetString(Op.Read(path))); + } + + [Fact] + public async Task WriteBehavior_Append_AppendsWhenSupportedAsync() + { + if (!Supports(c => c.Write && c.Read && c.WriteCanAppend)) + { + return; + } + + var path = NewPath("write-append-async"); + await Op.WriteAsync(path, System.Text.Encoding.UTF8.GetBytes("a"), new WriteOptions { Append = true }, CT); + await Op.WriteAsync(path, System.Text.Encoding.UTF8.GetBytes("b"), new WriteOptions { Append = true }, CT); + + Assert.Equal("ab", System.Text.Encoding.UTF8.GetString(await Op.ReadAsync(path, CT))); + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/ConstructorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/ConstructorTest.cs new file mode 100644 index 000000000000..3b15a398d5db --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/ConstructorTest.cs @@ -0,0 +1,102 @@ +/* + * 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. + */ + +using DotOpenDAL.ServiceConfig; + +namespace DotOpenDAL.Tests; + +public class ConstructorTest +{ + [Fact] + public void Constructor_SchemeIsEmpty_ThrowsArgumentException() + { + var ex = Assert.Throws(() => new Operator("")); + + Assert.Contains("scheme", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void Constructor_SchemeIsWhitespace_ThrowsArgumentException() + { + var ex = Assert.Throws(() => new Operator(" ")); + + Assert.Contains("scheme", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void Dictionary_NullKey_ThrowsArgumentNullException() + { + Assert.Throws(() => + { + var options = new Dictionary + { + [null!] = "value", + }; + _ = options; + }); + } + + [Fact] + public void Constructor_OptionValueIsNull_ThrowsConfigInvalid() + { + var options = new Dictionary + { + ["root"] = null!, + }; + + var ex = Assert.Throws(() => new Operator("memory", options)); + + Assert.Equal(ErrorCode.ConfigInvalid, ex.Code); + Assert.Contains("value at index 0", ex.Message); + } + + [Fact] + public void Constructor_InvalidScheme_ThrowsUnsupported() + { + var ex = Assert.Throws(() => new Operator("invalid-service")); + + Assert.Equal(ErrorCode.Unsupported, ex.Code); + } + + [Fact] + public void Constructor_OptionsMapProvided_ConstructsOperator() + { + var options = new Dictionary + { + ["root"] = "/tmp", + }; + + using var op = new Operator("memory", options); + + Assert.NotEqual(IntPtr.Zero, op.Op); + } + + [Fact] + public void Constructor_ServiceConfigProvided_ConstructsOperator() + { + var config = new MemoryServiceConfig + { + Root = "/tmp", + }; + + using var op = new Operator(config); + + Assert.NotEqual(IntPtr.Zero, op.Op); + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/DotOpenDAL.Tests.csproj b/bindings/dotnet/DotOpenDAL.Tests/DotOpenDAL.Tests.csproj index 0efccf26f80f..741eabd3d6de 100644 --- a/bindings/dotnet/DotOpenDAL.Tests/DotOpenDAL.Tests.csproj +++ b/bindings/dotnet/DotOpenDAL.Tests/DotOpenDAL.Tests.csproj @@ -4,6 +4,7 @@ net8.0;net10.0 enable enable + Exe false true @@ -12,14 +13,27 @@ + + + + PreserveNewest + + + + + PreserveNewest + + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/bindings/dotnet/DotOpenDAL.Tests/ExecutorTest.cs b/bindings/dotnet/DotOpenDAL.Tests/ExecutorTest.cs new file mode 100644 index 000000000000..b9f3b9b4cbb8 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/ExecutorTest.cs @@ -0,0 +1,108 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Tests; + +public class ExecutorTest +{ + private static CancellationToken CT => TestContext.Current.CancellationToken; + + [Fact] + public void CreateExecutor_InvalidCores_ThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => new Executor(0)); + } + + [Fact] + public void ReadWrite_WithDedicatedExecutor_RoundTripsSuccessfully() + { + using var executor = new Executor(1); + using var op = new Operator("memory"); + var content = System.Text.Encoding.UTF8.GetBytes("executor-content"); + + op.Write("executor-sync", content, executor); + var read = op.Read("executor-sync", executor); + + Assert.Equal(content, read); + } + + [Fact] + public async Task ReadWriteAsync_WithDedicatedExecutor_RoundTripsSuccessfully() + { + using var executor = new Executor(1); + using var op = new Operator("memory"); + var content = System.Text.Encoding.UTF8.GetBytes("executor-async-content"); + + await op.WriteAsync("executor-async", content, executor, CT); + var read = await op.ReadAsync("executor-async", executor, CT); + + Assert.Equal(content, read); + } + + [Fact] + public async Task OperatorCalls_WithDisposedExecutor_ThrowObjectDisposedException() + { + var executor = new Executor(1); + using var op = new Operator("memory"); + var content = new byte[] { 1, 2, 3 }; + executor.Dispose(); + + Assert.Throws(() => op.Write("disposed-executor", content, executor)); + Assert.Throws(() => op.Read("disposed-executor", executor)); + await Assert.ThrowsAsync(() => op.WriteAsync("disposed-executor", content, executor, CT)); + await Assert.ThrowsAsync(() => op.ReadAsync("disposed-executor", executor, CT)); + } + + [Fact] + public void ReadWrite_WithProcessorCountThreads_RoundTripsSuccessfully() + { + var threads = Math.Max(1, Environment.ProcessorCount); + using var executor = new Executor(threads); + using var op = new Operator("memory"); + var content = System.Text.Encoding.UTF8.GetBytes($"executor-threads-{threads}"); + + op.Write("executor-processor-sync", content, executor); + var read = op.Read("executor-processor-sync", executor); + + Assert.Equal(content, read); + } + + [Fact] + public async Task ReadWriteAsync_WithProcessorCountThreads_ParallelOperationsSucceed() + { + var threads = Math.Max(1, Environment.ProcessorCount); + var operationCount = Math.Min(threads * 2, 64); + + using var executor = new Executor(threads); + using var op = new Operator("memory"); + + var tasks = Enumerable.Range(0, operationCount).Select(async i => + { + var path = $"executor-processor-async-{i}"; + var content = System.Text.Encoding.UTF8.GetBytes($"executor-content-{i}"); + + await op.WriteAsync(path, content, executor, CT); + var read = await op.ReadAsync(path, executor, CT); + + Assert.Equal(content, read); + }); + + await Task.WhenAll(tasks); + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/LayerTest.cs b/bindings/dotnet/DotOpenDAL.Tests/LayerTest.cs new file mode 100644 index 000000000000..2bd79ffb97e9 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/LayerTest.cs @@ -0,0 +1,143 @@ +/* + * 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. + */ + +using DotOpenDAL.Layer; + +namespace DotOpenDAL.Tests; + +public class LayerTest +{ + [Fact] + public void WithConcurrentLimit_ReturnsNewOperator() + { + using var op = new Operator("memory"); + var before = op.Op; + using var layered = op.WithLayer(new ConcurrentLimitLayer(4)); + + Assert.NotEqual(IntPtr.Zero, layered.Op); + Assert.NotSame(op, layered); + Assert.Equal(before, op.Op); + Assert.NotEqual(before, layered.Op); + + layered.Write("layer-concurrent", [1, 2, 3]); + var value = layered.Read("layer-concurrent"); + Assert.Equal([1, 2, 3], value); + } + + [Fact] + public void WithRetry_ReturnsNewOperator() + { + using var op = new Operator("memory"); + var before = op.Op; + using var layered = op.WithLayer(new RetryLayer + { + Jitter = false, + Factor = 2, + MinDelay = TimeSpan.FromMilliseconds(1), + MaxDelay = TimeSpan.FromMilliseconds(10), + MaxTimes = 2, + }); + + Assert.NotEqual(IntPtr.Zero, layered.Op); + Assert.NotSame(op, layered); + Assert.Equal(before, op.Op); + Assert.NotEqual(before, layered.Op); + + layered.Write("layer-retry", [4, 5, 6]); + var value = layered.Read("layer-retry"); + Assert.Equal([4, 5, 6], value); + } + + [Fact] + public void WithConcurrentLimit_ZeroPermits_ThrowsArgumentOutOfRangeException() + { + using var op = new Operator("memory"); + + Assert.Throws(() => op.WithLayer(new ConcurrentLimitLayer(0))); + } + + [Fact] + public void WithRetry_InvalidFactor_ThrowsArgumentOutOfRangeException() + { + using var op = new Operator("memory"); + + Assert.Throws(() => op.WithLayer(new RetryLayer + { + Factor = 0, + })); + } + + [Fact] + public void WithTimeout_ReturnsNewOperator() + { + using var op = new Operator("memory"); + var before = op.Op; + using var layered = op.WithLayer(new TimeoutLayer + { + Timeout = TimeSpan.FromSeconds(5), + IoTimeout = TimeSpan.FromSeconds(2), + }); + + Assert.NotEqual(IntPtr.Zero, layered.Op); + Assert.NotSame(op, layered); + Assert.Equal(before, op.Op); + Assert.NotEqual(before, layered.Op); + + layered.Write("layer-timeout", [7, 8, 9]); + var value = layered.Read("layer-timeout"); + Assert.Equal([7, 8, 9], value); + } + + [Fact] + public void WithTimeout_ZeroTimeout_ThrowsArgumentOutOfRangeException() + { + using var op = new Operator("memory"); + + Assert.Throws(() => op.WithLayer(new TimeoutLayer + { + Timeout = TimeSpan.Zero, + })); + } + + [Fact] + public void WithLayer_OperatorsCanBeDisposedIndependently() + { + var op = new Operator("memory"); + var layered = op.WithLayer(new ConcurrentLimitLayer(2)); + + layered.Dispose(); + + op.Write("layer-dispose-origin", [1, 1, 1]); + var originalValue = op.Read("layer-dispose-origin"); + Assert.Equal([1, 1, 1], originalValue); + + op.Dispose(); + + var op2 = new Operator("memory"); + var layered2 = op2.WithLayer(new ConcurrentLimitLayer(2)); + + op2.Dispose(); + + layered2.Write("layer-dispose-layered", [2, 2, 2]); + var layeredValue = layered2.Read("layer-dispose-layered"); + Assert.Equal([2, 2, 2], layeredValue); + + layered2.Dispose(); + } +} diff --git a/bindings/dotnet/DotOpenDAL.Tests/OperatorInfoTest.cs b/bindings/dotnet/DotOpenDAL.Tests/OperatorInfoTest.cs new file mode 100644 index 000000000000..119d6dca5281 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL.Tests/OperatorInfoTest.cs @@ -0,0 +1,70 @@ +/* + * 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. + */ + +using DotOpenDAL.ServiceConfig; + +namespace DotOpenDAL.Tests; + +public class OperatorInfoTest +{ + [Fact] + public void OperatorInfo_MemoryConfig_ReturnsExpectedSchemeAndCapabilities() + { + var config = new MemoryServiceConfig + { + Root = "/opendal/", + }; + + using var op = new Operator(config); + var info = op.Info; + + Assert.Equal("memory", info.Scheme); + Assert.True(info.FullCapability.Read); + Assert.True(info.FullCapability.Write); + } + + [Fact] + public void OperatorInfo_FsConfig_ReturnsExpectedSchemeAndCapabilities() + { + var root = Path.Combine(Path.GetTempPath(), $"opendal-dotnet-info-{Guid.NewGuid():N}"); + Directory.CreateDirectory(root); + + try + { + var options = new Dictionary + { + ["root"] = root, + }; + + using var op = new Operator("fs", options); + var info = op.Info; + + Assert.Equal("fs", info.Scheme); + Assert.True(info.FullCapability.Read); + Assert.True(info.FullCapability.Write); + } + finally + { + if (Directory.Exists(root)) + { + Directory.Delete(root, recursive: true); + } + } + } +} diff --git a/bindings/dotnet/DotOpenDAL/AsyncState.cs b/bindings/dotnet/DotOpenDAL/AsyncState.cs new file mode 100644 index 000000000000..ba3f1cf2f19f --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/AsyncState.cs @@ -0,0 +1,86 @@ +/* + * 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. + */ + +namespace DotOpenDAL; + +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; + +internal static class AsyncStateRegistry +{ + private static long nextAsyncStateId; + private static readonly ConcurrentDictionary AsyncStates = new(); + + public static long Register(out AsyncState state) + { + state = new AsyncState(); + + while (true) + { + var id = Interlocked.Increment(ref nextAsyncStateId); + if (id == 0) + { + continue; + } + + if (AsyncStates.TryAdd(id, state)) + { + return id; + } + } + } + + public static void Unregister(long context) + { + AsyncStates.TryRemove(context, out _); + } + + public static bool TryTake(long context, [NotNullWhen(true)] out TState? state) where TState : class + { + state = null; + if (!AsyncStates.TryRemove(context, out var value)) + { + return false; + } + + state = value as TState; + return state is not null; + } +} + +public sealed class AsyncState +{ + public TaskCompletionSource Completion { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously); + + public CancellationTokenRegistration CancellationRegistration { get; private set; } + + public void BindCancellation(CancellationToken cancellationToken) + { + if (!cancellationToken.CanBeCanceled) + { + return; + } + + CancellationRegistration = cancellationToken.Register(static value => + { + var current = (AsyncState)value!; + current.Completion.TrySetCanceled(); + }, this); + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/BlockingOperator.cs b/bindings/dotnet/DotOpenDAL/BlockingOperator.cs deleted file mode 100644 index 8ec4da78b7fc..000000000000 --- a/bindings/dotnet/DotOpenDAL/BlockingOperator.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -using System; -using System.Runtime.InteropServices; - -namespace DotOpenDAL; - -public partial class BlockingOperator -{ - public IntPtr Op { get; } - - public BlockingOperator() - { - Op = blocking_operator_construct("memory"); - } - - public void Write(string path, string content) - { - blocking_operator_write(Op, path, content); - } - - public string Read(string path) - { - return blocking_operator_read(Op, path); - } - - [LibraryImport("opendal_dotnet", EntryPoint = "blocking_operator_construct", StringMarshalling = StringMarshalling.Utf8)] - private static partial IntPtr blocking_operator_construct(string scheme); - - [LibraryImport("opendal_dotnet", EntryPoint = "blocking_operator_write", StringMarshalling = StringMarshalling.Utf8)] - private static partial void blocking_operator_write(IntPtr op, string path, string content); - - [LibraryImport("opendal_dotnet", EntryPoint = "blocking_operator_read", StringMarshalling = StringMarshalling.Utf8)] - private static partial string blocking_operator_read(IntPtr op, string path); -} diff --git a/bindings/dotnet/DotOpenDAL/ByteBuffer.cs b/bindings/dotnet/DotOpenDAL/ByteBuffer.cs new file mode 100644 index 000000000000..054ac43089c5 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ByteBuffer.cs @@ -0,0 +1,60 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; + +namespace DotOpenDAL; + +[StructLayout(LayoutKind.Sequential)] +/// +/// FFI representation of a Rust byte buffer. +/// +public struct ByteBuffer +{ + /// + /// Pointer to the first byte in unmanaged memory. + /// + public IntPtr Data; + + /// + /// Number of valid bytes in . + /// + public nuint Len; + + /// + /// Total allocated capacity in bytes. + /// + public nuint Capacity; + + /// + /// Copies the unmanaged bytes into a managed array. + /// + public readonly unsafe byte[] ToManagedBytes() + { + if (Data == IntPtr.Zero || Len == 0) + { + return Array.Empty(); + } + + var size = checked((int)Len); + var managed = GC.AllocateUninitializedArray(size); + new ReadOnlySpan((void*)Data, size).CopyTo(managed); + return managed; + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Capability.cs b/bindings/dotnet/DotOpenDAL/Capability.cs new file mode 100644 index 000000000000..9d0e62042af6 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Capability.cs @@ -0,0 +1,362 @@ +/* + * 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. + */ + +using DotOpenDAL.Interop.NativeObject; + +namespace DotOpenDAL; + +/// +/// Capability is used to describe what operations are supported by current operator. +/// +/// +/// This model maps from native capability payload returned by OpenDAL. +/// For write multi size fields, nullable values are represented by a native sentinel value. +/// +public struct Capability +{ + internal Capability(OpenDALCapability native) + { + Stat = native.stat != 0; + StatWithIfMatch = native.statWithIfMatch != 0; + StatWithIfNoneMatch = native.statWithIfNoneMatch != 0; + StatWithIfModifiedSince = native.statWithIfModifiedSince != 0; + StatWithIfUnmodifiedSince = native.statWithIfUnmodifiedSince != 0; + StatWithOverrideCacheControl = native.statWithOverrideCacheControl != 0; + StatWithOverrideContentDisposition = native.statWithOverrideContentDisposition != 0; + StatWithOverrideContentType = native.statWithOverrideContentType != 0; + StatWithVersion = native.statWithVersion != 0; + + Read = native.read != 0; + ReadWithIfMatch = native.readWithIfMatch != 0; + ReadWithIfNoneMatch = native.readWithIfNoneMatch != 0; + ReadWithIfModifiedSince = native.readWithIfModifiedSince != 0; + ReadWithIfUnmodifiedSince = native.readWithIfUnmodifiedSince != 0; + ReadWithOverrideCacheControl = native.readWithOverrideCacheControl != 0; + ReadWithOverrideContentDisposition = native.readWithOverrideContentDisposition != 0; + ReadWithOverrideContentType = native.readWithOverrideContentType != 0; + ReadWithVersion = native.readWithVersion != 0; + + Write = native.write != 0; + WriteCanMulti = native.writeCanMulti != 0; + WriteCanEmpty = native.writeCanEmpty != 0; + WriteCanAppend = native.writeCanAppend != 0; + WriteWithContentType = native.writeWithContentType != 0; + WriteWithContentDisposition = native.writeWithContentDisposition != 0; + WriteWithContentEncoding = native.writeWithContentEncoding != 0; + WriteWithCacheControl = native.writeWithCacheControl != 0; + WriteWithIfMatch = native.writeWithIfMatch != 0; + WriteWithIfNoneMatch = native.writeWithIfNoneMatch != 0; + WriteWithIfNotExists = native.writeWithIfNotExists != 0; + WriteWithUserMetadata = native.writeWithUserMetadata != 0; + + WriteMultiMaxSize = native.writeMultiMaxSize == nuint.MaxValue ? null : native.writeMultiMaxSize; + WriteMultiMinSize = native.writeMultiMinSize == nuint.MaxValue ? null : native.writeMultiMinSize; + WriteTotalMaxSize = native.writeTotalMaxSize == nuint.MaxValue ? null : native.writeTotalMaxSize; + + CreateDir = native.createDir != 0; + Delete = native.delete != 0; + DeleteWithVersion = native.deleteWithVersion != 0; + DeleteWithRecursive = native.deleteWithRecursive != 0; + DeleteMaxSize = native.deleteMaxSize == nuint.MaxValue ? null : native.deleteMaxSize; + + Copy = native.copy != 0; + CopyWithIfNotExists = native.copyWithIfNotExists != 0; + Rename = native.rename != 0; + + List = native.list != 0; + ListWithLimit = native.listWithLimit != 0; + ListWithStartAfter = native.listWithStartAfter != 0; + ListWithRecursive = native.listWithRecursive != 0; + ListWithVersions = native.listWithVersions != 0; + ListWithDeleted = native.listWithDeleted != 0; + + Presign = native.presign != 0; + PresignRead = native.presignRead != 0; + PresignStat = native.presignStat != 0; + PresignWrite = native.presignWrite != 0; + PresignDelete = native.presignDelete != 0; + + Shared = native.shared != 0; + } + + /// + /// If operator supports stat. + /// + public bool Stat { get; private set; } + + /// + /// If operator supports stat with if match. + /// + public bool StatWithIfMatch { get; private set; } + + /// + /// If operator supports stat with if none match. + /// + public bool StatWithIfNoneMatch { get; private set; } + + /// + /// If operator supports stat with if modified since. + /// + public bool StatWithIfModifiedSince { get; private set; } + + /// + /// If operator supports stat with if unmodified since. + /// + public bool StatWithIfUnmodifiedSince { get; private set; } + + /// + /// If operator supports stat with override cache control. + /// + public bool StatWithOverrideCacheControl { get; private set; } + + /// + /// If operator supports stat with override content disposition. + /// + public bool StatWithOverrideContentDisposition { get; private set; } + + /// + /// If operator supports stat with override content type. + /// + public bool StatWithOverrideContentType { get; private set; } + + /// + /// If operator supports stat with version. + /// + public bool StatWithVersion { get; private set; } + + /// + /// If operator supports read. + /// + public bool Read { get; private set; } + + /// + /// If operator supports read with if match. + /// + public bool ReadWithIfMatch { get; private set; } + + /// + /// If operator supports read with if none match. + /// + public bool ReadWithIfNoneMatch { get; private set; } + + /// + /// If operator supports read with if modified since. + /// + public bool ReadWithIfModifiedSince { get; private set; } + + /// + /// If operator supports read with if unmodified since. + /// + public bool ReadWithIfUnmodifiedSince { get; private set; } + + /// + /// If operator supports read with override cache control. + /// + public bool ReadWithOverrideCacheControl { get; private set; } + + /// + /// If operator supports read with override content disposition. + /// + public bool ReadWithOverrideContentDisposition { get; private set; } + + /// + /// If operator supports read with override content type. + /// + public bool ReadWithOverrideContentType { get; private set; } + + /// + /// If operator supports read with version. + /// + public bool ReadWithVersion { get; private set; } + + /// + /// If operator supports write. + /// + public bool Write { get; private set; } + + /// + /// If operator supports write can be called in multi times. + /// + public bool WriteCanMulti { get; private set; } + + /// + /// If operator supports write with empty content. + /// + public bool WriteCanEmpty { get; private set; } + + /// + /// If operator supports write by append. + /// + public bool WriteCanAppend { get; private set; } + + /// + /// If operator supports write with content type. + /// + public bool WriteWithContentType { get; private set; } + + /// + /// If operator supports write with content disposition. + /// + public bool WriteWithContentDisposition { get; private set; } + + /// + /// If operator supports write with content encoding. + /// + public bool WriteWithContentEncoding { get; private set; } + + /// + /// If operator supports write with cache control. + /// + public bool WriteWithCacheControl { get; private set; } + + /// + /// If operator supports write with if match. + /// + public bool WriteWithIfMatch { get; private set; } + + /// + /// If operator supports write with if none match. + /// + public bool WriteWithIfNoneMatch { get; private set; } + + /// + /// If operator supports write with if not exists. + /// + public bool WriteWithIfNotExists { get; private set; } + + /// + /// If operator supports write with user metadata. + /// + public bool WriteWithUserMetadata { get; private set; } + + /// + /// write_multi_max_size is the max size that services support in write_multi. + /// + public ulong? WriteMultiMaxSize { get; private set; } + + /// + /// write_multi_min_size is the min size that services support in write_multi. + /// + public ulong? WriteMultiMinSize { get; private set; } + + /// + /// write_total_max_size is the max total size that services support in write. + /// + public ulong? WriteTotalMaxSize { get; private set; } + + /// + /// If operator supports create dir. + /// + public bool CreateDir { get; private set; } + + /// + /// If operator supports delete. + /// + public bool Delete { get; private set; } + + /// + /// If operator supports delete with version. + /// + public bool DeleteWithVersion { get; private set; } + + /// + /// If operator supports delete with recursive. + /// + public bool DeleteWithRecursive { get; private set; } + + /// + /// delete_max_size is the max size that services support in delete. + /// + public ulong? DeleteMaxSize { get; private set; } + + /// + /// If operator supports copy. + /// + public bool Copy { get; private set; } + + /// + /// If operator supports copy with if not exists. + /// + public bool CopyWithIfNotExists { get; private set; } + + /// + /// If operator supports rename. + /// + public bool Rename { get; private set; } + + /// + /// If operator supports list. + /// + public bool List { get; private set; } + + /// + /// If backend supports list with limit. + /// + public bool ListWithLimit { get; private set; } + + /// + /// If backend supports list with start after. + /// + public bool ListWithStartAfter { get; private set; } + + /// + /// If backend supports list with recursive. + /// + public bool ListWithRecursive { get; private set; } + + /// + /// If backend supports list with versions. + /// + public bool ListWithVersions { get; private set; } + + /// + /// If backend supports list with deleted. + /// + public bool ListWithDeleted { get; private set; } + + /// + /// If operator supports presign. + /// + public bool Presign { get; private set; } + + /// + /// If operator supports presign read. + /// + public bool PresignRead { get; private set; } + + /// + /// If operator supports presign stat. + /// + public bool PresignStat { get; private set; } + + /// + /// If operator supports presign write. + /// + public bool PresignWrite { get; private set; } + + /// + /// If operator supports presign delete. + /// + public bool PresignDelete { get; private set; } + + /// + /// If operator supports shared. + /// + public bool Shared { get; private set; } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Entry.cs b/bindings/dotnet/DotOpenDAL/Entry.cs new file mode 100644 index 000000000000..9ca8bea812fb --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Entry.cs @@ -0,0 +1,42 @@ +/* + * 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. + */ + +namespace DotOpenDAL; + +/// +/// Listed entry with path and metadata. +/// +public sealed class Entry +{ + internal Entry(string path, Metadata metadata) + { + Path = path; + Metadata = metadata; + } + + /// + /// Gets entry path returned by the backend. + /// + public string Path { get; } + + /// + /// Gets metadata associated with this entry. + /// + public Metadata Metadata { get; } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Executor.cs b/bindings/dotnet/DotOpenDAL/Executor.cs new file mode 100644 index 000000000000..e5ac10621ffa --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Executor.cs @@ -0,0 +1,68 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using DotOpenDAL.Interop.Result; + +namespace DotOpenDAL; + +/// +/// Managed wrapper over an OpenDAL native executor handle. +/// +public sealed class Executor : SafeHandle +{ + /// + /// Creates an executor with the given number of worker threads. + /// + /// + /// This executor can be passed to operator APIs to control the runtime that executes the underlying native tasks. + /// + /// Number of Tokio worker threads. Must be greater than zero. + /// is less than or equal to zero. + /// Native executor creation fails. + public Executor(int threads) : base(IntPtr.Zero, true) + { + SetHandle(CreateExecutor(threads)); + } + + /// + /// Gets whether the native handle is invalid. + /// + public override bool IsInvalid => handle == IntPtr.Zero; + + private static IntPtr CreateExecutor(int threads) + { + if (threads <= 0) + { + throw new ArgumentOutOfRangeException(nameof(threads), "threads must be greater than zero."); + } + + var result = NativeMethods.executor_create((nuint)threads); + return Operator.ToValueOrThrowAndRelease(result); + } + + /// + /// Releases the native executor handle. + /// + protected override bool ReleaseHandle() + { + NativeMethods.executor_free(handle); + return true; + } +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/Marshalling/EntryMarshaller.cs b/bindings/dotnet/DotOpenDAL/Interop/Marshalling/EntryMarshaller.cs new file mode 100644 index 000000000000..c9a977a4fa49 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Marshalling/EntryMarshaller.cs @@ -0,0 +1,80 @@ +/* + * 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. + */ + +using System.Runtime.CompilerServices; +using DotOpenDAL.Interop.NativeObject; + +namespace DotOpenDAL.Interop.Marshalling; + +/// +/// Converts native entry list payloads into managed collections. +/// +internal static class EntryMarshaller +{ + /// + /// Reads a native entry list pointer and converts it into managed entries. + /// + /// Pointer to a native opendal_entry_list payload. + /// A read-only collection of managed values. + /// Thrown when native list size exceeds . + internal static unsafe IReadOnlyList ToEntries(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + { + return Array.Empty(); + } + + var payload = Unsafe.Read((void*)ptr); + + if (payload.Len > int.MaxValue) + { + throw new InvalidOperationException("Entry list too large"); + } + + var count = (int)payload.Len; + var results = new List(count); + + if (payload.Entries == IntPtr.Zero) + { + return results; + } + + var entryPointers = new ReadOnlySpan((void*)payload.Entries, count); + for (var index = 0; index < count; index++) + { + var entryPtr = entryPointers[index]; + if (entryPtr == IntPtr.Zero) + { + continue; + } + + var entryPayload = Unsafe.Read((void*)entryPtr); + if (entryPayload.Metadata == IntPtr.Zero) + { + continue; + } + + var path = Utilities.ReadUtf8(entryPayload.Path); + var metadata = MetadataMarshaller.ToMetadata(entryPayload.Metadata); + results.Add(new Entry(path, metadata)); + } + + return results; + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Interop/Marshalling/MetadataMarshaller.cs b/bindings/dotnet/DotOpenDAL/Interop/Marshalling/MetadataMarshaller.cs new file mode 100644 index 000000000000..b0cc31495e68 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Marshalling/MetadataMarshaller.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +using System.Runtime.CompilerServices; +using DotOpenDAL.Interop.NativeObject; + +namespace DotOpenDAL.Interop.Marshalling; + +/// +/// Converts native metadata payloads into managed instances. +/// +internal static class MetadataMarshaller +{ + /// + /// Reads a native metadata pointer and converts it to managed metadata. + /// + /// Pointer to a native opendal_metadata payload. + /// A managed instance. + /// Thrown when the pointer is null. + internal static unsafe Metadata ToMetadata(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + { + throw new InvalidOperationException("stat returned null metadata pointer"); + } + + var payload = Unsafe.Read((void*)ptr); + return ToMetadata(payload); + } + + /// + /// Converts a native metadata payload structure into managed metadata. + /// + /// Native metadata payload copied from unmanaged memory. + /// A managed instance. + internal static Metadata ToMetadata(OpenDALMetadata payload) + { + DateTimeOffset? lastModified = null; + if (payload.LastModifiedHasValue != 0) + { + lastModified = DateTimeOffset.FromUnixTimeSeconds(payload.LastModifiedSecond) + .AddTicks(payload.LastModifiedNanosecond / 100); + } + + var mode = payload.Mode switch + { + 0 => EntryMode.File, + 1 => EntryMode.Dir, + _ => EntryMode.Unknown, + }; + + return new Metadata( + mode, + payload.ContentLength, + Utilities.ReadNullableUtf8(payload.ContentDisposition), + Utilities.ReadNullableUtf8(payload.ContentMd5), + Utilities.ReadNullableUtf8(payload.ContentType), + Utilities.ReadNullableUtf8(payload.ContentEncoding), + Utilities.ReadNullableUtf8(payload.CacheControl), + Utilities.ReadNullableUtf8(payload.ETag), + lastModified, + Utilities.ReadNullableUtf8(payload.Version) + ); + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Interop/Marshalling/OperatorInfoMarshaller.cs b/bindings/dotnet/DotOpenDAL/Interop/Marshalling/OperatorInfoMarshaller.cs new file mode 100644 index 000000000000..4ad9a24ed5a2 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Marshalling/OperatorInfoMarshaller.cs @@ -0,0 +1,62 @@ +/* + * 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. + */ + +using System.Runtime.CompilerServices; +using DotOpenDAL.Interop.NativeObject; + +namespace DotOpenDAL.Interop.Marshalling; + +/// +/// Converts native operator info payloads into managed instances. +/// +internal static class OperatorInfoMarshaller +{ + /// + /// Reads a native operator info pointer and converts it to managed operator info. + /// + /// Pointer to a native opendal_operator_info payload. + /// A managed instance. + /// Thrown when the pointer is null. + internal static unsafe OperatorInfo ToOperatorInfo(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + { + throw new InvalidOperationException("operator_info_get returned null pointer"); + } + + var payload = Unsafe.Read((void*)ptr); + return ToOperatorInfo(payload); + } + + /// + /// Converts a native operator info payload structure into managed operator info. + /// + /// Native operator info payload copied from unmanaged memory. + /// A managed instance. + internal static OperatorInfo ToOperatorInfo(OpenDALOperatorInfo payload) + { + return new OperatorInfo( + Utilities.ReadUtf8(payload.Scheme), + Utilities.ReadUtf8(payload.Root), + Utilities.ReadUtf8(payload.Name), + new Capability(payload.FullCapability), + new Capability(payload.NativeCapability) + ); + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Interop/Marshalling/PresignedRequestMarshaller.cs b/bindings/dotnet/DotOpenDAL/Interop/Marshalling/PresignedRequestMarshaller.cs new file mode 100644 index 000000000000..5539a77e9b06 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Marshalling/PresignedRequestMarshaller.cs @@ -0,0 +1,67 @@ +/* + * 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. + */ + +using System.Runtime.CompilerServices; +using DotOpenDAL.Interop.NativeObject; + +namespace DotOpenDAL.Interop.Marshalling; + +internal static class PresignedRequestMarshaller +{ + internal static unsafe PresignedRequest ToPresignedRequest(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + { + throw new InvalidOperationException("presign returned null request pointer"); + } + + var payload = Unsafe.Read((void*)ptr); + var method = Utilities.ReadUtf8(payload.Method); + var uri = Utilities.ReadUtf8(payload.Uri); + var headers = ToHeaders(payload.HeadersKeys, payload.HeadersValues, payload.HeadersLen); + return new PresignedRequest(method, uri, headers); + } + + private static unsafe IReadOnlyDictionary ToHeaders(IntPtr keysPtr, IntPtr valuesPtr, nuint len) + { + if (len == 0 || keysPtr == IntPtr.Zero || valuesPtr == IntPtr.Zero) + { + return new Dictionary(); + } + + if (len > int.MaxValue) + { + throw new InvalidOperationException("Presigned request headers exceed supported size"); + } + + var count = (int)len; + var keys = new ReadOnlySpan((void*)keysPtr, count); + var values = new ReadOnlySpan((void*)valuesPtr, count); + + var result = new Dictionary(count, StringComparer.OrdinalIgnoreCase); + for (var index = 0; index < count; index++) + { + var key = Utilities.ReadUtf8(keys[index]); + var value = Utilities.ReadUtf8(values[index]); + result[key] = value; + } + + return result; + } +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALCapability.cs b/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALCapability.cs new file mode 100644 index 000000000000..77049dd6fbde --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALCapability.cs @@ -0,0 +1,88 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; + +namespace DotOpenDAL.Interop.NativeObject; + +[StructLayout(LayoutKind.Sequential)] +internal struct OpenDALCapability +{ + [MarshalAs(UnmanagedType.U1)] internal byte stat; + [MarshalAs(UnmanagedType.U1)] internal byte statWithIfMatch; + [MarshalAs(UnmanagedType.U1)] internal byte statWithIfNoneMatch; + [MarshalAs(UnmanagedType.U1)] internal byte statWithIfModifiedSince; + [MarshalAs(UnmanagedType.U1)] internal byte statWithIfUnmodifiedSince; + [MarshalAs(UnmanagedType.U1)] internal byte statWithOverrideCacheControl; + [MarshalAs(UnmanagedType.U1)] internal byte statWithOverrideContentDisposition; + [MarshalAs(UnmanagedType.U1)] internal byte statWithOverrideContentType; + [MarshalAs(UnmanagedType.U1)] internal byte statWithVersion; + + [MarshalAs(UnmanagedType.U1)] internal byte read; + [MarshalAs(UnmanagedType.U1)] internal byte readWithIfMatch; + [MarshalAs(UnmanagedType.U1)] internal byte readWithIfNoneMatch; + [MarshalAs(UnmanagedType.U1)] internal byte readWithIfModifiedSince; + [MarshalAs(UnmanagedType.U1)] internal byte readWithIfUnmodifiedSince; + [MarshalAs(UnmanagedType.U1)] internal byte readWithOverrideCacheControl; + [MarshalAs(UnmanagedType.U1)] internal byte readWithOverrideContentDisposition; + [MarshalAs(UnmanagedType.U1)] internal byte readWithOverrideContentType; + [MarshalAs(UnmanagedType.U1)] internal byte readWithVersion; + + [MarshalAs(UnmanagedType.U1)] internal byte write; + [MarshalAs(UnmanagedType.U1)] internal byte writeCanMulti; + [MarshalAs(UnmanagedType.U1)] internal byte writeCanEmpty; + [MarshalAs(UnmanagedType.U1)] internal byte writeCanAppend; + [MarshalAs(UnmanagedType.U1)] internal byte writeWithContentType; + [MarshalAs(UnmanagedType.U1)] internal byte writeWithContentDisposition; + [MarshalAs(UnmanagedType.U1)] internal byte writeWithContentEncoding; + [MarshalAs(UnmanagedType.U1)] internal byte writeWithCacheControl; + [MarshalAs(UnmanagedType.U1)] internal byte writeWithIfMatch; + [MarshalAs(UnmanagedType.U1)] internal byte writeWithIfNoneMatch; + [MarshalAs(UnmanagedType.U1)] internal byte writeWithIfNotExists; + [MarshalAs(UnmanagedType.U1)] internal byte writeWithUserMetadata; + + internal nuint writeMultiMaxSize; + internal nuint writeMultiMinSize; + internal nuint writeTotalMaxSize; + + [MarshalAs(UnmanagedType.U1)] internal byte createDir; + [MarshalAs(UnmanagedType.U1)] internal byte delete; + [MarshalAs(UnmanagedType.U1)] internal byte deleteWithVersion; + [MarshalAs(UnmanagedType.U1)] internal byte deleteWithRecursive; + internal nuint deleteMaxSize; + + [MarshalAs(UnmanagedType.U1)] internal byte copy; + [MarshalAs(UnmanagedType.U1)] internal byte copyWithIfNotExists; + [MarshalAs(UnmanagedType.U1)] internal byte rename; + + [MarshalAs(UnmanagedType.U1)] internal byte list; + [MarshalAs(UnmanagedType.U1)] internal byte listWithLimit; + [MarshalAs(UnmanagedType.U1)] internal byte listWithStartAfter; + [MarshalAs(UnmanagedType.U1)] internal byte listWithRecursive; + [MarshalAs(UnmanagedType.U1)] internal byte listWithVersions; + [MarshalAs(UnmanagedType.U1)] internal byte listWithDeleted; + + [MarshalAs(UnmanagedType.U1)] internal byte presign; + [MarshalAs(UnmanagedType.U1)] internal byte presignRead; + [MarshalAs(UnmanagedType.U1)] internal byte presignStat; + [MarshalAs(UnmanagedType.U1)] internal byte presignWrite; + [MarshalAs(UnmanagedType.U1)] internal byte presignDelete; + + [MarshalAs(UnmanagedType.U1)] internal byte shared; +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALEntry.cs b/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALEntry.cs new file mode 100644 index 000000000000..62cf9b13a6d3 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALEntry.cs @@ -0,0 +1,30 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; + +namespace DotOpenDAL.Interop.NativeObject; + +[StructLayout(LayoutKind.Sequential)] +internal struct OpenDALEntry +{ + public IntPtr Path; + + public IntPtr Metadata; +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALEntryList.cs b/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALEntryList.cs new file mode 100644 index 000000000000..8dd453ce3c44 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALEntryList.cs @@ -0,0 +1,30 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; + +namespace DotOpenDAL.Interop.NativeObject; + +[StructLayout(LayoutKind.Sequential)] +internal struct OpenDALEntryList +{ + public IntPtr Entries; + + public nuint Len; +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALMetadata.cs b/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALMetadata.cs new file mode 100644 index 000000000000..ab441a8f578e --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALMetadata.cs @@ -0,0 +1,50 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; + +namespace DotOpenDAL.Interop.NativeObject; + +[StructLayout(LayoutKind.Sequential)] +internal struct OpenDALMetadata +{ + public int Mode; + + public ulong ContentLength; + + public IntPtr ContentDisposition; + + public IntPtr ContentMd5; + + public IntPtr ContentType; + + public IntPtr ContentEncoding; + + public IntPtr CacheControl; + + public IntPtr ETag; + + public byte LastModifiedHasValue; + + public long LastModifiedSecond; + + public int LastModifiedNanosecond; + + public IntPtr Version; +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALOperatorInfo.cs b/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALOperatorInfo.cs new file mode 100644 index 000000000000..3a7f00ddcba3 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALOperatorInfo.cs @@ -0,0 +1,39 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; + +namespace DotOpenDAL.Interop.NativeObject; + +[StructLayout(LayoutKind.Sequential)] +/// +/// Native payload for operator information, including scheme, root, name, and capabilities. +/// +internal struct OpenDALOperatorInfo +{ + public IntPtr Scheme; + + public IntPtr Root; + + public IntPtr Name; + + public OpenDALCapability FullCapability; + + public OpenDALCapability NativeCapability; +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALPresignedRequest.cs b/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALPresignedRequest.cs new file mode 100644 index 000000000000..1a280f17a16d --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/NativeObject/OpenDALPresignedRequest.cs @@ -0,0 +1,36 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; + +namespace DotOpenDAL.Interop.NativeObject; + +[StructLayout(LayoutKind.Sequential)] +internal struct OpenDALPresignedRequest +{ + public IntPtr Method; + + public IntPtr Uri; + + public IntPtr HeadersKeys; + + public IntPtr HeadersValues; + + public nuint HeadersLen; +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/Result/Abstractions/INativeResult.cs b/bindings/dotnet/DotOpenDAL/Interop/Result/Abstractions/INativeResult.cs new file mode 100644 index 000000000000..ebf10c693388 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Result/Abstractions/INativeResult.cs @@ -0,0 +1,36 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Interop.Result.Abstractions; + +/// +/// Base interop result contract for error inspection and native resource release. +/// +internal interface INativeResult +{ + /// + /// Gets error details returned from native code. + /// + OpenDALError GetError(); + + /// + /// Releases native resources owned by this result. + /// + void Release(); +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/Result/Abstractions/INativeValueResult.cs b/bindings/dotnet/DotOpenDAL/Interop/Result/Abstractions/INativeValueResult.cs new file mode 100644 index 000000000000..1be24c448aa2 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Result/Abstractions/INativeValueResult.cs @@ -0,0 +1,31 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Interop.Result.Abstractions; + +/// +/// Interop result contract for operations that carry a return value payload. +/// +internal interface INativeValueResult : INativeResult +{ + /// + /// Converts native payload data into a managed return value. + /// + TOutput ToValue(); +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALEntryListResult.cs b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALEntryListResult.cs new file mode 100644 index 000000000000..bb98a4c3dba3 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALEntryListResult.cs @@ -0,0 +1,50 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using DotOpenDAL.Interop.Marshalling; +using DotOpenDAL.Interop.Result.Abstractions; + +namespace DotOpenDAL.Interop.Result; + +[StructLayout(LayoutKind.Sequential)] +/// +/// Result wrapper for operations that return an entry list payload. +/// +internal struct OpenDALEntryListResult : INativeValueResult> +{ + public IntPtr Ptr; + + public OpenDALError Error; + + public readonly OpenDALError GetError() + { + return Error; + } + + public readonly IReadOnlyList ToValue() + { + return EntryMarshaller.ToEntries(Ptr); + } + + public readonly void Release() + { + NativeMethods.opendal_entry_list_result_release(this); + } +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALExecutorResult.cs b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALExecutorResult.cs new file mode 100644 index 000000000000..f8db2d850e3d --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALExecutorResult.cs @@ -0,0 +1,50 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using DotOpenDAL.Interop.Result.Abstractions; + +namespace DotOpenDAL.Interop.Result; + +[StructLayout(LayoutKind.Sequential)] +/// +/// Result wrapper for operations that return an executor pointer payload. +/// +internal struct OpenDALExecutorResult + : INativeValueResult +{ + public IntPtr Ptr; + + public OpenDALError Error; + + public readonly void Release() + { + NativeMethods.opendal_error_release(Error); + } + + public readonly OpenDALError GetError() + { + return Error; + } + + public readonly IntPtr ToValue() + { + return Ptr; + } +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALMetadataResult.cs b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALMetadataResult.cs new file mode 100644 index 000000000000..863f4c7ad8fc --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALMetadataResult.cs @@ -0,0 +1,50 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using DotOpenDAL.Interop.Marshalling; +using DotOpenDAL.Interop.Result.Abstractions; + +namespace DotOpenDAL.Interop.Result; + +[StructLayout(LayoutKind.Sequential)] +/// +/// Result wrapper for operations that return a metadata payload. +/// +internal struct OpenDALMetadataResult : INativeValueResult +{ + public IntPtr Ptr; + + public OpenDALError Error; + + public readonly void Release() + { + NativeMethods.opendal_metadata_result_release(this); + } + + public readonly OpenDALError GetError() + { + return Error; + } + + public readonly Metadata ToValue() + { + return MetadataMarshaller.ToMetadata(Ptr); + } +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALOperatorInfoResult.cs b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALOperatorInfoResult.cs new file mode 100644 index 000000000000..33e6f19dd4d3 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALOperatorInfoResult.cs @@ -0,0 +1,50 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using DotOpenDAL.Interop.Marshalling; +using DotOpenDAL.Interop.Result.Abstractions; + +namespace DotOpenDAL.Interop.Result; + +[StructLayout(LayoutKind.Sequential)] +/// +/// Result wrapper for operations that return an operator info payload. +/// +internal struct OpenDALOperatorInfoResult : INativeValueResult +{ + public IntPtr Ptr; + + public OpenDALError Error; + + public readonly void Release() + { + NativeMethods.opendal_operator_info_result_release(this); + } + + public readonly OpenDALError GetError() + { + return Error; + } + + public readonly OperatorInfo ToValue() + { + return OperatorInfoMarshaller.ToOperatorInfo(Ptr); + } +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALOperatorResult.cs b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALOperatorResult.cs new file mode 100644 index 000000000000..e432b620406e --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALOperatorResult.cs @@ -0,0 +1,50 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using DotOpenDAL.Interop.Result.Abstractions; + +namespace DotOpenDAL.Interop.Result; + +[StructLayout(LayoutKind.Sequential)] +/// +/// Result wrapper for operations that return an operator pointer payload. +/// +internal struct OpenDALOperatorResult + : INativeValueResult +{ + public IntPtr Ptr; + + public OpenDALError Error; + + public readonly void Release() + { + NativeMethods.opendal_error_release(Error); + } + + public readonly OpenDALError GetError() + { + return Error; + } + + public readonly IntPtr ToValue() + { + return Ptr; + } +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALOptionsResult.cs b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALOptionsResult.cs new file mode 100644 index 000000000000..cd5d84442220 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALOptionsResult.cs @@ -0,0 +1,50 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using DotOpenDAL.Interop.Result.Abstractions; + +namespace DotOpenDAL.Interop.Result; + +[StructLayout(LayoutKind.Sequential)] +/// +/// Result wrapper for operations that return an options pointer payload. +/// +internal struct OpenDALOptionsResult + : INativeValueResult +{ + public IntPtr Ptr; + + public OpenDALError Error; + + public readonly void Release() + { + NativeMethods.opendal_error_release(Error); + } + + public readonly OpenDALError GetError() + { + return Error; + } + + public readonly IntPtr ToValue() + { + return Ptr; + } +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALPresignedRequestResult.cs b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALPresignedRequestResult.cs new file mode 100644 index 000000000000..e806018dbf37 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALPresignedRequestResult.cs @@ -0,0 +1,47 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using DotOpenDAL.Interop.Marshalling; +using DotOpenDAL.Interop.Result.Abstractions; + +namespace DotOpenDAL.Interop.Result; + +[StructLayout(LayoutKind.Sequential)] +internal struct OpenDALPresignedRequestResult : INativeValueResult +{ + public IntPtr Ptr; + + public OpenDALError Error; + + public readonly void Release() + { + NativeMethods.opendal_presigned_request_result_release(this); + } + + public readonly OpenDALError GetError() + { + return Error; + } + + public readonly PresignedRequest ToValue() + { + return PresignedRequestMarshaller.ToPresignedRequest(Ptr); + } +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALReadResult.cs b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALReadResult.cs new file mode 100644 index 000000000000..15f2bfb9e21b --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALReadResult.cs @@ -0,0 +1,55 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using DotOpenDAL.Interop.Result.Abstractions; + +namespace DotOpenDAL.Interop.Result; + +[StructLayout(LayoutKind.Sequential)] +/// +/// Result wrapper for operations that return a byte buffer payload. +/// +internal struct OpenDALReadResult : INativeValueResult +{ + /// + /// Byte buffer payload on success. + /// + public ByteBuffer Buffer; + + /// + /// Error details for the operation. + /// + public OpenDALError Error; + + public readonly void Release() + { + NativeMethods.opendal_read_result_release(this); + } + + public readonly OpenDALError GetError() + { + return Error; + } + + public readonly byte[] ToValue() + { + return Buffer.ToManagedBytes(); + } +} diff --git a/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALResult.cs b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALResult.cs new file mode 100644 index 000000000000..5d515c6bb809 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Interop/Result/OpenDALResult.cs @@ -0,0 +1,42 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using DotOpenDAL.Interop.Result.Abstractions; + +namespace DotOpenDAL.Interop.Result; + +[StructLayout(LayoutKind.Sequential)] +/// +/// Result wrapper for operations that only return success or error. +/// +public struct OpenDALResult : INativeResult +{ + public OpenDALError Error; + + public readonly void Release() + { + NativeMethods.opendal_error_release(Error); + } + + public readonly OpenDALError GetError() + { + return Error; + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Layer/Abstractions/ILayer.cs b/bindings/dotnet/DotOpenDAL/Layer/Abstractions/ILayer.cs new file mode 100644 index 000000000000..047c3d8725de --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Layer/Abstractions/ILayer.cs @@ -0,0 +1,33 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Layer.Abstractions; + +/// +/// Represents an operator layer that produces a layered operator instance. +/// +public interface ILayer +{ + /// + /// Applies this layer to the specified operator. + /// + /// Operator to apply the layer to. + /// The layered operator instance. + Operator Apply(Operator op); +} diff --git a/bindings/dotnet/DotOpenDAL/Layer/ConcurrentLimitLayer.cs b/bindings/dotnet/DotOpenDAL/Layer/ConcurrentLimitLayer.cs new file mode 100644 index 000000000000..2ca9f6152fd5 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Layer/ConcurrentLimitLayer.cs @@ -0,0 +1,60 @@ +/* + * 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. + */ + +using DotOpenDAL.Layer.Abstractions; + +namespace DotOpenDAL.Layer; + +/// +/// Layer that limits concurrent operations on an operator. +/// +public sealed class ConcurrentLimitLayer : ILayer +{ + /// + /// Gets maximum concurrent permits. + /// + public nuint Permits { get; } + + /// + /// Creates a concurrent-limit layer. + /// + /// Maximum number of concurrent permits. Must be greater than zero. + public ConcurrentLimitLayer(nuint permits) + { + if (permits == 0) + { + throw new ArgumentOutOfRangeException(nameof(permits), "Permits must be greater than zero."); + } + Permits = permits; + } + + /// + /// Applies concurrent-limit behavior to the specified operator. + /// + /// Operator to layer. + /// The layered operator instance. + public Operator Apply(Operator op) + { + ArgumentNullException.ThrowIfNull(op); + ObjectDisposedException.ThrowIf(op.IsInvalid, op); + + var result = NativeMethods.operator_layer_concurrent_limit(op, Permits); + return op.ApplyLayerResult(result); + } +} diff --git a/bindings/dotnet/DotOpenDAL/Layer/RetryLayer.cs b/bindings/dotnet/DotOpenDAL/Layer/RetryLayer.cs new file mode 100644 index 000000000000..1ad0bd8e1acc --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Layer/RetryLayer.cs @@ -0,0 +1,104 @@ +/* + * 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. + */ + +using DotOpenDAL.Layer.Abstractions; + +namespace DotOpenDAL.Layer; + +/// +/// Retry layer configuration for transient operation failures. +/// +public sealed class RetryLayer : ILayer +{ + /// + /// Gets whether to enable randomized backoff jitter. + /// + public bool Jitter { get; init; } + + /// + /// Gets exponential retry factor. + /// + public float Factor { get; init; } = 2f; + + /// + /// Gets minimum retry delay. + /// + public TimeSpan MinDelay { get; init; } = TimeSpan.FromSeconds(1); + + /// + /// Gets maximum retry delay. + /// + public TimeSpan MaxDelay { get; init; } = TimeSpan.FromSeconds(60); + + /// + /// Gets maximum retry attempts. + /// + public nuint MaxTimes { get; init; } = 3; + + /// + /// Applies retry behavior to the specified operator. + /// + /// Operator to layer. + /// The layered operator instance. + public Operator Apply(Operator op) + { + ArgumentNullException.ThrowIfNull(op); + ObjectDisposedException.ThrowIf(op.IsInvalid, op); + Validate(); + + var result = NativeMethods.operator_layer_retry( + op, + Jitter, + Factor, + ToNanos(MinDelay), + ToNanos(MaxDelay), + MaxTimes + ); + + return op.ApplyLayerResult(result); + } + + private void Validate() + { + if (float.IsNaN(Factor) || float.IsInfinity(Factor) || Factor <= 0) + { + throw new ArgumentOutOfRangeException(nameof(Factor), "Factor must be a positive finite number."); + } + + if (MinDelay < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(MinDelay), "MinDelay must be non-negative."); + } + + if (MaxDelay < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(MaxDelay), "MaxDelay must be non-negative."); + } + + if (MaxDelay < MinDelay) + { + throw new ArgumentOutOfRangeException(nameof(MaxDelay), "MaxDelay must be greater than or equal to MinDelay."); + } + } + + private static ulong ToNanos(TimeSpan delay) + { + return checked((ulong)delay.Ticks * 100UL); + } +} diff --git a/bindings/dotnet/DotOpenDAL/Layer/TimeoutLayer.cs b/bindings/dotnet/DotOpenDAL/Layer/TimeoutLayer.cs new file mode 100644 index 000000000000..f07d743a4064 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Layer/TimeoutLayer.cs @@ -0,0 +1,76 @@ +/* + * 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. + */ + +using DotOpenDAL.Layer.Abstractions; + +namespace DotOpenDAL.Layer; + +/// +/// Timeout layer configuration for operation and I/O deadlines. +/// +public sealed class TimeoutLayer : ILayer +{ + /// + /// Gets total operation timeout. + /// + public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(60); + + /// + /// Gets per-I/O timeout. + /// + public TimeSpan IoTimeout { get; init; } = TimeSpan.FromSeconds(10); + + /// + /// Applies timeout behavior to the specified operator. + /// + /// Operator to layer. + /// The layered operator instance. + public Operator Apply(Operator op) + { + ArgumentNullException.ThrowIfNull(op); + ObjectDisposedException.ThrowIf(op.IsInvalid, op); + Validate(); + + var result = NativeMethods.operator_layer_timeout( + op, + ToNanos(Timeout), + ToNanos(IoTimeout) + ); + + return op.ApplyLayerResult(result); + } + + private void Validate() + { + if (Timeout <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(Timeout), "Timeout must be greater than zero."); + } + + if (IoTimeout <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(IoTimeout), "IoTimeout must be greater than zero."); + } + } + + private static ulong ToNanos(TimeSpan delay) + { + return checked((ulong)delay.Ticks * 100UL); + } +} diff --git a/bindings/dotnet/DotOpenDAL/Metadata.cs b/bindings/dotnet/DotOpenDAL/Metadata.cs new file mode 100644 index 000000000000..42871ccd3ea3 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Metadata.cs @@ -0,0 +1,132 @@ +/* + * 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. + */ + +namespace DotOpenDAL; + +/// +/// Entry mode of a path reported by native OpenDAL metadata. +/// +public enum EntryMode +{ + /// + /// Entry is a file. + /// + File = 0, + + /// + /// Entry is a directory. + /// + Dir = 1, + + /// + /// Entry mode is unknown. + /// + Unknown = 2, +} + +/// +/// Metadata associated with a path. +/// +public sealed class Metadata +{ + internal Metadata( + EntryMode mode, + ulong contentLength, + string? contentDisposition, + string? contentMd5, + string? contentType, + string? contentEncoding, + string? cacheControl, + string? etag, + DateTimeOffset? lastModified, + string? version) + { + Mode = mode; + ContentLength = contentLength; + ContentDisposition = contentDisposition; + ContentMd5 = contentMd5; + ContentType = contentType; + ContentEncoding = contentEncoding; + CacheControl = cacheControl; + ETag = etag; + LastModified = lastModified; + Version = version; + } + + /// + /// Gets entry mode. + /// + public EntryMode Mode { get; } + + /// + /// Gets content length in bytes. + /// + public ulong ContentLength { get; } + + /// + /// Gets Content-Disposition header value, if available. + /// + public string? ContentDisposition { get; } + + /// + /// Gets Content-MD5 header value, if available. + /// + public string? ContentMd5 { get; } + + /// + /// Gets Content-Type header value, if available. + /// + public string? ContentType { get; } + + /// + /// Gets Content-Encoding header value, if available. + /// + public string? ContentEncoding { get; } + + /// + /// Gets Cache-Control header value, if available. + /// + public string? CacheControl { get; } + + /// + /// Gets entity tag (ETag) value, if available. + /// + public string? ETag { get; } + + /// + /// Gets last-modified timestamp, if available. + /// The value is materialized from native Unix seconds and nanoseconds. + /// + public DateTimeOffset? LastModified { get; } + + /// + /// Gets object version, if available. + /// + public string? Version { get; } + + /// + /// Gets whether this metadata represents a file. + /// + public bool IsFile => Mode == EntryMode.File; + + /// + /// Gets whether this metadata represents a directory. + /// + public bool IsDir => Mode == EntryMode.Dir; +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/NativeMethods.cs b/bindings/dotnet/DotOpenDAL/NativeMethods.cs new file mode 100644 index 000000000000..f7f44c6d926a --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/NativeMethods.cs @@ -0,0 +1,522 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using DotOpenDAL.Interop.Result; + +namespace DotOpenDAL; + +internal partial class NativeMethods +{ + const string __DllName = "opendal_dotnet"; + + #region Operator Lifecycle + + [LibraryImport(__DllName, EntryPoint = "operator_construct", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOperatorResult operator_construct( + string scheme, + IntPtr options + ); + + [LibraryImport(__DllName, EntryPoint = "constructor_option_build", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOptionsResult constructor_option_build( + [In] string[] keys, + [In] string[] values, + nuint len + ); + + [LibraryImport(__DllName, EntryPoint = "constructor_option_free")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void constructor_option_free(IntPtr options); + + [LibraryImport(__DllName, EntryPoint = "operator_free")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void operator_free(IntPtr op); + + [LibraryImport(__DllName, EntryPoint = "operator_info_get")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOperatorInfoResult operator_info_get(Operator op); + + [LibraryImport(__DllName, EntryPoint = "operator_info_free")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void operator_info_free(IntPtr info); + + [LibraryImport(__DllName, EntryPoint = "operator_duplicate")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOperatorResult operator_duplicate(Operator op); + + #endregion + + #region Option Builders + + #region ReadOption + + [LibraryImport(__DllName, EntryPoint = "read_option_build", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOptionsResult read_option_build( + [In] string[] keys, + [In] string[] values, + nuint len + ); + + [LibraryImport(__DllName, EntryPoint = "read_option_free")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void read_option_free(IntPtr options); + + #endregion + + #region WriteOption + + [LibraryImport(__DllName, EntryPoint = "write_option_build", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOptionsResult write_option_build( + [In] string[] keys, + [In] string[] values, + nuint len + ); + + [LibraryImport(__DllName, EntryPoint = "write_option_free")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void write_option_free(IntPtr options); + + #endregion + + #region StatOption + + [LibraryImport(__DllName, EntryPoint = "stat_option_build", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOptionsResult stat_option_build( + [In] string[] keys, + [In] string[] values, + nuint len + ); + + [LibraryImport(__DllName, EntryPoint = "stat_option_free")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void stat_option_free(IntPtr options); + + #endregion + + #region ListOption + + [LibraryImport(__DllName, EntryPoint = "list_option_build", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOptionsResult list_option_build( + [In] string[] keys, + [In] string[] values, + nuint len + ); + + [LibraryImport(__DllName, EntryPoint = "list_option_free")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void list_option_free(IntPtr options); + + #endregion + + #endregion + + #region Layer + + [LibraryImport(__DllName, EntryPoint = "operator_layer_retry")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOperatorResult operator_layer_retry( + Operator op, + [MarshalAs(UnmanagedType.I1)] bool jitter, + float factor, + ulong minDelayNanos, + ulong maxDelayNanos, + nuint maxTimes + ); + + [LibraryImport(__DllName, EntryPoint = "operator_layer_concurrent_limit")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOperatorResult operator_layer_concurrent_limit( + Operator op, + nuint permits + ); + + [LibraryImport(__DllName, EntryPoint = "operator_layer_timeout")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOperatorResult operator_layer_timeout( + Operator op, + ulong timeoutNanos, + ulong ioTimeoutNanos + ); + + #endregion + + #region IO Operations + + #region Write + + [LibraryImport(__DllName, EntryPoint = "operator_write_with_options", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALResult operator_write_with_options( + Operator op, + IntPtr executor, + string path, + [In] byte[] data, + nuint len, + IntPtr options + ); + + [LibraryImport(__DllName, EntryPoint = "operator_write_with_options_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_write_with_options_async( + Operator op, + IntPtr executor, + string path, + ByteBuffer data, + IntPtr options, + delegate* unmanaged[Cdecl] callback, + long context + ); + + #endregion + + #region Read + + [LibraryImport(__DllName, EntryPoint = "operator_read_with_options", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALReadResult operator_read_with_options( + Operator op, + IntPtr executor, + string path, + IntPtr options + ); + + [LibraryImport(__DllName, EntryPoint = "operator_read_with_options_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_read_with_options_async( + Operator op, + IntPtr executor, + string path, + IntPtr options, + delegate* unmanaged[Cdecl] callback, + long context + ); + + #endregion + + #region Stat + + [LibraryImport(__DllName, EntryPoint = "operator_stat_with_options", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALMetadataResult operator_stat_with_options( + Operator op, + IntPtr executor, + string path, + IntPtr options + ); + + [LibraryImport(__DllName, EntryPoint = "operator_stat_with_options_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_stat_with_options_async( + Operator op, + IntPtr executor, + string path, + IntPtr options, + delegate* unmanaged[Cdecl] callback, + long context + ); + + #endregion + + #region List + + [LibraryImport(__DllName, EntryPoint = "operator_list_with_options", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALEntryListResult operator_list_with_options( + Operator op, + IntPtr executor, + string path, + IntPtr options + ); + + [LibraryImport(__DllName, EntryPoint = "operator_list_with_options_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_list_with_options_async( + Operator op, + IntPtr executor, + string path, + IntPtr options, + delegate* unmanaged[Cdecl] callback, + long context + ); + + #endregion + + #region Delete + + [LibraryImport(__DllName, EntryPoint = "operator_delete", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALResult operator_delete( + Operator op, + IntPtr executor, + string path + ); + + [LibraryImport(__DllName, EntryPoint = "operator_delete_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_delete_async( + Operator op, + IntPtr executor, + string path, + delegate* unmanaged[Cdecl] callback, + long context + ); + + #endregion + + #region CreateDir + + [LibraryImport(__DllName, EntryPoint = "operator_create_dir", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALResult operator_create_dir( + Operator op, + IntPtr executor, + string path + ); + + [LibraryImport(__DllName, EntryPoint = "operator_create_dir_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_create_dir_async( + Operator op, + IntPtr executor, + string path, + delegate* unmanaged[Cdecl] callback, + long context + ); + + #endregion + + #region Copy + + [LibraryImport(__DllName, EntryPoint = "operator_copy", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALResult operator_copy( + Operator op, + IntPtr executor, + string sourcePath, + string targetPath + ); + + [LibraryImport(__DllName, EntryPoint = "operator_copy_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_copy_async( + Operator op, + IntPtr executor, + string sourcePath, + string targetPath, + delegate* unmanaged[Cdecl] callback, + long context + ); + + #endregion + + #region Rename + + [LibraryImport(__DllName, EntryPoint = "operator_rename", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALResult operator_rename( + Operator op, + IntPtr executor, + string sourcePath, + string targetPath + ); + + [LibraryImport(__DllName, EntryPoint = "operator_rename_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_rename_async( + Operator op, + IntPtr executor, + string sourcePath, + string targetPath, + delegate* unmanaged[Cdecl] callback, + long context + ); + + #endregion + + #region RemoveAll + + [LibraryImport(__DllName, EntryPoint = "operator_remove_all", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALResult operator_remove_all( + Operator op, + IntPtr executor, + string path + ); + + [LibraryImport(__DllName, EntryPoint = "operator_remove_all_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_remove_all_async( + Operator op, + IntPtr executor, + string path, + delegate* unmanaged[Cdecl] callback, + long context + ); + + #endregion + + #region Presign + + [LibraryImport(__DllName, EntryPoint = "operator_presign_read_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_presign_read_async( + Operator op, + IntPtr executor, + string path, + ulong expireNanos, + delegate* unmanaged[Cdecl] callback, + long context + ); + + [LibraryImport(__DllName, EntryPoint = "operator_presign_write_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_presign_write_async( + Operator op, + IntPtr executor, + string path, + ulong expireNanos, + delegate* unmanaged[Cdecl] callback, + long context + ); + + [LibraryImport(__DllName, EntryPoint = "operator_presign_stat_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_presign_stat_async( + Operator op, + IntPtr executor, + string path, + ulong expireNanos, + delegate* unmanaged[Cdecl] callback, + long context + ); + + [LibraryImport(__DllName, EntryPoint = "operator_presign_delete_async", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial OpenDALResult operator_presign_delete_async( + Operator op, + IntPtr executor, + string path, + ulong expireNanos, + delegate* unmanaged[Cdecl] callback, + long context + ); + + #endregion + + #region Streams + + [LibraryImport(__DllName, EntryPoint = "operator_input_stream_create", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOperatorResult operator_input_stream_create( + Operator op, + IntPtr executor, + string path, + IntPtr options + ); + + [LibraryImport(__DllName, EntryPoint = "operator_input_stream_read_next")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALReadResult operator_input_stream_read_next(IntPtr stream); + + [LibraryImport(__DllName, EntryPoint = "operator_input_stream_free")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void operator_input_stream_free(IntPtr stream); + + [LibraryImport(__DllName, EntryPoint = "operator_output_stream_create", StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALOperatorResult operator_output_stream_create( + Operator op, + IntPtr executor, + string path, + IntPtr options + ); + + [LibraryImport(__DllName, EntryPoint = "operator_output_stream_write")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALResult operator_output_stream_write( + IntPtr stream, + [In] byte[] data, + nuint len + ); + + [LibraryImport(__DllName, EntryPoint = "operator_output_stream_flush")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALResult operator_output_stream_flush(IntPtr stream); + + [LibraryImport(__DllName, EntryPoint = "operator_output_stream_close")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALResult operator_output_stream_close(IntPtr stream); + + [LibraryImport(__DllName, EntryPoint = "operator_output_stream_free")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void operator_output_stream_free(IntPtr stream); + + #endregion + + #endregion + + #region Executor + + [LibraryImport(__DllName, EntryPoint = "executor_create")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial OpenDALExecutorResult executor_create(nuint threads); + + [LibraryImport(__DllName, EntryPoint = "executor_free")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void executor_free(IntPtr executor); + + #endregion + + #region Result Release + + [LibraryImport(__DllName, EntryPoint = "opendal_error_release")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void opendal_error_release(OpenDALError error); + + [LibraryImport(__DllName, EntryPoint = "opendal_operator_info_result_release")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void opendal_operator_info_result_release(OpenDALOperatorInfoResult result); + + [LibraryImport(__DllName, EntryPoint = "opendal_metadata_result_release")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void opendal_metadata_result_release(OpenDALMetadataResult result); + + [LibraryImport(__DllName, EntryPoint = "opendal_entry_list_result_release")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void opendal_entry_list_result_release(OpenDALEntryListResult result); + + [LibraryImport(__DllName, EntryPoint = "opendal_read_result_release")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void opendal_read_result_release(OpenDALReadResult result); + + [LibraryImport(__DllName, EntryPoint = "opendal_presigned_request_result_release")] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void opendal_presigned_request_result_release(OpenDALPresignedRequestResult result); + + #endregion + +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/OpenDALError.cs b/bindings/dotnet/DotOpenDAL/OpenDALError.cs new file mode 100644 index 000000000000..6ecb8d91b701 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/OpenDALError.cs @@ -0,0 +1,131 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; + +namespace DotOpenDAL; + +[StructLayout(LayoutKind.Sequential)] +/// +/// Error payload returned by native OpenDAL binding calls. +/// +public struct OpenDALError +{ + /// + /// Non-zero when this instance represents an error. + /// + public byte HasError; + + /// + /// Numeric error code defined by . + /// + public int Code; + + /// + /// Pointer to a UTF-8 error message allocated by native code. + /// + public IntPtr Message; + + /// + /// Gets whether this payload represents an error. + /// + public readonly bool IsError => HasError != 0; +} + +/// +/// Represents all error kinds that may be returned by OpenDAL. +/// +/// For details about each error code, see: +/// https://docs.rs/opendal/latest/opendal/enum.ErrorKind.html +/// +/// +public enum ErrorCode +{ + /// + /// OpenDAL don't know what happened here, and no actions other than just + /// returning it back. For example, s3 returns an internal service error. + /// + Unexpected = 0, + + /// + /// Underlying service doesn't support this operation. + /// + Unsupported = 1, + + /// + /// The config for backend is invalid. + /// + ConfigInvalid = 2, + + /// + /// The given path is not found. + /// + NotFound = 3, + + /// + /// The given path doesn't have enough permission for this operation. + /// + PermissionDenied = 4, + + /// + /// The given path is a directory. + /// + IsADirectory = 5, + + /// + /// The given path is not a directory. + /// + NotADirectory = 6, + + /// + /// The given path already exists thus we failed to the specified operation on it. + /// + AlreadyExists = 7, + + /// + /// Requests that sent to this path is over the limit, please slow down. + /// + RateLimited = 8, + + /// + /// The given file paths are same. + /// + IsSameFile = 9, + + /// + /// The condition of this operation is not match. + /// + /// The `condition` itself is context based. + /// + /// For example, in S3, the `condition` can be: + /// 1. writing a file with If-Match header but the file's ETag is not match (will get a 412 Precondition Failed). + /// 2. reading a file with If-None-Match header but the file's ETag is match (will get a 304 Not Modified). + /// + /// As OpenDAL cannot handle the `condition not match` error, it will always return this error to users. + /// So users could handle this error by themselves. + /// + ConditionNotMatch = 10, + + /// + /// The range of the content is not satisfied. + /// + /// OpenDAL returns this error to indicate that the range of the read request is not satisfied. + /// + RangeNotSatisfied = 11, +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/OpenDALException.cs b/bindings/dotnet/DotOpenDAL/OpenDALException.cs new file mode 100644 index 000000000000..695205163bea --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/OpenDALException.cs @@ -0,0 +1,61 @@ +/* + * 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. + */ + +namespace DotOpenDAL; + +/// +/// Exception thrown when an OpenDAL native call returns an error. +/// +public class OpenDALException : Exception +{ + /// + /// Gets the OpenDAL error code associated with this exception. + /// Unknown native code values are normalized to . + /// + public ErrorCode Code { get; } + + /// + /// Initializes a new exception from a native OpenDAL error payload. + /// + /// Error payload returned by the native binding. + /// + /// Native message text is decoded from UTF-8 and used as the exception message. + /// + public OpenDALException(OpenDALError error) : base(Utilities.ReadUtf8(error.Message)) + { + if (!TryParse(error.Code, out var parsed)) + { + parsed = ErrorCode.Unexpected; + } + + Code = parsed; + } + + private static bool TryParse(int value, out ErrorCode result) + { + if ((uint)value <= (uint)ErrorCode.RangeNotSatisfied) + { + result = (ErrorCode)value; + return true; + } + + result = default; + return false; + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Operator.cs b/bindings/dotnet/DotOpenDAL/Operator.cs new file mode 100644 index 000000000000..d8950bb3c111 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Operator.cs @@ -0,0 +1,1669 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using DotOpenDAL.Interop.Result; +using DotOpenDAL.Interop.Result.Abstractions; +using DotOpenDAL.Layer.Abstractions; +using DotOpenDAL.Options; +using DotOpenDAL.Options.Abstractions; +using DotOpenDAL.ServiceConfig.Abstractions; +using System.Diagnostics.CodeAnalysis; + +namespace DotOpenDAL; + +/// +/// Managed wrapper over an OpenDAL native operator handle. +/// +public partial class Operator : SafeHandle +{ + private Lazy info; + + private Operator() : base(IntPtr.Zero, true) + { + info = CreateInfoLazy(); + } + + private Operator(IntPtr nativeHandle) : this() + { + if (nativeHandle == IntPtr.Zero) + { + throw new ArgumentException("Native operator handle must not be zero.", nameof(nativeHandle)); + } + + SetHandle(nativeHandle); + } + + /// + /// Gets metadata of this operator. + /// + /// The operator has been disposed. + /// Native operator info retrieval fails. + public OperatorInfo Info + { + get + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + return info.Value; + } + } + + /// + /// Gets the underlying native operator pointer. + /// + public IntPtr Op => DangerousGetHandle(); + + /// + /// Gets whether the native handle is invalid. + /// + public override bool IsInvalid => handle == IntPtr.Zero; + + /// + /// Creates an operator for the specified backend scheme and options. + /// + /// + /// Available scheme names are defined by OpenDAL. + /// See OpenDAL Scheme documentation + /// for supported backends and their related configuration options. + /// + /// Name of the backend service, such as fs or memory. + /// Key/value options used to configure the selected backend service. + /// is null, empty, or whitespace. + /// Native operator construction fails. + public Operator(string scheme, IReadOnlyDictionary? options = null) : base(IntPtr.Zero, true) + { + ArgumentException.ThrowIfNullOrWhiteSpace(scheme); + info = CreateInfoLazy(); + + using var nativeOptionsHandle = CreateConstructorOptionsHandle(options); + var result = NativeMethods.operator_construct(scheme, GetOptionsHandle(nativeOptionsHandle)); + SetHandle(ToValueOrThrowAndRelease(result)); + } + + /// + /// Creates an operator from a typed service configuration. + /// + /// + /// This overload converts into backend key/value options internally, + /// then creates the same native operator as . + /// + /// Typed service configuration for the target backend service. + /// is null. + /// Native operator construction fails. + public Operator(IServiceConfig config) : this( + config?.Scheme ?? throw new ArgumentNullException(nameof(config)), + config.ToOptions()) + { + } + + /// + /// Applies the specified layer and returns a new operator instance. + /// + /// Layer to apply. + /// A new operator with the layer applied. + /// is null. + /// The operator has been disposed. + /// Native layer application fails. + public Operator WithLayer(ILayer layer) + { + ArgumentNullException.ThrowIfNull(layer); + ObjectDisposedException.ThrowIf(IsInvalid, this); + return layer.Apply(this); + } + + /// + /// Writes the specified content to a path. + /// + /// Target path in the configured backend. + /// Bytes to write. + /// The operator has been disposed. + /// Native write fails. + public void Write(string path, byte[] content) + { + Write(path, content, options: null, executor: null); + } + + /// + /// Writes the specified content to a path with write options. + /// + /// Target path in the configured backend. + /// Bytes to write. + /// Additional write options. + public void Write(string path, byte[] content, WriteOptions options) + { + Write(path, content, (WriteOptions?)options, executor: null); + } + + /// + /// Writes the specified content to a path using the provided executor. + /// + /// Target path in the configured backend. + /// Bytes to write. + /// Executor used for this operation, or to use default executor. + /// The operator or executor has been disposed. + /// Native write fails. + public void Write(string path, byte[] content, Executor? executor) + { + Write(path, content, options: null, executor); + } + + /// + /// Writes the specified content to a path with write options using the provided executor. + /// + /// Target path in the configured backend. + /// Bytes to write. + /// Additional write options. + /// Executor used for this operation, or to use default executor. + public void Write(string path, byte[] content, WriteOptions? options, Executor? executor) + { + ArgumentNullException.ThrowIfNull(content); + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + using var nativeOptionsHandle = options?.BuildNativeOptionsHandle(); + + OpenDALResult result = NativeMethods.operator_write_with_options( + this, + executorHandle, + path, + content, + (nuint)content.Length, + GetOptionsHandle(nativeOptionsHandle) + ); + + ThrowIfErrorAndRelease(result); + } + + /// + /// Writes the specified content to a path asynchronously. + /// + /// Target path in the configured backend. + /// Bytes to write. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + /// The operator has been disposed. + /// is already canceled. + /// Native write submission fails immediately. + public Task WriteAsync(string path, byte[] content, CancellationToken cancellationToken = default) + { + return WriteAsync(path, content, options: null, executor: null, cancellationToken); + } + + /// + /// Writes the specified content to a path asynchronously with write options. + /// + /// Target path in the configured backend. + /// Bytes to write. + /// Additional write options. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + public Task WriteAsync( + string path, + byte[] content, + WriteOptions options, + CancellationToken cancellationToken = default) + { + return WriteAsync(path, content, (WriteOptions?)options, executor: null, cancellationToken); + } + + /// + /// Writes the specified content to a path asynchronously using the provided executor. + /// + /// Target path in the configured backend. + /// Bytes to write. + /// Executor used for this operation, or to use default executor. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + /// The operator or executor has been disposed. + /// is already canceled. + /// Native write submission fails immediately. + public Task WriteAsync( + string path, + byte[] content, + Executor? executor, + CancellationToken cancellationToken = default) + { + return WriteAsync(path, content, options: null, executor, cancellationToken); + } + + /// + /// Writes the specified content to a path asynchronously with optional write options and executor. + /// + /// Target path in the configured backend. + /// Bytes to write. + /// Additional write options, or for default behavior. + /// Executor used for this operation, or to use default executor. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + public Task WriteAsync( + string path, + byte[] content, + WriteOptions? options, + Executor? executor, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(content); + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + return SubmitAsyncOperation(options, SubmitWriteAsync, cancellationToken); + + OpenDALResult SubmitWriteAsync(long context, IntPtr optionsHandle) + { + unsafe + { + fixed (byte* ptr = content) + { + var buffer = new ByteBuffer + { + Data = (IntPtr)ptr, + Len = (nuint)content.Length, + Capacity = (nuint)content.Length, + }; + + return NativeMethods.operator_write_with_options_async( + this, + executorHandle, + path, + buffer, + optionsHandle, + &OnWriteCompleted, + context + ); + } + } + } + } + + /// + /// Reads all bytes from a path. + /// + /// Source path in the configured backend. + /// The content bytes. + /// The operator has been disposed. + /// Native read fails. + public byte[] Read(string path) + { + return Read(path, options: null, executor: null); + } + + /// + /// Reads bytes from a path with read options. + /// + /// Source path in the configured backend. + /// Additional read options. + /// The content bytes. + public byte[] Read(string path, ReadOptions options) + { + return Read(path, options, executor: null); + } + + /// + /// Reads all bytes from a path using the provided executor. + /// + /// Source path in the configured backend. + /// Executor used for this operation, or to use default executor. + /// The content bytes. + /// The operator or executor has been disposed. + /// Native read fails. + public byte[] Read(string path, Executor? executor) + { + return Read(path, options: null, executor); + } + + /// + /// Reads bytes from a path with read options using the provided executor. + /// + /// Source path in the configured backend. + /// Additional read options. + /// Executor used for this operation, or to use default executor. + /// The content bytes. + public byte[] Read(string path, ReadOptions? options, Executor? executor) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + OpenDALReadResult result; + using var nativeOptionsHandle = options?.BuildNativeOptionsHandle(); + result = NativeMethods.operator_read_with_options(this, executorHandle, path, GetOptionsHandle(nativeOptionsHandle)); + + return ToValueOrThrowAndRelease(result); + } + + /// + /// Reads all bytes from a path asynchronously. + /// + /// Source path in the configured backend. + /// Cancellation token for the managed task. + /// A task that resolves with the read content. + /// The operator has been disposed. + /// is already canceled. + /// Native read submission fails immediately. + public Task ReadAsync(string path, CancellationToken cancellationToken = default) + { + return ReadAsync(path, options: null, executor: null, cancellationToken); + } + + /// + /// Reads bytes from a path asynchronously with read options. + /// + /// Source path in the configured backend. + /// Additional read options. + /// Cancellation token for the managed task. + /// A task that resolves with the read content. + public Task ReadAsync(string path, ReadOptions options, CancellationToken cancellationToken = default) + { + return ReadAsync(path, options, executor: null, cancellationToken); + } + + /// + /// Reads all bytes from a path asynchronously using the provided executor. + /// + /// Source path in the configured backend. + /// Executor used for this operation, or to use default executor. + /// Cancellation token for the managed task. + /// A task that resolves with the read content. + /// The operator or executor has been disposed. + /// is already canceled. + /// Native read submission fails immediately. + public Task ReadAsync(string path, Executor? executor, CancellationToken cancellationToken = default) + { + return ReadAsync(path, options: null, executor, cancellationToken); + } + + /// + /// Reads bytes from a path asynchronously with optional read options and executor. + /// + /// Source path in the configured backend. + /// Additional read options, or for default behavior. + /// Executor used for this operation, or to use default executor. + /// Cancellation token for the managed task. + /// A task that resolves with the read content. + public Task ReadAsync( + string path, + ReadOptions? options, + Executor? executor, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + return SubmitAsyncOperation(options, SubmitReadAsync, cancellationToken); + + OpenDALResult SubmitReadAsync(long context, IntPtr optionsHandle) + { + unsafe + { + return NativeMethods.operator_read_with_options_async( + this, + executorHandle, + path, + optionsHandle, + &OnReadCompleted, + context + ); + } + } + } + + /// + /// Gets metadata for the specified path. + /// + /// Target path in the configured backend. + /// Additional stat options. + /// Metadata of the target path. + public Metadata Stat(string path, StatOptions? options = null) + { + return Stat(path, options, executor: null); + } + + /// + /// Gets metadata for the specified path using the provided executor. + /// + /// Target path in the configured backend. + /// Additional stat options. + /// Executor used for this operation, or to use default executor. + /// Metadata of the target path. + public Metadata Stat(string path, StatOptions? options, Executor? executor) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + OpenDALMetadataResult result; + using var nativeOptionsHandle = options?.BuildNativeOptionsHandle(); + result = NativeMethods.operator_stat_with_options(this, executorHandle, path, GetOptionsHandle(nativeOptionsHandle)); + + return ToValueOrThrowAndRelease(result); + } + + /// + /// Gets metadata for the specified path asynchronously. + /// + /// Target path in the configured backend. + /// Additional stat options. + /// Cancellation token for the managed task. + /// A task that resolves with metadata. + public Task StatAsync( + string path, + StatOptions? options = null, + CancellationToken cancellationToken = default) + { + return StatAsync(path, options, executor: null, cancellationToken); + } + + /// + /// Gets metadata for the specified path asynchronously using the provided executor. + /// + /// Target path in the configured backend. + /// Additional stat options. + /// Executor used for this operation, or to use default executor. + /// Cancellation token for the managed task. + /// A task that resolves with metadata. + public Task StatAsync( + string path, + StatOptions? options, + Executor? executor, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + return SubmitAsyncOperation(options, SubmitStatAsync, cancellationToken); + + OpenDALResult SubmitStatAsync(long context, IntPtr optionsHandle) + { + unsafe + { + return NativeMethods.operator_stat_with_options_async( + this, + executorHandle, + path, + optionsHandle, + &OnStatCompleted, + context + ); + } + } + } + + /// + /// Lists entries under the specified path. + /// + /// Target path in the configured backend. + /// Additional list options. + /// Listed entries. + public IReadOnlyList List(string path, ListOptions? options = null) + { + return List(path, options, executor: null); + } + + /// + /// Lists entries under the specified path using the provided executor. + /// + /// Target path in the configured backend. + /// Additional list options. + /// Executor used for this operation, or to use default executor. + /// Listed entries. + public IReadOnlyList List(string path, ListOptions? options, Executor? executor) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + OpenDALEntryListResult result; + using var nativeOptionsHandle = options?.BuildNativeOptionsHandle(); + result = NativeMethods.operator_list_with_options(this, executorHandle, path, GetOptionsHandle(nativeOptionsHandle)); + + return ToValueOrThrowAndRelease, OpenDALEntryListResult>(result); + } + + /// + /// Lists entries under the specified path asynchronously. + /// + /// Target path in the configured backend. + /// Additional list options. + /// Cancellation token for the managed task. + /// A task that resolves with listed entries. + public Task> ListAsync( + string path, + ListOptions? options = null, + CancellationToken cancellationToken = default) + { + return ListAsync(path, options, executor: null, cancellationToken); + } + + /// + /// Lists entries under the specified path asynchronously using the provided executor. + /// + /// Target path in the configured backend. + /// Additional list options. + /// Executor used for this operation, or to use default executor. + /// Cancellation token for the managed task. + /// A task that resolves with listed entries. + public Task> ListAsync( + string path, + ListOptions? options, + Executor? executor, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + return SubmitAsyncOperation, ListOptions>(options, SubmitListAsync, cancellationToken); + + OpenDALResult SubmitListAsync(long context, IntPtr optionsHandle) + { + unsafe + { + return NativeMethods.operator_list_with_options_async( + this, + executorHandle, + path, + optionsHandle, + &OnListCompleted, + context + ); + } + } + } + + /// + /// Duplicates this operator and returns a new managed instance. + /// + /// A new operator handle that shares the same backend configuration. + /// The operator has been disposed. + /// Native operator duplication fails. + public Operator Duplicate() + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + + var result = NativeMethods.operator_duplicate(this); + var newHandle = ToValueOrThrowAndRelease(result); + if (newHandle == IntPtr.Zero) + { + throw new InvalidOperationException("Duplicate returned null operator pointer"); + } + + return new Operator(newHandle); + } + + /// + /// Deletes the file at the specified path. + /// + /// Target path in the configured backend. + public void Delete(string path) + { + Delete(path, executor: null); + } + + /// + /// Deletes the file at the specified path using the provided executor. + /// + /// Target path in the configured backend. + /// Executor used for this operation, or to use default executor. + public void Delete(string path, Executor? executor) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + var result = NativeMethods.operator_delete(this, executorHandle, path); + ThrowIfErrorAndRelease(result); + } + + /// + /// Deletes the file at the specified path asynchronously. + /// + /// Target path in the configured backend. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + public Task DeleteAsync(string path, CancellationToken cancellationToken = default) + { + return DeleteAsync(path, executor: null, cancellationToken); + } + + /// + /// Deletes the file at the specified path asynchronously using the provided executor. + /// + /// Target path in the configured backend. + /// Executor used for this operation, or to use default executor. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + public Task DeleteAsync(string path, Executor? executor, CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + return SubmitAsyncOperation(SubmitDeleteAsync, cancellationToken); + + OpenDALResult SubmitDeleteAsync(long context) + { + unsafe + { + return NativeMethods.operator_delete_async( + this, + executorHandle, + path, + &OnDeleteCompleted, + context + ); + } + } + } + + /// + /// Creates a directory at the specified path. + /// + /// Target path in the configured backend. + public void CreateDir(string path) + { + CreateDir(path, executor: null); + } + + /// + /// Creates a directory at the specified path using the provided executor. + /// + /// Target path in the configured backend. + /// Executor used for this operation, or to use default executor. + public void CreateDir(string path, Executor? executor) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + var result = NativeMethods.operator_create_dir(this, executorHandle, path); + ThrowIfErrorAndRelease(result); + } + + /// + /// Creates a directory at the specified path asynchronously. + /// + /// Target path in the configured backend. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + public Task CreateDirAsync(string path, CancellationToken cancellationToken = default) + { + return CreateDirAsync(path, executor: null, cancellationToken); + } + + /// + /// Creates a directory at the specified path asynchronously using the provided executor. + /// + /// Target path in the configured backend. + /// Executor used for this operation, or to use default executor. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + public Task CreateDirAsync(string path, Executor? executor, CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + return SubmitAsyncOperation(SubmitCreateDirAsync, cancellationToken); + + OpenDALResult SubmitCreateDirAsync(long context) + { + unsafe + { + return NativeMethods.operator_create_dir_async( + this, + executorHandle, + path, + &OnCreateDirCompleted, + context + ); + } + } + } + + /// + /// Copies a file from source path to target path. + /// + /// Source path in the configured backend. + /// Target path in the configured backend. + public void Copy(string sourcePath, string targetPath) + { + Copy(sourcePath, targetPath, executor: null); + } + + /// + /// Copies a file from source path to target path using the provided executor. + /// + /// Source path in the configured backend. + /// Target path in the configured backend. + /// Executor used for this operation, or to use default executor. + public void Copy(string sourcePath, string targetPath, Executor? executor) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + var result = NativeMethods.operator_copy(this, executorHandle, sourcePath, targetPath); + ThrowIfErrorAndRelease(result); + } + + /// + /// Copies a file from source path to target path asynchronously. + /// + /// Source path in the configured backend. + /// Target path in the configured backend. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + public Task CopyAsync(string sourcePath, string targetPath, CancellationToken cancellationToken = default) + { + return CopyAsync(sourcePath, targetPath, executor: null, cancellationToken); + } + + /// + /// Copies a file from source path to target path asynchronously using the provided executor. + /// + /// Source path in the configured backend. + /// Target path in the configured backend. + /// Executor used for this operation, or to use default executor. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + public Task CopyAsync( + string sourcePath, + string targetPath, + Executor? executor, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + return SubmitAsyncOperation(SubmitCopyAsync, cancellationToken); + + OpenDALResult SubmitCopyAsync(long context) + { + unsafe + { + return NativeMethods.operator_copy_async( + this, + executorHandle, + sourcePath, + targetPath, + &OnCopyCompleted, + context + ); + } + } + } + + /// + /// Renames a file from source path to target path. + /// + /// Source path in the configured backend. + /// Target path in the configured backend. + public void Rename(string sourcePath, string targetPath) + { + Rename(sourcePath, targetPath, executor: null); + } + + /// + /// Renames a file from source path to target path using the provided executor. + /// + /// Source path in the configured backend. + /// Target path in the configured backend. + /// Executor used for this operation, or to use default executor. + public void Rename(string sourcePath, string targetPath, Executor? executor) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + var result = NativeMethods.operator_rename(this, executorHandle, sourcePath, targetPath); + ThrowIfErrorAndRelease(result); + } + + /// + /// Renames a file from source path to target path asynchronously. + /// + /// Source path in the configured backend. + /// Target path in the configured backend. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + public Task RenameAsync(string sourcePath, string targetPath, CancellationToken cancellationToken = default) + { + return RenameAsync(sourcePath, targetPath, executor: null, cancellationToken); + } + + /// + /// Renames a file from source path to target path asynchronously using the provided executor. + /// + /// Source path in the configured backend. + /// Target path in the configured backend. + /// Executor used for this operation, or to use default executor. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + public Task RenameAsync( + string sourcePath, + string targetPath, + Executor? executor, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + return SubmitAsyncOperation(SubmitRenameAsync, cancellationToken); + + OpenDALResult SubmitRenameAsync(long context) + { + unsafe + { + return NativeMethods.operator_rename_async( + this, + executorHandle, + sourcePath, + targetPath, + &OnRenameCompleted, + context + ); + } + } + } + + /// + /// Removes all entries under the specified path recursively. + /// + /// Target path in the configured backend. + public void RemoveAll(string path) + { + RemoveAll(path, executor: null); + } + + /// + /// Removes all entries under the specified path recursively using the provided executor. + /// + /// Target path in the configured backend. + /// Executor used for this operation, or to use default executor. + public void RemoveAll(string path, Executor? executor) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + var result = NativeMethods.operator_remove_all(this, executorHandle, path); + ThrowIfErrorAndRelease(result); + } + + /// + /// Removes all entries under the specified path recursively asynchronously. + /// + /// Target path in the configured backend. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + public Task RemoveAllAsync(string path, CancellationToken cancellationToken = default) + { + return RemoveAllAsync(path, executor: null, cancellationToken); + } + + /// + /// Removes all entries under the specified path recursively asynchronously using the provided executor. + /// + /// Target path in the configured backend. + /// Executor used for this operation, or to use default executor. + /// Cancellation token for the managed task. + /// A task that completes when the native callback reports completion. + public Task RemoveAllAsync(string path, Executor? executor, CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + return SubmitAsyncOperation(SubmitRemoveAllAsync, cancellationToken); + + OpenDALResult SubmitRemoveAllAsync(long context) + { + unsafe + { + return NativeMethods.operator_remove_all_async( + this, + executorHandle, + path, + &OnRemoveAllCompleted, + context + ); + } + } + } + + /// + /// Creates a presigned read request asynchronously. + /// + /// Target path in the configured backend. + /// Presigned request expiration duration. + /// Cancellation token for the managed task. + /// A task that resolves with a presigned request. + public Task PresignReadAsync( + string path, + TimeSpan expiration, + CancellationToken cancellationToken = default) + { + return PresignReadAsync(path, expiration, executor: null, cancellationToken); + } + + /// + /// Creates a presigned read request asynchronously using the provided executor. + /// + public Task PresignReadAsync( + string path, + TimeSpan expiration, + Executor? executor, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + var expireNanos = Utilities.ToNanoseconds(expiration, nameof(expiration)); + + return SubmitAsyncOperation(SubmitPresignReadAsync, cancellationToken); + + OpenDALResult SubmitPresignReadAsync(long context) + { + unsafe + { + return NativeMethods.operator_presign_read_async( + this, + executorHandle, + path, + expireNanos, + &OnPresignReadCompleted, + context + ); + } + } + } + + /// + /// Creates a presigned write request asynchronously. + /// + public Task PresignWriteAsync( + string path, + TimeSpan expiration, + CancellationToken cancellationToken = default) + { + return PresignWriteAsync(path, expiration, executor: null, cancellationToken); + } + + /// + /// Creates a presigned write request asynchronously using the provided executor. + /// + public Task PresignWriteAsync( + string path, + TimeSpan expiration, + Executor? executor, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + var expireNanos = Utilities.ToNanoseconds(expiration, nameof(expiration)); + + return SubmitAsyncOperation(SubmitPresignWriteAsync, cancellationToken); + + OpenDALResult SubmitPresignWriteAsync(long context) + { + unsafe + { + return NativeMethods.operator_presign_write_async( + this, + executorHandle, + path, + expireNanos, + &OnPresignWriteCompleted, + context + ); + } + } + } + + /// + /// Creates a presigned stat request asynchronously. + /// + public Task PresignStatAsync( + string path, + TimeSpan expiration, + CancellationToken cancellationToken = default) + { + return PresignStatAsync(path, expiration, executor: null, cancellationToken); + } + + /// + /// Creates a presigned stat request asynchronously using the provided executor. + /// + public Task PresignStatAsync( + string path, + TimeSpan expiration, + Executor? executor, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + var expireNanos = Utilities.ToNanoseconds(expiration, nameof(expiration)); + + return SubmitAsyncOperation(SubmitPresignStatAsync, cancellationToken); + + OpenDALResult SubmitPresignStatAsync(long context) + { + unsafe + { + return NativeMethods.operator_presign_stat_async( + this, + executorHandle, + path, + expireNanos, + &OnPresignStatCompleted, + context + ); + } + } + } + + /// + /// Creates a presigned delete request asynchronously. + /// + public Task PresignDeleteAsync( + string path, + TimeSpan expiration, + CancellationToken cancellationToken = default) + { + return PresignDeleteAsync(path, expiration, executor: null, cancellationToken); + } + + /// + /// Creates a presigned delete request asynchronously using the provided executor. + /// + public Task PresignDeleteAsync( + string path, + TimeSpan expiration, + Executor? executor, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + var expireNanos = Utilities.ToNanoseconds(expiration, nameof(expiration)); + + return SubmitAsyncOperation(SubmitPresignDeleteAsync, cancellationToken); + + OpenDALResult SubmitPresignDeleteAsync(long context) + { + unsafe + { + return NativeMethods.operator_presign_delete_async( + this, + executorHandle, + path, + expireNanos, + &OnPresignDeleteCompleted, + context + ); + } + } + } + + /// + /// Opens a read stream for the specified path. + /// + /// Target path in the configured backend. + /// Optional read options. + /// Executor used for stream creation, or to use default executor. + /// A readable stream over the given path. + public OperatorInputStream OpenReadStream( + string path, + ReadOptions? options = null, + Executor? executor = null) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + using var nativeOptions = options?.BuildNativeOptionsHandle(); + var result = NativeMethods.operator_input_stream_create( + this, + executorHandle, + path, + GetOptionsHandle(nativeOptions) + ); + + var streamHandle = ToValueOrThrowAndRelease(result); + if (streamHandle == IntPtr.Zero) + { + throw new InvalidOperationException("OpenReadStream returned null stream pointer"); + } + + return new OperatorInputStream(streamHandle); + } + + /// + /// Opens a write stream for the specified path. + /// + /// Target path in the configured backend. + /// Optional write options. + /// Buffer size used by the managed write stream. + /// Executor used for stream creation, or to use default executor. + /// A writable stream over the given path. + public OperatorOutputStream OpenWriteStream( + string path, + WriteOptions? options = null, + int bufferSize = OperatorOutputStream.DefaultBufferSize, + Executor? executor = null) + { + ObjectDisposedException.ThrowIf(IsInvalid, this); + var executorHandle = GetExecutorHandle(executor); + + using var nativeOptions = options?.BuildNativeOptionsHandle(); + var result = NativeMethods.operator_output_stream_create( + this, + executorHandle, + path, + GetOptionsHandle(nativeOptions) + ); + + var streamHandle = ToValueOrThrowAndRelease(result); + if (streamHandle == IntPtr.Zero) + { + throw new InvalidOperationException("OpenWriteStream returned null stream pointer"); + } + + return new OperatorOutputStream(streamHandle, bufferSize); + } + + /// + /// Releases the native operator handle. + /// + /// after the handle has been released. + protected override bool ReleaseHandle() + { + NativeMethods.operator_free(handle); + return true; + } + + /// + /// Applies a native layer result by creating a new operator from the returned handle. + /// + /// Native result that contains a new operator pointer. + /// A new operator instance. + /// Returned operator pointer is null. + /// Native layer application fails. + internal Operator ApplyLayerResult(OpenDALOperatorResult result) + { + var newHandle = ToValueOrThrowAndRelease(result); + if (newHandle == IntPtr.Zero) + { + throw new InvalidOperationException("Layer application returned null operator pointer"); + } + + return new Operator(newHandle); + } + + /// + /// Gets the native executor handle for operation submission. + /// + /// Executor instance or to use default executor. + /// Native executor pointer, or when no executor is specified. + /// The executor has already been disposed. + private static IntPtr GetExecutorHandle(Executor? executor) + { + if (executor is null) + { + return IntPtr.Zero; + } + + ObjectDisposedException.ThrowIf(executor.IsClosed || executor.IsInvalid, executor); + return executor.DangerousGetHandle(); + } + + /// + /// Gets the native options pointer from an optional native options handle. + /// + /// Native options handle or . + /// Native options pointer, or when options are not provided. + private static IntPtr GetOptionsHandle(NativeOptionsHandle? options) + { + return options is null ? IntPtr.Zero : options.DangerousGetHandle(); + } + + /// + /// Creates the lazily-evaluated operator info loader. + /// + /// A thread-safe lazy loader for . + private Lazy CreateInfoLazy() + { + return new Lazy(CreateOperatorInfo, LazyThreadSafetyMode.ExecutionAndPublication); + } + + /// + /// Retrieves operator info from the native layer. + /// + /// Managed operator info value. + /// Native operator info retrieval fails. + private OperatorInfo CreateOperatorInfo() + { + var result = NativeMethods.operator_info_get(this); + + return ToValueOrThrowAndRelease(result); + } + + /// + /// Builds constructor options for operator creation when key/value options are provided. + /// + /// Backend options dictionary. + /// Native options handle, or when options are empty. + private static NativeOptionsHandle? CreateConstructorOptionsHandle(IReadOnlyDictionary? options) + { + if (options is null || options.Count == 0) + { + return null; + } + + return NativeOptionsBuilder.BuildNativeOptionsHandle( + options, + NativeMethods.constructor_option_build, + NativeMethods.constructor_option_free + ); + } + + /// + /// Converts a native result into a managed value, throwing on native error and always releasing native resources. + /// + /// Managed output type. + /// Native result type. + /// Native result payload. + /// Managed value converted from . + /// Native operation returns an error. + internal static TOutput ToValueOrThrowAndRelease(TResult result) + where TResult : struct, INativeValueResult + { + try + { + var error = result.GetError(); + if (error.IsError) + { + throw new OpenDALException(error); + } + + return result.ToValue(); + } + finally + { + result.Release(); + } + } + + /// + /// Throws when a native result reports an error and always releases native resources. + /// + /// Native result type. + /// Native result payload. + /// Native operation returns an error. + internal static void ThrowIfErrorAndRelease(TResult result) + where TResult : struct, INativeResult + { + try + { + var error = result.GetError(); + if (error.IsError) + { + throw new OpenDALException(error); + } + } + finally + { + result.Release(); + } + } + + /// + /// Submits a native async operation and binds it to a managed task completion source. + /// + /// Managed task result type. + /// Managed options type. + /// Optional managed options for this operation. + /// Submission delegate that invokes the native async API. + /// Cancellation token for managed task observation. + /// A task completed by the corresponding native callback. + /// is already canceled. + /// Native submission returns an immediate error. + internal static Task SubmitAsyncOperation( + TOptions? options, + Func submit, + CancellationToken cancellationToken) + where TOptions : class, IOptions + { + cancellationToken.ThrowIfCancellationRequested(); + var context = AsyncStateRegistry.Register(out var asyncState); + try + { + using var nativeOptionsHandle = options?.BuildNativeOptionsHandle(); + var submitResult = submit(context, GetOptionsHandle(nativeOptionsHandle)); + ThrowIfErrorAndRelease(submitResult); + asyncState.BindCancellation(cancellationToken); + return asyncState.Completion.Task; + } + catch + { + AsyncStateRegistry.Unregister(context); + throw; + } + } + + /// + /// Submits a native async operation without options and binds it to a managed task completion source. + /// + /// Managed task result type. + /// Submission delegate that invokes the native async API. + /// Cancellation token for managed task observation. + /// A task completed by the corresponding native callback. + internal static Task SubmitAsyncOperation( + Func submit, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var context = AsyncStateRegistry.Register(out var asyncState); + try + { + var submitResult = submit(context); + ThrowIfErrorAndRelease(submitResult); + asyncState.BindCancellation(cancellationToken); + return asyncState.Completion.Task; + } + catch + { + AsyncStateRegistry.Unregister(context); + throw; + } + } + + /// + /// Submits a native async operation without options and binds it to a managed task completion source. + /// + /// Submission delegate that invokes the native async API. + /// Cancellation token for managed task observation. + /// A task completed by the corresponding native callback. + /// is already canceled. + /// Native submission returns an immediate error. + internal static Task SubmitAsyncOperation( + Func submit, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var context = AsyncStateRegistry.Register(out var asyncState); + try + { + var submitResult = submit(context); + ThrowIfErrorAndRelease(submitResult); + asyncState.BindCancellation(cancellationToken); + return asyncState.Completion.Task; + } + catch + { + AsyncStateRegistry.Unregister(context); + throw; + } + } + + /// + /// Attempts to retrieve and remove async state for a callback context. + /// + /// Async state result type. + /// Native callback context id. + /// Resolved async state when found. + /// if an async state is found; otherwise . + private static bool TryTakeAsyncState(long context, [NotNullWhen(true)] out AsyncState? state) + { + if (AsyncStateRegistry.TryTake>(context, out var current)) + { + state = current; + return true; + } + + state = null; + return false; + } + + /// + /// Completes a value-producing async state from a native callback result. + /// + /// Managed output type. + /// Native result type. + /// Native callback context id. + /// Native callback result payload. + private static void CompleteAsyncState(long context, TResult result) + where TResult : struct, INativeValueResult + { + if (!TryTakeAsyncState(context, out AsyncState? state)) + { + return; + } + + try + { + state.CancellationRegistration.Dispose(); + + if (result.GetError().IsError) + { + state.Completion.TrySetException(new OpenDALException(result.GetError())); + return; + } + + state.Completion.TrySetResult(result.ToValue()); + } + catch (Exception ex) + { + state.Completion.TrySetException(ex); + } + } + + /// + /// Completes a non-value async state from a native callback result. + /// + /// Native result type. + /// Native callback context id. + /// Native callback result payload. + private static void CompleteAsyncState(long context, TResult result) + where TResult : struct, INativeResult + { + if (!TryTakeAsyncState(context, out AsyncState? state)) + { + return; + } + + try + { + state.CancellationRegistration.Dispose(); + + var error = result.GetError(); + if (error.IsError) + { + state.Completion.TrySetException(new OpenDALException(error)); + return; + } + + state.Completion.TrySetResult(true); + } + catch (Exception ex) + { + state.Completion.TrySetException(ex); + } + } + + /// + /// Finalizes a value-producing native callback by completing managed state and releasing native resources. + /// + /// Managed output type. + /// Native result type. + /// Native callback context id. + /// Native callback result payload. + internal static void CompleteAsyncCallback(long context, TResult result) + where TResult : struct, INativeValueResult + { + try + { + CompleteAsyncState(context, result); + } + finally + { + result.Release(); + } + } + + /// + /// Finalizes a non-value native callback by completing managed state and releasing native resources. + /// + /// Native result type. + /// Native callback context id. + /// Native callback result payload. + internal static void CompleteAsyncCallback(long context, TResult result) + where TResult : struct, INativeResult + { + try + { + CompleteAsyncState(context, result); + } + finally + { + result.Release(); + } + } + + #region Async Callbacks + + /// + /// Native callback invoked when an asynchronous write operation finishes. + /// + /// Opaque async state context previously registered by . + /// Write completion result returned by the native layer. + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnWriteCompleted(long context, OpenDALResult result) + { + CompleteAsyncCallback(context, result); + } + + /// + /// Native callback invoked when an asynchronous read operation finishes. + /// + /// Opaque async state context previously registered by . + /// Read completion result returned by the native layer, including byte buffer payload. + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnReadCompleted(long context, OpenDALReadResult result) + { + CompleteAsyncCallback(context, result); + } + + /// + /// Native callback invoked when an asynchronous stat operation finishes. + /// + /// Opaque async state context previously registered by . + /// Stat completion result returned by the native layer. + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnStatCompleted(long context, OpenDALMetadataResult result) + { + CompleteAsyncCallback(context, result); + } + + /// + /// Native callback invoked when an asynchronous list operation finishes. + /// + /// Opaque async state context previously registered by . + /// List completion result returned by the native layer. + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnListCompleted(long context, OpenDALEntryListResult result) + { + CompleteAsyncCallback, OpenDALEntryListResult>(context, result); + } + + /// + /// Native callback invoked when an asynchronous delete operation finishes. + /// + /// Opaque async state context previously registered by . + /// Delete completion result returned by the native layer. + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnDeleteCompleted(long context, OpenDALResult result) + { + CompleteAsyncCallback(context, result); + } + + /// + /// Native callback invoked when an asynchronous create-dir operation finishes. + /// + /// Opaque async state context previously registered by . + /// Create-dir completion result returned by the native layer. + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnCreateDirCompleted(long context, OpenDALResult result) + { + CompleteAsyncCallback(context, result); + } + + /// + /// Native callback invoked when an asynchronous copy operation finishes. + /// + /// Opaque async state context previously registered by . + /// Copy completion result returned by the native layer. + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnCopyCompleted(long context, OpenDALResult result) + { + CompleteAsyncCallback(context, result); + } + + /// + /// Native callback invoked when an asynchronous rename operation finishes. + /// + /// Opaque async state context previously registered by . + /// Rename completion result returned by the native layer. + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnRenameCompleted(long context, OpenDALResult result) + { + CompleteAsyncCallback(context, result); + } + + /// + /// Native callback invoked when an asynchronous remove-all operation finishes. + /// + /// Opaque async state context previously registered by . + /// Remove-all completion result returned by the native layer. + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnRemoveAllCompleted(long context, OpenDALResult result) + { + CompleteAsyncCallback(context, result); + } + + /// + /// Native callback invoked when an asynchronous presign-read operation finishes. + /// + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnPresignReadCompleted(long context, OpenDALPresignedRequestResult result) + { + CompleteAsyncCallback(context, result); + } + + /// + /// Native callback invoked when an asynchronous presign-write operation finishes. + /// + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnPresignWriteCompleted(long context, OpenDALPresignedRequestResult result) + { + CompleteAsyncCallback(context, result); + } + + /// + /// Native callback invoked when an asynchronous presign-stat operation finishes. + /// + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnPresignStatCompleted(long context, OpenDALPresignedRequestResult result) + { + CompleteAsyncCallback(context, result); + } + + /// + /// Native callback invoked when an asynchronous presign-delete operation finishes. + /// + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void OnPresignDeleteCompleted(long context, OpenDALPresignedRequestResult result) + { + CompleteAsyncCallback(context, result); + } + + #endregion + +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/OperatorInfo.cs b/bindings/dotnet/DotOpenDAL/OperatorInfo.cs new file mode 100644 index 000000000000..1916cbd13745 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/OperatorInfo.cs @@ -0,0 +1,60 @@ +/* + * 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. + */ + +namespace DotOpenDAL; + +/// +/// Represents metadata of an OpenDAL operator. +/// +public sealed class OperatorInfo +{ + /// + /// Gets the scheme of this operator. + /// + public string Scheme { get; } + + /// + /// Gets the configured root of this operator. + /// + public string Root { get; } + + /// + /// Gets the configured name of this operator. + /// + public string Name { get; } + + /// + /// Gets the full capability of this operator. + /// + public Capability FullCapability { get; } + + /// + /// Gets the native capability of this operator. + /// + public Capability NativeCapability { get; } + + internal OperatorInfo(string scheme, string root, string name, Capability fullCapability, Capability nativeCapability) + { + Scheme = scheme; + Root = root; + Name = name; + FullCapability = fullCapability; + NativeCapability = nativeCapability; + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/OperatorInputStream.cs b/bindings/dotnet/DotOpenDAL/OperatorInputStream.cs new file mode 100644 index 000000000000..b00ca8badfca --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/OperatorInputStream.cs @@ -0,0 +1,152 @@ +/* + * 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. + */ + +using DotOpenDAL.Interop.Result; + +namespace DotOpenDAL; + +/// +/// Read-only stream over an OpenDAL path. +/// +public sealed class OperatorInputStream : Stream +{ + private IntPtr handle; + private bool disposed; + private byte[]? chunk; + private int chunkOffset; + + internal OperatorInputStream(IntPtr handle) + { + if (handle == IntPtr.Zero) + { + throw new ArgumentException("Native input stream handle must not be zero.", nameof(handle)); + } + + this.handle = handle; + } + + public override bool CanRead => !disposed; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + ArgumentNullException.ThrowIfNull(buffer); + return Read(buffer.AsSpan(offset, count)); + } + + public override int Read(Span destination) + { + ThrowIfDisposed(); + if (destination.Length == 0) + { + return 0; + } + + var totalRead = 0; + while (destination.Length > 0) + { + if (chunk is null || chunkOffset >= chunk.Length) + { + var next = NativeMethods.operator_input_stream_read_next(handle); + chunk = Operator.ToValueOrThrowAndRelease(next); + chunkOffset = 0; + if (chunk.Length == 0) + { + return totalRead; + } + } + + var available = chunk.Length - chunkOffset; + var toCopy = Math.Min(available, destination.Length); + chunk.AsSpan(chunkOffset, toCopy).CopyTo(destination); + chunkOffset += toCopy; + destination = destination[toCopy..]; + totalRead += toCopy; + } + + return totalRead; + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(Read(buffer, offset, count)); + } + + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return ValueTask.FromResult(Read(buffer.Span)); + } + + public override void Flush() + { + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + private void ThrowIfDisposed() + { + ObjectDisposedException.ThrowIf(disposed || handle == IntPtr.Zero, this); + } + + protected override void Dispose(bool disposing) + { + if (!disposed) + { + NativeMethods.operator_input_stream_free(handle); + handle = IntPtr.Zero; + chunk = null; + chunkOffset = 0; + disposed = true; + } + + base.Dispose(disposing); + } +} diff --git a/bindings/dotnet/DotOpenDAL/OperatorOutputStream.cs b/bindings/dotnet/DotOpenDAL/OperatorOutputStream.cs new file mode 100644 index 000000000000..c48b3816d3b0 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/OperatorOutputStream.cs @@ -0,0 +1,186 @@ +/* + * 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. + */ + +using DotOpenDAL.Interop.Result; + +namespace DotOpenDAL; + +/// +/// Write-only stream over an OpenDAL path. +/// +public sealed class OperatorOutputStream : Stream +{ + internal const int DefaultBufferSize = 16 * 1024; + + private IntPtr handle; + private bool disposed; + private readonly byte[] buffer; + private int buffered; + + internal OperatorOutputStream(IntPtr handle, int bufferSize) + { + if (handle == IntPtr.Zero) + { + throw new ArgumentException("Native output stream handle must not be zero.", nameof(handle)); + } + + if (bufferSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize), "Buffer size must be greater than zero."); + } + + this.handle = handle; + buffer = GC.AllocateUninitializedArray(bufferSize); + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => !disposed; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override void Write(byte[] source, int offset, int count) + { + ArgumentNullException.ThrowIfNull(source); + Write(source.AsSpan(offset, count)); + } + + public override void Write(ReadOnlySpan source) + { + ThrowIfDisposed(); + while (!source.IsEmpty) + { + var writable = buffer.Length - buffered; + if (writable == 0) + { + FlushBuffered(); + writable = buffer.Length; + } + + var take = Math.Min(writable, source.Length); + source[..take].CopyTo(buffer.AsSpan(buffered)); + buffered += take; + source = source[take..]; + } + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + Write(buffer, offset, count); + return Task.CompletedTask; + } + + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + Write(buffer.Span); + return ValueTask.CompletedTask; + } + + public override void Flush() + { + ThrowIfDisposed(); + FlushBuffered(); + var result = NativeMethods.operator_output_stream_flush(handle); + Operator.ThrowIfErrorAndRelease(result); + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + Flush(); + return Task.CompletedTask; + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + private void FlushBuffered() + { + if (buffered == 0) + { + return; + } + + var result = NativeMethods.operator_output_stream_write(handle, buffer, (nuint)buffered); + Operator.ThrowIfErrorAndRelease(result); + buffered = 0; + } + + private void ThrowIfDisposed() + { + ObjectDisposedException.ThrowIf(disposed || handle == IntPtr.Zero, this); + } + + protected override void Dispose(bool disposing) + { + if (!disposed) + { + OpenDALException? pending = null; + try + { + Flush(); + var close = NativeMethods.operator_output_stream_close(handle); + try + { + Operator.ThrowIfErrorAndRelease(close); + } + catch (OpenDALException ex) + { + pending = ex; + } + } + finally + { + NativeMethods.operator_output_stream_free(handle); + handle = IntPtr.Zero; + buffered = 0; + disposed = true; + } + + if (pending is not null) + { + throw pending; + } + } + + base.Dispose(disposing); + } +} diff --git a/bindings/dotnet/DotOpenDAL/Options/Abstractions/IOptions.cs b/bindings/dotnet/DotOpenDAL/Options/Abstractions/IOptions.cs new file mode 100644 index 000000000000..591f061a86d7 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Options/Abstractions/IOptions.cs @@ -0,0 +1,28 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Options.Abstractions; + +/// +/// Represents operation options that can build native option payload handles. +/// +public interface IOptions +{ + NativeOptionsHandle BuildNativeOptionsHandle(); +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Options/ListOptions.cs b/bindings/dotnet/DotOpenDAL/Options/ListOptions.cs new file mode 100644 index 000000000000..0e513cd6ead7 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Options/ListOptions.cs @@ -0,0 +1,57 @@ +/* + * 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. + */ + +using DotOpenDAL.Options.Abstractions; + +namespace DotOpenDAL.Options; + +/// +/// Additional options for list operations. +/// +public sealed class ListOptions : IOptions +{ + public bool Recursive { get; init; } + + public long? Limit { get; init; } + + public string? StartAfter { get; init; } + + public bool Versions { get; init; } + + public bool Deleted { get; init; } + + public NativeOptionsHandle BuildNativeOptionsHandle() + { + OptionValidators.RequireNullableGreaterThanZero(Limit, nameof(Limit)); + + var nativeOptions = new NativeOptionsBuilder() + .AddBoolTrue("recursive", Recursive) + .AddNullableInt64("limit", Limit) + .AddString("start_after", StartAfter) + .AddBoolTrue("versions", Versions) + .AddBoolTrue("deleted", Deleted) + .Build(); + + return NativeOptionsBuilder.BuildNativeOptionsHandle( + nativeOptions, + NativeMethods.list_option_build, + NativeMethods.list_option_free + ); + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Options/NativeOptionsBuilder.cs b/bindings/dotnet/DotOpenDAL/Options/NativeOptionsBuilder.cs new file mode 100644 index 000000000000..0ee00de4a098 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Options/NativeOptionsBuilder.cs @@ -0,0 +1,176 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using DotOpenDAL.Interop.Result; + +namespace DotOpenDAL.Options; + +/// +/// Delegate that builds a native options payload from managed key/value arrays. +/// +/// Option keys. +/// Option values aligned by index with . +/// Number of key/value pairs. +/// Native options build result. +internal delegate OpenDALOptionsResult NativeBuildOptionsDelegate(string[] keys, string[] values, nuint len); + +/// +/// Builder that incrementally collects native key/value options. +/// +internal sealed class NativeOptionsBuilder +{ + private readonly Dictionary options = new(); + + /// + /// Adds the option with value "true" when is true. + /// + public NativeOptionsBuilder AddBoolTrue(string key, bool value) + { + if (value) + { + options[key] = "true"; + } + + return this; + } + + /// + /// Adds the option when the string value is not null or empty. + /// + public NativeOptionsBuilder AddString(string key, string? value) + { + if (!string.IsNullOrEmpty(value)) + { + options[key] = value; + } + + return this; + } + + /// + /// Adds the option when the int value differs from the specified default. + /// + public NativeOptionsBuilder AddInt32IfNotDefault(string key, int value, int defaultValue) + { + if (value != defaultValue) + { + options[key] = value.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + + return this; + } + + /// + /// Adds the option when the long value differs from the specified default. + /// + public NativeOptionsBuilder AddInt64IfNotDefault(string key, long value, long defaultValue) + { + if (value != defaultValue) + { + options[key] = value.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + + return this; + } + + /// + /// Adds the option when the nullable long value is provided. + /// + public NativeOptionsBuilder AddNullableInt64(string key, long? value) + { + if (value is not null) + { + options[key] = value.Value.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + + return this; + } + + /// + /// Adds the option as Unix time milliseconds when the timestamp is provided. + /// + public NativeOptionsBuilder AddUnixTimeMilliseconds(string key, DateTimeOffset? value) + { + if (value is not null) + { + options[key] = value.Value.ToUnixTimeMilliseconds().ToString(System.Globalization.CultureInfo.InvariantCulture); + } + + return this; + } + + /// + /// Adds prefixed entries from the provided dictionary. + /// + public NativeOptionsBuilder AddPrefixedEntries(string prefix, IReadOnlyDictionary? values) + { + if (values is null) + { + return this; + } + + foreach (var entry in values) + { + options[$"{prefix}{entry.Key}"] = entry.Value; + } + + return this; + } + + /// + /// Builds the final native options dictionary. + /// + public IReadOnlyDictionary Build() + { + return options; + } + + /// + /// Builds a native options handle from managed key/value options. + /// + /// Managed options dictionary. + /// Native build function used to allocate and populate options payload. + /// Native release function used by the resulting handle. + /// A safe handle that owns the native options payload. + /// is null. + /// Native options build fails. + public static NativeOptionsHandle BuildNativeOptionsHandle( + IReadOnlyDictionary options, + NativeBuildOptionsDelegate build, + Action release) + { + ArgumentNullException.ThrowIfNull(options); + + var keys = new string[options.Count]; + var values = new string[options.Count]; + var index = 0; + + foreach (var option in options) + { + keys[index] = option.Key; + values[index] = option.Value; + index++; + } + + var result = build(keys, values, (nuint)options.Count); + var handle = Operator.ToValueOrThrowAndRelease(result); + return new NativeOptionsHandle(handle, release); + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Options/NativeOptionsHandle.cs b/bindings/dotnet/DotOpenDAL/Options/NativeOptionsHandle.cs new file mode 100644 index 000000000000..02e8ab69e100 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Options/NativeOptionsHandle.cs @@ -0,0 +1,48 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; + +namespace DotOpenDAL.Options; + +public sealed class NativeOptionsHandle : SafeHandle +{ + private readonly Action release; + + public NativeOptionsHandle(IntPtr ptr, Action release) + : base(IntPtr.Zero, true) + { + this.release = release; + SetHandle(ptr); + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + if (IsInvalid) + { + return true; + } + + release(handle); + handle = IntPtr.Zero; + return true; + } +} diff --git a/bindings/dotnet/DotOpenDAL/Options/OptionValidators.cs b/bindings/dotnet/DotOpenDAL/Options/OptionValidators.cs new file mode 100644 index 000000000000..bad4ec22e07f --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Options/OptionValidators.cs @@ -0,0 +1,70 @@ +/* + * 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. + */ + +namespace DotOpenDAL.Options; + +/// +/// Common validation helpers for numeric option values. +/// +internal static class OptionValidators +{ + /// + /// Ensures the value is greater than or equal to zero. + /// + public static void RequireGreaterThanOrEqualZero(long value, string paramName) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(paramName, $"{paramName} must be >= 0."); + } + } + + /// + /// Ensures the nullable value is greater than or equal to zero when provided. + /// + public static void RequireNullableGreaterThanOrEqualZero(long? value, string paramName) + { + if (value is < 0) + { + throw new ArgumentOutOfRangeException(paramName, $"{paramName} must be >= 0."); + } + } + + /// + /// Ensures the value is strictly greater than zero. + /// + public static void RequireGreaterThanZero(int value, string paramName) + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(paramName, $"{paramName} must be > 0."); + } + } + + /// + /// Ensures the nullable value is strictly greater than zero when provided. + /// + public static void RequireNullableGreaterThanZero(long? value, string paramName) + { + if (value is <= 0) + { + throw new ArgumentOutOfRangeException(paramName, $"{paramName} must be > 0 when provided."); + } + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Options/ReadOptions.cs b/bindings/dotnet/DotOpenDAL/Options/ReadOptions.cs new file mode 100644 index 000000000000..9cdf7bbba3fc --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Options/ReadOptions.cs @@ -0,0 +1,85 @@ +/* + * 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. + */ + +using DotOpenDAL.Options.Abstractions; + +namespace DotOpenDAL.Options; + +/// +/// Additional options for read operations. +/// +public sealed class ReadOptions : IOptions +{ + public long Offset { get; init; } + + public long? Length { get; init; } + + public string? Version { get; init; } + + public string? IfMatch { get; init; } + + public string? IfNoneMatch { get; init; } + + public DateTimeOffset? IfModifiedSince { get; init; } + + public DateTimeOffset? IfUnmodifiedSince { get; init; } + + public int Concurrent { get; init; } = 1; + + public long? Chunk { get; init; } + + public long? Gap { get; init; } + + public string? OverrideContentType { get; init; } + + public string? OverrideCacheControl { get; init; } + + public string? OverrideContentDisposition { get; init; } + + public NativeOptionsHandle BuildNativeOptionsHandle() + { + OptionValidators.RequireGreaterThanOrEqualZero(Offset, nameof(Offset)); + OptionValidators.RequireNullableGreaterThanOrEqualZero(Length, nameof(Length)); + OptionValidators.RequireGreaterThanZero(Concurrent, nameof(Concurrent)); + OptionValidators.RequireNullableGreaterThanZero(Chunk, nameof(Chunk)); + OptionValidators.RequireNullableGreaterThanZero(Gap, nameof(Gap)); + + var nativeOptions = new NativeOptionsBuilder() + .AddInt64IfNotDefault("offset", Offset, 0) + .AddNullableInt64("length", Length) + .AddString("version", Version) + .AddString("if_match", IfMatch) + .AddString("if_none_match", IfNoneMatch) + .AddUnixTimeMilliseconds("if_modified_since", IfModifiedSince) + .AddUnixTimeMilliseconds("if_unmodified_since", IfUnmodifiedSince) + .AddInt32IfNotDefault("concurrent", Concurrent, 1) + .AddNullableInt64("chunk", Chunk) + .AddNullableInt64("gap", Gap) + .AddString("override_content_type", OverrideContentType) + .AddString("override_cache_control", OverrideCacheControl) + .AddString("override_content_disposition", OverrideContentDisposition) + .Build(); + + return NativeOptionsBuilder.BuildNativeOptionsHandle( + nativeOptions, + NativeMethods.read_option_build, + NativeMethods.read_option_free + ); + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Options/StatOptions.cs b/bindings/dotnet/DotOpenDAL/Options/StatOptions.cs new file mode 100644 index 000000000000..bc018bd85393 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Options/StatOptions.cs @@ -0,0 +1,64 @@ +/* + * 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. + */ + +using DotOpenDAL.Options.Abstractions; + +namespace DotOpenDAL.Options; + +/// +/// Additional options for stat operations. +/// +public sealed class StatOptions : IOptions +{ + public string? Version { get; init; } + + public string? IfMatch { get; init; } + + public string? IfNoneMatch { get; init; } + + public DateTimeOffset? IfModifiedSince { get; init; } + + public DateTimeOffset? IfUnmodifiedSince { get; init; } + + public string? OverrideContentType { get; init; } + + public string? OverrideCacheControl { get; init; } + + public string? OverrideContentDisposition { get; init; } + + public NativeOptionsHandle BuildNativeOptionsHandle() + { + var nativeOptions = new NativeOptionsBuilder() + .AddString("version", Version) + .AddString("if_match", IfMatch) + .AddString("if_none_match", IfNoneMatch) + .AddUnixTimeMilliseconds("if_modified_since", IfModifiedSince) + .AddUnixTimeMilliseconds("if_unmodified_since", IfUnmodifiedSince) + .AddString("override_content_type", OverrideContentType) + .AddString("override_cache_control", OverrideCacheControl) + .AddString("override_content_disposition", OverrideContentDisposition) + .Build(); + + return NativeOptionsBuilder.BuildNativeOptionsHandle( + nativeOptions, + NativeMethods.stat_option_build, + NativeMethods.stat_option_free + ); + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/Options/WriteOptions.cs b/bindings/dotnet/DotOpenDAL/Options/WriteOptions.cs new file mode 100644 index 000000000000..a1fd4d65d754 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Options/WriteOptions.cs @@ -0,0 +1,76 @@ +/* + * 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. + */ + +using DotOpenDAL.Options.Abstractions; + +namespace DotOpenDAL.Options; + +/// +/// Additional options for write operations. +/// +public sealed class WriteOptions : IOptions +{ + public bool Append { get; init; } + + public string? CacheControl { get; init; } + + public string? ContentType { get; init; } + + public string? ContentDisposition { get; init; } + + public string? ContentEncoding { get; init; } + + public string? IfMatch { get; init; } + + public string? IfNoneMatch { get; init; } + + public bool IfNotExists { get; init; } + + public int Concurrent { get; init; } = 1; + + public long? Chunk { get; init; } + + public IReadOnlyDictionary? UserMetadata { get; init; } + + public NativeOptionsHandle BuildNativeOptionsHandle() + { + OptionValidators.RequireGreaterThanZero(Concurrent, nameof(Concurrent)); + OptionValidators.RequireNullableGreaterThanZero(Chunk, nameof(Chunk)); + + var nativeOptions = new NativeOptionsBuilder() + .AddBoolTrue("append", Append) + .AddString("cache_control", CacheControl) + .AddString("content_type", ContentType) + .AddString("content_disposition", ContentDisposition) + .AddString("content_encoding", ContentEncoding) + .AddString("if_match", IfMatch) + .AddString("if_none_match", IfNoneMatch) + .AddBoolTrue("if_not_exists", IfNotExists) + .AddInt32IfNotDefault("concurrent", Concurrent, 1) + .AddNullableInt64("chunk", Chunk) + .AddPrefixedEntries("user_metadata.", UserMetadata) + .Build(); + + return NativeOptionsBuilder.BuildNativeOptionsHandle( + nativeOptions, + NativeMethods.write_option_build, + NativeMethods.write_option_free + ); + } +} \ No newline at end of file diff --git a/bindings/dotnet/DotOpenDAL/PresignedRequest.cs b/bindings/dotnet/DotOpenDAL/PresignedRequest.cs new file mode 100644 index 000000000000..e72e0d29cd08 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/PresignedRequest.cs @@ -0,0 +1,48 @@ +/* + * 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. + */ + +namespace DotOpenDAL; + +/// +/// Represents a presigned HTTP request generated by OpenDAL. +/// +public sealed class PresignedRequest +{ + internal PresignedRequest(string method, string uri, IReadOnlyDictionary headers) + { + Method = method; + Uri = uri; + Headers = headers; + } + + /// + /// Gets HTTP method for this presigned request. + /// + public string Method { get; } + + /// + /// Gets target URI for this presigned request. + /// + public string Uri { get; } + + /// + /// Gets HTTP headers that should be sent with this request. + /// + public IReadOnlyDictionary Headers { get; } +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/Abstractions/IServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/Abstractions/IServiceConfig.cs new file mode 100644 index 000000000000..a55b4c64c6ec --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/Abstractions/IServiceConfig.cs @@ -0,0 +1,37 @@ +/* + * 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. + */ + +namespace DotOpenDAL.ServiceConfig.Abstractions; + +/// +/// Represents a typed backend configuration that can be converted to OpenDAL options. +/// +public interface IServiceConfig +{ + /// + /// Gets backend scheme name, such as memory or fs. + /// + string Scheme { get; } + + /// + /// Converts this configuration into key/value options passed to native OpenDAL. + /// + /// Backend-specific option map. + IReadOnlyDictionary ToOptions(); +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/AliyunDriveServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/AliyunDriveServiceConfig.cs new file mode 100644 index 000000000000..1f2fd13f94dc --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/AliyunDriveServiceConfig.cs @@ -0,0 +1,89 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service aliyun_drive. + /// + public sealed class AliyunDriveServiceConfig : IServiceConfig + { + /// + /// The access_token of this backend. Solution for client-only purpose. #4733 Required if no client_id, client_secret and refresh_token are provided. + /// + public string? AccessToken { get; init; } + /// + /// The client_id of this backend. Required if no access_token is provided. + /// + public string? ClientId { get; init; } + /// + /// The client_secret of this backend. Required if no access_token is provided. + /// + public string? ClientSecret { get; init; } + /// + /// The drive_type of this backend. All operations will happen under this type of drive. Available values are default, backup and resource. Fallback to default if not set or no other drives can be found. + /// + public string? DriveType { get; init; } + /// + /// The refresh_token of this backend. Required if no access_token is provided. + /// + public string? RefreshToken { get; init; } + /// + /// The Root of this backend. All operations will happen under this root. Default to / if not set. + /// + public string? Root { get; init; } + + public string Scheme => "aliyun_drive"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccessToken is not null) + { + map["access_token"] = Utilities.ToOptionString(AccessToken); + } + if (ClientId is not null) + { + map["client_id"] = Utilities.ToOptionString(ClientId); + } + if (ClientSecret is not null) + { + map["client_secret"] = Utilities.ToOptionString(ClientSecret); + } + if (DriveType is not null) + { + map["drive_type"] = Utilities.ToOptionString(DriveType); + } + if (RefreshToken is not null) + { + map["refresh_token"] = Utilities.ToOptionString(RefreshToken); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/AlluxioServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/AlluxioServiceConfig.cs new file mode 100644 index 000000000000..177c443de51c --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/AlluxioServiceConfig.cs @@ -0,0 +1,57 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service alluxio. + /// + public sealed class AlluxioServiceConfig : IServiceConfig + { + /// + /// endpoint of this backend. Endpoint must be full uri, mostly like http://127.0.0.1:39999. + /// + public string? Endpoint { get; init; } + /// + /// root of this backend. All operations will happen under this root. default to / if not set. + /// + public string? Root { get; init; } + + public string Scheme => "alluxio"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/AzblobServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/AzblobServiceConfig.cs new file mode 100644 index 000000000000..8ef06495f690 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/AzblobServiceConfig.cs @@ -0,0 +1,121 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service azblob. + /// + public sealed class AzblobServiceConfig : IServiceConfig + { + /// + /// The account key of Azblob service backend. + /// + public string? AccountKey { get; init; } + /// + /// The account name of Azblob service backend. + /// + public string? AccountName { get; init; } + /// + /// The maximum batch operations of Azblob service backend. + /// + public long? BatchMaxOperations { get; init; } + /// + /// The container name of Azblob service backend. + /// + public string? Container { get; init; } + /// + /// The encryption algorithm of Azblob service backend. + /// + public string? EncryptionAlgorithm { get; init; } + /// + /// The encryption key of Azblob service backend. + /// + public string? EncryptionKey { get; init; } + /// + /// The encryption key sha256 of Azblob service backend. + /// + public string? EncryptionKeySha256 { get; init; } + /// + /// The endpoint of Azblob service backend. Endpoint must be full uri, e.g. Azblob: https://accountname.blob.core.windows.net Azurite: http://127.0.0.1:10000/devstoreaccount1 + /// + public string? Endpoint { get; init; } + /// + /// The root of Azblob service backend. All operations will happen under this root. + /// + public string? Root { get; init; } + /// + /// The sas token of Azblob service backend. + /// + public string? SasToken { get; init; } + + public string Scheme => "azblob"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccountKey is not null) + { + map["account_key"] = Utilities.ToOptionString(AccountKey); + } + if (AccountName is not null) + { + map["account_name"] = Utilities.ToOptionString(AccountName); + } + if (BatchMaxOperations is not null) + { + map["batch_max_operations"] = Utilities.ToOptionString(BatchMaxOperations); + } + if (Container is not null) + { + map["container"] = Utilities.ToOptionString(Container); + } + if (EncryptionAlgorithm is not null) + { + map["encryption_algorithm"] = Utilities.ToOptionString(EncryptionAlgorithm); + } + if (EncryptionKey is not null) + { + map["encryption_key"] = Utilities.ToOptionString(EncryptionKey); + } + if (EncryptionKeySha256 is not null) + { + map["encryption_key_sha256"] = Utilities.ToOptionString(EncryptionKeySha256); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (SasToken is not null) + { + map["sas_token"] = Utilities.ToOptionString(SasToken); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/AzdlsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/AzdlsServiceConfig.cs new file mode 100644 index 000000000000..c27d3452ac17 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/AzdlsServiceConfig.cs @@ -0,0 +1,121 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service azdls. + /// + public sealed class AzdlsServiceConfig : IServiceConfig + { + /// + /// Account key of this backend. required for shared_key authentication + /// + public string? AccountKey { get; init; } + /// + /// Account name of this backend. + /// + public string? AccountName { get; init; } + /// + /// authority_host The authority host of the service principal. required for client_credentials authentication default value: https://login.microsoftonline.com + /// + public string? AuthorityHost { get; init; } + /// + /// client_id The client id of the service principal. required for client_credentials authentication + /// + public string? ClientId { get; init; } + /// + /// client_secret The client secret of the service principal. required for client_credentials authentication + /// + public string? ClientSecret { get; init; } + /// + /// Endpoint of this backend. + /// + public string? Endpoint { get; init; } + /// + /// Filesystem name of this backend. + /// + public string? Filesystem { get; init; } + /// + /// Root of this backend. + /// + public string? Root { get; init; } + /// + /// sas_token The shared access signature token. required for sas authentication + /// + public string? SasToken { get; init; } + /// + /// tenant_id The tenant id of the service principal. required for client_credentials authentication + /// + public string? TenantId { get; init; } + + public string Scheme => "azdls"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccountKey is not null) + { + map["account_key"] = Utilities.ToOptionString(AccountKey); + } + if (AccountName is not null) + { + map["account_name"] = Utilities.ToOptionString(AccountName); + } + if (AuthorityHost is not null) + { + map["authority_host"] = Utilities.ToOptionString(AuthorityHost); + } + if (ClientId is not null) + { + map["client_id"] = Utilities.ToOptionString(ClientId); + } + if (ClientSecret is not null) + { + map["client_secret"] = Utilities.ToOptionString(ClientSecret); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Filesystem is not null) + { + map["filesystem"] = Utilities.ToOptionString(Filesystem); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (SasToken is not null) + { + map["sas_token"] = Utilities.ToOptionString(SasToken); + } + if (TenantId is not null) + { + map["tenant_id"] = Utilities.ToOptionString(TenantId); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/AzfileServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/AzfileServiceConfig.cs new file mode 100644 index 000000000000..75ff9deef379 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/AzfileServiceConfig.cs @@ -0,0 +1,89 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service azfile. + /// + public sealed class AzfileServiceConfig : IServiceConfig + { + /// + /// The account key for azfile. + /// + public string? AccountKey { get; init; } + /// + /// The account name for azfile. + /// + public string? AccountName { get; init; } + /// + /// The endpoint for azfile. + /// + public string? Endpoint { get; init; } + /// + /// The root path for azfile. + /// + public string? Root { get; init; } + /// + /// The sas token for azfile. + /// + public string? SasToken { get; init; } + /// + /// The share name for azfile. + /// + public string? ShareName { get; init; } + + public string Scheme => "azfile"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccountKey is not null) + { + map["account_key"] = Utilities.ToOptionString(AccountKey); + } + if (AccountName is not null) + { + map["account_name"] = Utilities.ToOptionString(AccountName); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (SasToken is not null) + { + map["sas_token"] = Utilities.ToOptionString(SasToken); + } + if (ShareName is not null) + { + map["share_name"] = Utilities.ToOptionString(ShareName); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/B2ServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/B2ServiceConfig.cs new file mode 100644 index 000000000000..73ddb4a557e0 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/B2ServiceConfig.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service b2. + /// + public sealed class B2ServiceConfig : IServiceConfig + { + /// + /// applicationKey of this backend. If application_key is set, we will take user's input first. If not, we will try to load it from environment. + /// + public string? ApplicationKey { get; init; } + /// + /// keyID of this backend. If application_key_id is set, we will take user's input first. If not, we will try to load it from environment. + /// + public string? ApplicationKeyId { get; init; } + /// + /// bucket of this backend. required. + /// + public string? Bucket { get; init; } + /// + /// bucket id of this backend. required. + /// + public string? BucketId { get; init; } + /// + /// root of this backend. All operations will happen under this root. + /// + public string? Root { get; init; } + + public string Scheme => "b2"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (ApplicationKey is not null) + { + map["application_key"] = Utilities.ToOptionString(ApplicationKey); + } + if (ApplicationKeyId is not null) + { + map["application_key_id"] = Utilities.ToOptionString(ApplicationKeyId); + } + if (Bucket is not null) + { + map["bucket"] = Utilities.ToOptionString(Bucket); + } + if (BucketId is not null) + { + map["bucket_id"] = Utilities.ToOptionString(BucketId); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/CacacheServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/CacacheServiceConfig.cs new file mode 100644 index 000000000000..2076f34e5ee4 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/CacacheServiceConfig.cs @@ -0,0 +1,49 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service cacache. + /// + public sealed class CacacheServiceConfig : IServiceConfig + { + /// + /// That path to the cacache data directory. + /// + public string? Datadir { get; init; } + + public string Scheme => "cacache"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Datadir is not null) + { + map["datadir"] = Utilities.ToOptionString(Datadir); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/CloudflareKvServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/CloudflareKvServiceConfig.cs new file mode 100644 index 000000000000..e0e0b005040e --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/CloudflareKvServiceConfig.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service cloudflare_kv. + /// + public sealed class CloudflareKvServiceConfig : IServiceConfig + { + /// + /// The account ID used to authenticate with CloudFlare. Used as URI path parameter. + /// + public string? AccountId { get; init; } + /// + /// The token used to authenticate with CloudFlare. + /// + public string? ApiToken { get; init; } + /// + /// The default ttl for write operations. + /// + public string? DefaultTtl { get; init; } + /// + /// The namespace ID. Used as URI path parameter. + /// + public string? NamespaceId { get; init; } + /// + /// Root within this backend. + /// + public string? Root { get; init; } + + public string Scheme => "cloudflare_kv"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccountId is not null) + { + map["account_id"] = Utilities.ToOptionString(AccountId); + } + if (ApiToken is not null) + { + map["api_token"] = Utilities.ToOptionString(ApiToken); + } + if (DefaultTtl is not null) + { + map["default_ttl"] = Utilities.ToOptionString(DefaultTtl); + } + if (NamespaceId is not null) + { + map["namespace_id"] = Utilities.ToOptionString(NamespaceId); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/CompfsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/CompfsServiceConfig.cs new file mode 100644 index 000000000000..9ae0f4879184 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/CompfsServiceConfig.cs @@ -0,0 +1,49 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service compfs. + /// + public sealed class CompfsServiceConfig : IServiceConfig + { + /// + /// root of this backend. All operations will happen under this root. + /// + public string? Root { get; init; } + + public string Scheme => "compfs"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/CosServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/CosServiceConfig.cs new file mode 100644 index 000000000000..6e250d0e0cfe --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/CosServiceConfig.cs @@ -0,0 +1,97 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service cos. + /// + public sealed class CosServiceConfig : IServiceConfig + { + /// + /// Bucket of this backend. + /// + public string? Bucket { get; init; } + /// + /// Disable config load so that opendal will not load config from + /// + public bool? DisableConfigLoad { get; init; } + /// + /// is bucket versioning enabled for this bucket + /// + public bool? EnableVersioning { get; init; } + /// + /// Endpoint of this backend. + /// + public string? Endpoint { get; init; } + /// + /// Root of this backend. + /// + public string? Root { get; init; } + /// + /// Secret ID of this backend. + /// + public string? SecretId { get; init; } + /// + /// Secret key of this backend. + /// + public string? SecretKey { get; init; } + + public string Scheme => "cos"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Bucket is not null) + { + map["bucket"] = Utilities.ToOptionString(Bucket); + } + if (DisableConfigLoad is not null) + { + map["disable_config_load"] = Utilities.ToOptionString(DisableConfigLoad); + } + if (EnableVersioning is not null) + { + map["enable_versioning"] = Utilities.ToOptionString(EnableVersioning); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (SecretId is not null) + { + map["secret_id"] = Utilities.ToOptionString(SecretId); + } + if (SecretKey is not null) + { + map["secret_key"] = Utilities.ToOptionString(SecretKey); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/D1ServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/D1ServiceConfig.cs new file mode 100644 index 000000000000..fc64c209aab2 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/D1ServiceConfig.cs @@ -0,0 +1,97 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service d1. + /// + public sealed class D1ServiceConfig : IServiceConfig + { + /// + /// Set the account id of cloudflare api. + /// + public string? AccountId { get; init; } + /// + /// Set the database id of cloudflare api. + /// + public string? DatabaseId { get; init; } + /// + /// Set the key field of D1 Database. + /// + public string? KeyField { get; init; } + /// + /// Set the working directory of OpenDAL. + /// + public string? Root { get; init; } + /// + /// Set the table of D1 Database. + /// + public string? Table { get; init; } + /// + /// Set the token of cloudflare api. + /// + public string? Token { get; init; } + /// + /// Set the value field of D1 Database. + /// + public string? ValueField { get; init; } + + public string Scheme => "d1"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccountId is not null) + { + map["account_id"] = Utilities.ToOptionString(AccountId); + } + if (DatabaseId is not null) + { + map["database_id"] = Utilities.ToOptionString(DatabaseId); + } + if (KeyField is not null) + { + map["key_field"] = Utilities.ToOptionString(KeyField); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Table is not null) + { + map["table"] = Utilities.ToOptionString(Table); + } + if (Token is not null) + { + map["token"] = Utilities.ToOptionString(Token); + } + if (ValueField is not null) + { + map["value_field"] = Utilities.ToOptionString(ValueField); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/DashmapServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/DashmapServiceConfig.cs new file mode 100644 index 000000000000..c5ee1143acad --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/DashmapServiceConfig.cs @@ -0,0 +1,49 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service dashmap. + /// + public sealed class DashmapServiceConfig : IServiceConfig + { + /// + /// root path of this backend + /// + public string? Root { get; init; } + + public string Scheme => "dashmap"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/DbfsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/DbfsServiceConfig.cs new file mode 100644 index 000000000000..69b99422dcfb --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/DbfsServiceConfig.cs @@ -0,0 +1,65 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service dbfs. + /// + public sealed class DbfsServiceConfig : IServiceConfig + { + /// + /// The endpoint for dbfs. + /// + public string? Endpoint { get; init; } + /// + /// The root for dbfs. + /// + public string? Root { get; init; } + /// + /// The token for dbfs. + /// + public string? Token { get; init; } + + public string Scheme => "dbfs"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Token is not null) + { + map["token"] = Utilities.ToOptionString(Token); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/DropboxServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/DropboxServiceConfig.cs new file mode 100644 index 000000000000..393ac668c532 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/DropboxServiceConfig.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service dropbox. + /// + public sealed class DropboxServiceConfig : IServiceConfig + { + /// + /// access token for dropbox. + /// + public string? AccessToken { get; init; } + /// + /// client_id for dropbox. + /// + public string? ClientId { get; init; } + /// + /// client_secret for dropbox. + /// + public string? ClientSecret { get; init; } + /// + /// refresh_token for dropbox. + /// + public string? RefreshToken { get; init; } + /// + /// root path for dropbox. + /// + public string? Root { get; init; } + + public string Scheme => "dropbox"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccessToken is not null) + { + map["access_token"] = Utilities.ToOptionString(AccessToken); + } + if (ClientId is not null) + { + map["client_id"] = Utilities.ToOptionString(ClientId); + } + if (ClientSecret is not null) + { + map["client_secret"] = Utilities.ToOptionString(ClientSecret); + } + if (RefreshToken is not null) + { + map["refresh_token"] = Utilities.ToOptionString(RefreshToken); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/EtcdServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/EtcdServiceConfig.cs new file mode 100644 index 000000000000..44d143ca5d85 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/EtcdServiceConfig.cs @@ -0,0 +1,97 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service etcd. + /// + public sealed class EtcdServiceConfig : IServiceConfig + { + /// + /// certificate authority file path default is None + /// + public string? CaPath { get; init; } + /// + /// cert path default is None + /// + public string? CertPath { get; init; } + /// + /// network address of the Etcd services. If use https, must set TLS options: ca_path, cert_path, key_path. e.g. "127.0.0.1:23790,127.0.0.1:23791,127.0.0.1:23792" or "http://127.0.0.1:23790,http://127.0.0.1:23791,http://127.0.0.1:23792" or "https://127.0.0.1:23790,https://127.0.0.1:23791,https://127.0.0.1:23792" default is "http://127.0.0.1:2379" + /// + public string? Endpoints { get; init; } + /// + /// key path default is None + /// + public string? KeyPath { get; init; } + /// + /// the password for authentication default is None + /// + public string? Password { get; init; } + /// + /// the working directory of the etcd service. Can be "/path/to/dir" default is "/" + /// + public string? Root { get; init; } + /// + /// the username to connect etcd service. default is None + /// + public string? Username { get; init; } + + public string Scheme => "etcd"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (CaPath is not null) + { + map["ca_path"] = Utilities.ToOptionString(CaPath); + } + if (CertPath is not null) + { + map["cert_path"] = Utilities.ToOptionString(CertPath); + } + if (Endpoints is not null) + { + map["endpoints"] = Utilities.ToOptionString(Endpoints); + } + if (KeyPath is not null) + { + map["key_path"] = Utilities.ToOptionString(KeyPath); + } + if (Password is not null) + { + map["password"] = Utilities.ToOptionString(Password); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Username is not null) + { + map["username"] = Utilities.ToOptionString(Username); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/FsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/FsServiceConfig.cs new file mode 100644 index 000000000000..07e3c3c8a708 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/FsServiceConfig.cs @@ -0,0 +1,57 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service fs. + /// + public sealed class FsServiceConfig : IServiceConfig + { + /// + /// tmp dir for atomic write + /// + public string? AtomicWriteDir { get; init; } + /// + /// root dir for backend + /// + public string? Root { get; init; } + + public string Scheme => "fs"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AtomicWriteDir is not null) + { + map["atomic_write_dir"] = Utilities.ToOptionString(AtomicWriteDir); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/GcsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/GcsServiceConfig.cs new file mode 100644 index 000000000000..43cb7a9a2ecd --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/GcsServiceConfig.cs @@ -0,0 +1,145 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service gcs. + /// + public sealed class GcsServiceConfig : IServiceConfig + { + /// + /// Allow opendal to send requests without signing when credentials are not loaded. + /// + public bool? AllowAnonymous { get; init; } + /// + /// bucket name + /// + public string? Bucket { get; init; } + /// + /// Credentials string for GCS service OAuth2 authentication. + /// + public string? Credential { get; init; } + /// + /// Local path to credentials file for GCS service OAuth2 authentication. + /// + public string? CredentialPath { get; init; } + /// + /// The default storage class used by gcs. + /// + public string? DefaultStorageClass { get; init; } + /// + /// Disable loading configuration from the environment. + /// + public bool? DisableConfigLoad { get; init; } + /// + /// Disable attempting to load credentials from the GCE metadata server when running within Google Cloud. + /// + public bool? DisableVmMetadata { get; init; } + /// + /// endpoint URI of GCS service, default is https://storage.googleapis.com + /// + public string? Endpoint { get; init; } + /// + /// The predefined acl for GCS. + /// + public string? PredefinedAcl { get; init; } + /// + /// root URI, all operations happens under root + /// + public string? Root { get; init; } + /// + /// Scope for gcs. + /// + public string? Scope { get; init; } + /// + /// Service Account for gcs. + /// + public string? ServiceAccount { get; init; } + /// + /// A Google Cloud OAuth2 token. Takes precedence over credential and credential_path. + /// + public string? Token { get; init; } + + public string Scheme => "gcs"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AllowAnonymous is not null) + { + map["allow_anonymous"] = Utilities.ToOptionString(AllowAnonymous); + } + if (Bucket is not null) + { + map["bucket"] = Utilities.ToOptionString(Bucket); + } + if (Credential is not null) + { + map["credential"] = Utilities.ToOptionString(Credential); + } + if (CredentialPath is not null) + { + map["credential_path"] = Utilities.ToOptionString(CredentialPath); + } + if (DefaultStorageClass is not null) + { + map["default_storage_class"] = Utilities.ToOptionString(DefaultStorageClass); + } + if (DisableConfigLoad is not null) + { + map["disable_config_load"] = Utilities.ToOptionString(DisableConfigLoad); + } + if (DisableVmMetadata is not null) + { + map["disable_vm_metadata"] = Utilities.ToOptionString(DisableVmMetadata); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (PredefinedAcl is not null) + { + map["predefined_acl"] = Utilities.ToOptionString(PredefinedAcl); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Scope is not null) + { + map["scope"] = Utilities.ToOptionString(Scope); + } + if (ServiceAccount is not null) + { + map["service_account"] = Utilities.ToOptionString(ServiceAccount); + } + if (Token is not null) + { + map["token"] = Utilities.ToOptionString(Token); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/GdriveServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/GdriveServiceConfig.cs new file mode 100644 index 000000000000..96f73f1f4b04 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/GdriveServiceConfig.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service gdrive. + /// + public sealed class GdriveServiceConfig : IServiceConfig + { + /// + /// Access token for gdrive. + /// + public string? AccessToken { get; init; } + /// + /// Client id for gdrive. + /// + public string? ClientId { get; init; } + /// + /// Client secret for gdrive. + /// + public string? ClientSecret { get; init; } + /// + /// Refresh token for gdrive. + /// + public string? RefreshToken { get; init; } + /// + /// The root for gdrive + /// + public string? Root { get; init; } + + public string Scheme => "gdrive"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccessToken is not null) + { + map["access_token"] = Utilities.ToOptionString(AccessToken); + } + if (ClientId is not null) + { + map["client_id"] = Utilities.ToOptionString(ClientId); + } + if (ClientSecret is not null) + { + map["client_secret"] = Utilities.ToOptionString(ClientSecret); + } + if (RefreshToken is not null) + { + map["refresh_token"] = Utilities.ToOptionString(RefreshToken); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/GhacServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/GhacServiceConfig.cs new file mode 100644 index 000000000000..bf007da451ed --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/GhacServiceConfig.cs @@ -0,0 +1,73 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service ghac. + /// + public sealed class GhacServiceConfig : IServiceConfig + { + /// + /// The endpoint for ghac service. + /// + public string? Endpoint { get; init; } + /// + /// The root path for ghac. + /// + public string? Root { get; init; } + /// + /// The runtime token for ghac service. + /// + public string? RuntimeToken { get; init; } + /// + /// The version that used by cache. + /// + public string? Version { get; init; } + + public string Scheme => "ghac"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (RuntimeToken is not null) + { + map["runtime_token"] = Utilities.ToOptionString(RuntimeToken); + } + if (Version is not null) + { + map["version"] = Utilities.ToOptionString(Version); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/GithubServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/GithubServiceConfig.cs new file mode 100644 index 000000000000..b909af0d3d35 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/GithubServiceConfig.cs @@ -0,0 +1,73 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service github. + /// + public sealed class GithubServiceConfig : IServiceConfig + { + /// + /// GitHub repo owner. required. + /// + public string? Owner { get; init; } + /// + /// GitHub repo name. required. + /// + public string? Repo { get; init; } + /// + /// root of this backend. All operations will happen under this root. + /// + public string? Root { get; init; } + /// + /// GitHub access_token. optional. If not provided, the backend will only support read operations for public repositories. And rate limit will be limited to 60 requests per hour. + /// + public string? Token { get; init; } + + public string Scheme => "github"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Owner is not null) + { + map["owner"] = Utilities.ToOptionString(Owner); + } + if (Repo is not null) + { + map["repo"] = Utilities.ToOptionString(Repo); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Token is not null) + { + map["token"] = Utilities.ToOptionString(Token); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/GridfsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/GridfsServiceConfig.cs new file mode 100644 index 000000000000..d2e6c89cffa4 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/GridfsServiceConfig.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service gridfs. + /// + public sealed class GridfsServiceConfig : IServiceConfig + { + /// + /// The bucket name of the MongoDB GridFs service to read/write. + /// + public string? Bucket { get; init; } + /// + /// The chunk size of the MongoDB GridFs service used to break the user file into chunks. + /// + public int? ChunkSize { get; init; } + /// + /// The connection string of the MongoDB service. + /// + public string? ConnectionString { get; init; } + /// + /// The database name of the MongoDB GridFs service to read/write. + /// + public string? Database { get; init; } + /// + /// The working directory, all operations will be performed under it. + /// + public string? Root { get; init; } + + public string Scheme => "gridfs"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Bucket is not null) + { + map["bucket"] = Utilities.ToOptionString(Bucket); + } + if (ChunkSize is not null) + { + map["chunk_size"] = Utilities.ToOptionString(ChunkSize); + } + if (ConnectionString is not null) + { + map["connection_string"] = Utilities.ToOptionString(ConnectionString); + } + if (Database is not null) + { + map["database"] = Utilities.ToOptionString(Database); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/HdfsNativeServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/HdfsNativeServiceConfig.cs new file mode 100644 index 000000000000..106b051e9bcc --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/HdfsNativeServiceConfig.cs @@ -0,0 +1,65 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service hdfs_native. + /// + public sealed class HdfsNativeServiceConfig : IServiceConfig + { + /// + /// enable the append capacity + /// + public bool? EnableAppend { get; init; } + /// + /// name_node of this backend + /// + public string? NameNode { get; init; } + /// + /// work dir of this backend + /// + public string? Root { get; init; } + + public string Scheme => "hdfs_native"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (EnableAppend is not null) + { + map["enable_append"] = Utilities.ToOptionString(EnableAppend); + } + if (NameNode is not null) + { + map["name_node"] = Utilities.ToOptionString(NameNode); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/HttpServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/HttpServiceConfig.cs new file mode 100644 index 000000000000..46c0d2802b38 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/HttpServiceConfig.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service http. + /// + public sealed class HttpServiceConfig : IServiceConfig + { + /// + /// endpoint of this backend + /// + public string? Endpoint { get; init; } + /// + /// password of this backend + /// + public string? Password { get; init; } + /// + /// root of this backend + /// + public string? Root { get; init; } + /// + /// token of this backend + /// + public string? Token { get; init; } + /// + /// username of this backend + /// + public string? Username { get; init; } + + public string Scheme => "http"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Password is not null) + { + map["password"] = Utilities.ToOptionString(Password); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Token is not null) + { + map["token"] = Utilities.ToOptionString(Token); + } + if (Username is not null) + { + map["username"] = Utilities.ToOptionString(Username); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/HuggingfaceServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/HuggingfaceServiceConfig.cs new file mode 100644 index 000000000000..844382324e4b --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/HuggingfaceServiceConfig.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service huggingface. + /// + public sealed class HuggingfaceServiceConfig : IServiceConfig + { + /// + /// Repo id of this backend. This is required. + /// + public string? RepoId { get; init; } + /// + /// Repo type of this backend. Default is model. Available values: model dataset + /// + public string? RepoType { get; init; } + /// + /// Revision of this backend. Default is main. + /// + public string? Revision { get; init; } + /// + /// Root of this backend. Can be "/path/to/dir". Default is "/". + /// + public string? Root { get; init; } + /// + /// Token of this backend. This is optional. + /// + public string? Token { get; init; } + + public string Scheme => "huggingface"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (RepoId is not null) + { + map["repo_id"] = Utilities.ToOptionString(RepoId); + } + if (RepoType is not null) + { + map["repo_type"] = Utilities.ToOptionString(RepoType); + } + if (Revision is not null) + { + map["revision"] = Utilities.ToOptionString(Revision); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Token is not null) + { + map["token"] = Utilities.ToOptionString(Token); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/IpfsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/IpfsServiceConfig.cs new file mode 100644 index 000000000000..5f27e01c8a9a --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/IpfsServiceConfig.cs @@ -0,0 +1,57 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service ipfs. + /// + public sealed class IpfsServiceConfig : IServiceConfig + { + /// + /// IPFS gateway endpoint. + /// + public string? Endpoint { get; init; } + /// + /// IPFS root. + /// + public string? Root { get; init; } + + public string Scheme => "ipfs"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/IpmfsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/IpmfsServiceConfig.cs new file mode 100644 index 000000000000..c089b76fdc40 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/IpmfsServiceConfig.cs @@ -0,0 +1,57 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service ipmfs. + /// + public sealed class IpmfsServiceConfig : IServiceConfig + { + /// + /// Endpoint for ipfs. + /// + public string? Endpoint { get; init; } + /// + /// Root for ipfs. + /// + public string? Root { get; init; } + + public string Scheme => "ipmfs"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/KoofrServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/KoofrServiceConfig.cs new file mode 100644 index 000000000000..d6cd4e638420 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/KoofrServiceConfig.cs @@ -0,0 +1,73 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service koofr. + /// + public sealed class KoofrServiceConfig : IServiceConfig + { + /// + /// Koofr email. + /// + public string? Email { get; init; } + /// + /// Koofr endpoint. + /// + public string? Endpoint { get; init; } + /// + /// password of this backend. (Must be the application password) + /// + public string? Password { get; init; } + /// + /// root of this backend. All operations will happen under this root. + /// + public string? Root { get; init; } + + public string Scheme => "koofr"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Email is not null) + { + map["email"] = Utilities.ToOptionString(Email); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Password is not null) + { + map["password"] = Utilities.ToOptionString(Password); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/LakefsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/LakefsServiceConfig.cs new file mode 100644 index 000000000000..c45c2bd00c23 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/LakefsServiceConfig.cs @@ -0,0 +1,89 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service lakefs. + /// + public sealed class LakefsServiceConfig : IServiceConfig + { + /// + /// Name of the branch or a commit ID. Default is main. This is optional. + /// + public string? Branch { get; init; } + /// + /// Base url. This is required. + /// + public string? Endpoint { get; init; } + /// + /// Password for Lakefs basic authentication. This is required. + /// + public string? Password { get; init; } + /// + /// The repository name This is required. + /// + public string? Repository { get; init; } + /// + /// Root of this backend. Can be "/path/to/dir". Default is "/". + /// + public string? Root { get; init; } + /// + /// Username for Lakefs basic authentication. This is required. + /// + public string? Username { get; init; } + + public string Scheme => "lakefs"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Branch is not null) + { + map["branch"] = Utilities.ToOptionString(Branch); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Password is not null) + { + map["password"] = Utilities.ToOptionString(Password); + } + if (Repository is not null) + { + map["repository"] = Utilities.ToOptionString(Repository); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Username is not null) + { + map["username"] = Utilities.ToOptionString(Username); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/MemcachedServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/MemcachedServiceConfig.cs new file mode 100644 index 000000000000..b2a16fa74dbd --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/MemcachedServiceConfig.cs @@ -0,0 +1,89 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service memcached. + /// + public sealed class MemcachedServiceConfig : IServiceConfig + { + /// + /// The maximum number of connections allowed. default is 10 + /// + public int? ConnectionPoolMaxSize { get; init; } + /// + /// The default ttl for put operations. + /// + public string? DefaultTtl { get; init; } + /// + /// network address of the memcached service. For example: "tcp://localhost:11211" + /// + public string? Endpoint { get; init; } + /// + /// Memcached password, optional. + /// + public string? Password { get; init; } + /// + /// the working directory of the service. Can be "/path/to/dir" default is "/" + /// + public string? Root { get; init; } + /// + /// Memcached username, optional. + /// + public string? Username { get; init; } + + public string Scheme => "memcached"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (ConnectionPoolMaxSize is not null) + { + map["connection_pool_max_size"] = Utilities.ToOptionString(ConnectionPoolMaxSize); + } + if (DefaultTtl is not null) + { + map["default_ttl"] = Utilities.ToOptionString(DefaultTtl); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Password is not null) + { + map["password"] = Utilities.ToOptionString(Password); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Username is not null) + { + map["username"] = Utilities.ToOptionString(Username); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/MemoryServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/MemoryServiceConfig.cs new file mode 100644 index 000000000000..40baecf3f494 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/MemoryServiceConfig.cs @@ -0,0 +1,49 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service memory. + /// + public sealed class MemoryServiceConfig : IServiceConfig + { + /// + /// root of the backend. + /// + public string? Root { get; init; } + + public string Scheme => "memory"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/MiniMokaServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/MiniMokaServiceConfig.cs new file mode 100644 index 000000000000..6d104d41b62d --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/MiniMokaServiceConfig.cs @@ -0,0 +1,73 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service mini_moka. + /// + public sealed class MiniMokaServiceConfig : IServiceConfig + { + /// + /// Sets the max capacity of the cache. Refer to mini-moka::sync::CacheBuilder::max_capacity + /// + public long? MaxCapacity { get; init; } + /// + /// root path of this backend + /// + public string? Root { get; init; } + /// + /// Sets the time to idle of the cache. Refer to mini-moka::sync::CacheBuilder::time_to_idle + /// + public string? TimeToIdle { get; init; } + /// + /// Sets the time to live of the cache. Refer to mini-moka::sync::CacheBuilder::time_to_live + /// + public string? TimeToLive { get; init; } + + public string Scheme => "mini_moka"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (MaxCapacity is not null) + { + map["max_capacity"] = Utilities.ToOptionString(MaxCapacity); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (TimeToIdle is not null) + { + map["time_to_idle"] = Utilities.ToOptionString(TimeToIdle); + } + if (TimeToLive is not null) + { + map["time_to_live"] = Utilities.ToOptionString(TimeToLive); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/MokaServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/MokaServiceConfig.cs new file mode 100644 index 000000000000..81b5655d3cfe --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/MokaServiceConfig.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service moka. + /// + public sealed class MokaServiceConfig : IServiceConfig + { + /// + /// Sets the max capacity of the cache. Refer to moka::future::CacheBuilder::max_capacity + /// + public long? MaxCapacity { get; init; } + /// + /// Name for this cache instance. + /// + public string? Name { get; init; } + /// + /// root path of this backend + /// + public string? Root { get; init; } + /// + /// Sets the time to idle of the cache. Refer to moka::future::CacheBuilder::time_to_idle + /// + public string? TimeToIdle { get; init; } + /// + /// Sets the time to live of the cache. Refer to moka::future::CacheBuilder::time_to_live + /// + public string? TimeToLive { get; init; } + + public string Scheme => "moka"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (MaxCapacity is not null) + { + map["max_capacity"] = Utilities.ToOptionString(MaxCapacity); + } + if (Name is not null) + { + map["name"] = Utilities.ToOptionString(Name); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (TimeToIdle is not null) + { + map["time_to_idle"] = Utilities.ToOptionString(TimeToIdle); + } + if (TimeToLive is not null) + { + map["time_to_live"] = Utilities.ToOptionString(TimeToLive); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/MongodbServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/MongodbServiceConfig.cs new file mode 100644 index 000000000000..bdf557fc9e3b --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/MongodbServiceConfig.cs @@ -0,0 +1,89 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service mongodb. + /// + public sealed class MongodbServiceConfig : IServiceConfig + { + /// + /// collection of this backend + /// + public string? Collection { get; init; } + /// + /// connection string of this backend + /// + public string? ConnectionString { get; init; } + /// + /// database of this backend + /// + public string? Database { get; init; } + /// + /// key field of this backend + /// + public string? KeyField { get; init; } + /// + /// root of this backend + /// + public string? Root { get; init; } + /// + /// value field of this backend + /// + public string? ValueField { get; init; } + + public string Scheme => "mongodb"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Collection is not null) + { + map["collection"] = Utilities.ToOptionString(Collection); + } + if (ConnectionString is not null) + { + map["connection_string"] = Utilities.ToOptionString(ConnectionString); + } + if (Database is not null) + { + map["database"] = Utilities.ToOptionString(Database); + } + if (KeyField is not null) + { + map["key_field"] = Utilities.ToOptionString(KeyField); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (ValueField is not null) + { + map["value_field"] = Utilities.ToOptionString(ValueField); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/MonoiofsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/MonoiofsServiceConfig.cs new file mode 100644 index 000000000000..a4830b3c6c69 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/MonoiofsServiceConfig.cs @@ -0,0 +1,49 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service monoiofs. + /// + public sealed class MonoiofsServiceConfig : IServiceConfig + { + /// + /// The Root of this backend. All operations will happen under this root. Builder::build will return error if not set. + /// + public string? Root { get; init; } + + public string Scheme => "monoiofs"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/MysqlServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/MysqlServiceConfig.cs new file mode 100644 index 000000000000..0020298f8bf2 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/MysqlServiceConfig.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service mysql. + /// + public sealed class MysqlServiceConfig : IServiceConfig + { + /// + /// This connection string is used to connect to the mysql service. There are url based formats. The format of connect string resembles the url format of the mysql client. The format is: [scheme://][user[:[password]]@]host[:port][/schema][?attribute1=value1&attribute2=value2... mysql://user@localhost mysql://user:password@localhost mysql://user:password@localhost:3306 mysql://user:password@localhost:3306/db For more information, please refer to https://docs.rs/sqlx/latest/sqlx/mysql/struct.MySqlConnectOptions.html . + /// + public string? ConnectionString { get; init; } + /// + /// The key field name for mysql. + /// + public string? KeyField { get; init; } + /// + /// The root for mysql. + /// + public string? Root { get; init; } + /// + /// The table name for mysql. + /// + public string? Table { get; init; } + /// + /// The value field name for mysql. + /// + public string? ValueField { get; init; } + + public string Scheme => "mysql"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (ConnectionString is not null) + { + map["connection_string"] = Utilities.ToOptionString(ConnectionString); + } + if (KeyField is not null) + { + map["key_field"] = Utilities.ToOptionString(KeyField); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Table is not null) + { + map["table"] = Utilities.ToOptionString(Table); + } + if (ValueField is not null) + { + map["value_field"] = Utilities.ToOptionString(ValueField); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/ObsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/ObsServiceConfig.cs new file mode 100644 index 000000000000..76ac5bf32a65 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/ObsServiceConfig.cs @@ -0,0 +1,89 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service obs. + /// + public sealed class ObsServiceConfig : IServiceConfig + { + /// + /// Access key id for obs. + /// + public string? AccessKeyId { get; init; } + /// + /// Bucket for obs. + /// + public string? Bucket { get; init; } + /// + /// Is bucket versioning enabled for this bucket + /// + public bool? EnableVersioning { get; init; } + /// + /// Endpoint for obs. + /// + public string? Endpoint { get; init; } + /// + /// Root for obs. + /// + public string? Root { get; init; } + /// + /// Secret access key for obs. + /// + public string? SecretAccessKey { get; init; } + + public string Scheme => "obs"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccessKeyId is not null) + { + map["access_key_id"] = Utilities.ToOptionString(AccessKeyId); + } + if (Bucket is not null) + { + map["bucket"] = Utilities.ToOptionString(Bucket); + } + if (EnableVersioning is not null) + { + map["enable_versioning"] = Utilities.ToOptionString(EnableVersioning); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (SecretAccessKey is not null) + { + map["secret_access_key"] = Utilities.ToOptionString(SecretAccessKey); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/OnedriveServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/OnedriveServiceConfig.cs new file mode 100644 index 000000000000..f1df3856f326 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/OnedriveServiceConfig.cs @@ -0,0 +1,89 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service onedrive. + /// + public sealed class OnedriveServiceConfig : IServiceConfig + { + /// + /// Microsoft Graph API (also OneDrive API) access token + /// + public string? AccessToken { get; init; } + /// + /// Microsoft Graph API Application (client) ID that is in the Azure's app registration portal + /// + public string? ClientId { get; init; } + /// + /// Microsoft Graph API Application client secret that is in the Azure's app registration portal + /// + public string? ClientSecret { get; init; } + /// + /// Enabling version support + /// + public bool? EnableVersioning { get; init; } + /// + /// Microsoft Graph API (also OneDrive API) refresh token + /// + public string? RefreshToken { get; init; } + /// + /// The root path for the OneDrive service for the file access + /// + public string? Root { get; init; } + + public string Scheme => "onedrive"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccessToken is not null) + { + map["access_token"] = Utilities.ToOptionString(AccessToken); + } + if (ClientId is not null) + { + map["client_id"] = Utilities.ToOptionString(ClientId); + } + if (ClientSecret is not null) + { + map["client_secret"] = Utilities.ToOptionString(ClientSecret); + } + if (EnableVersioning is not null) + { + map["enable_versioning"] = Utilities.ToOptionString(EnableVersioning); + } + if (RefreshToken is not null) + { + map["refresh_token"] = Utilities.ToOptionString(RefreshToken); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/OpfsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/OpfsServiceConfig.cs new file mode 100644 index 000000000000..0edff7d9af39 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/OpfsServiceConfig.cs @@ -0,0 +1,40 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service opfs. + /// + public sealed class OpfsServiceConfig : IServiceConfig + { + public string Scheme => "opfs"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/OssServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/OssServiceConfig.cs new file mode 100644 index 000000000000..3eb5b0be7ad1 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/OssServiceConfig.cs @@ -0,0 +1,201 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service oss. + /// + public sealed class OssServiceConfig : IServiceConfig + { + /// + /// Access key id for oss. this field if it's is_some env value: [ALIBABA_CLOUD_ACCESS_KEY_ID] + /// + public string? AccessKeyId { get; init; } + /// + /// Access key secret for oss. this field if it's is_some env value: [ALIBABA_CLOUD_ACCESS_KEY_SECRET] + /// + public string? AccessKeySecret { get; init; } + /// + /// Addressing style for oss. + /// + public string? AddressingStyle { get; init; } + /// + /// Allow anonymous for oss. + /// + public bool? AllowAnonymous { get; init; } + /// + /// The size of max batch operations. + /// + public long? BatchMaxOperations { get; init; } + /// + /// Bucket for oss. + /// + public string? Bucket { get; init; } + /// + /// The size of max delete operations. + /// + public long? DeleteMaxSize { get; init; } + /// + /// is bucket versioning enabled for this bucket + /// + public bool? EnableVersioning { get; init; } + /// + /// Endpoint for oss. + /// + public string? Endpoint { get; init; } + /// + /// oidc_provider_arn will be loaded from this field if it's is_some env value: [ALIBABA_CLOUD_OIDC_PROVIDER_ARN] + /// + public string? OidcProviderArn { get; init; } + /// + /// oidc_token_file will be loaded from this field if it's is_some env value: [ALIBABA_CLOUD_OIDC_TOKEN_FILE] + /// + public string? OidcTokenFile { get; init; } + /// + /// Pre sign addressing style for oss. + /// + public string? PresignAddressingStyle { get; init; } + /// + /// Presign endpoint for oss. + /// + public string? PresignEndpoint { get; init; } + /// + /// If role_arn is set, we will use already known config as source credential to assume role with role_arn. this field if it's is_some env value: [ALIBABA_CLOUD_ROLE_ARN] + /// + public string? RoleArn { get; init; } + /// + /// role_session_name for this backend. + /// + public string? RoleSessionName { get; init; } + /// + /// Root for oss. + /// + public string? Root { get; init; } + /// + /// security_token will be loaded from this field if it's is_some env value: [ALIBABA_CLOUD_SECURITY_TOKEN] + /// + public string? SecurityToken { get; init; } + /// + /// Server side encryption for oss. + /// + public string? ServerSideEncryption { get; init; } + /// + /// Server side encryption key id for oss. + /// + public string? ServerSideEncryptionKeyId { get; init; } + /// + /// sts_endpoint will be loaded from this field if it's is_some env value: [ALIBABA_CLOUD_STS_ENDPOINT] + /// + public string? StsEndpoint { get; init; } + + public string Scheme => "oss"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccessKeyId is not null) + { + map["access_key_id"] = Utilities.ToOptionString(AccessKeyId); + } + if (AccessKeySecret is not null) + { + map["access_key_secret"] = Utilities.ToOptionString(AccessKeySecret); + } + if (AddressingStyle is not null) + { + map["addressing_style"] = Utilities.ToOptionString(AddressingStyle); + } + if (AllowAnonymous is not null) + { + map["allow_anonymous"] = Utilities.ToOptionString(AllowAnonymous); + } + if (BatchMaxOperations is not null) + { + map["batch_max_operations"] = Utilities.ToOptionString(BatchMaxOperations); + } + if (Bucket is not null) + { + map["bucket"] = Utilities.ToOptionString(Bucket); + } + if (DeleteMaxSize is not null) + { + map["delete_max_size"] = Utilities.ToOptionString(DeleteMaxSize); + } + if (EnableVersioning is not null) + { + map["enable_versioning"] = Utilities.ToOptionString(EnableVersioning); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (OidcProviderArn is not null) + { + map["oidc_provider_arn"] = Utilities.ToOptionString(OidcProviderArn); + } + if (OidcTokenFile is not null) + { + map["oidc_token_file"] = Utilities.ToOptionString(OidcTokenFile); + } + if (PresignAddressingStyle is not null) + { + map["presign_addressing_style"] = Utilities.ToOptionString(PresignAddressingStyle); + } + if (PresignEndpoint is not null) + { + map["presign_endpoint"] = Utilities.ToOptionString(PresignEndpoint); + } + if (RoleArn is not null) + { + map["role_arn"] = Utilities.ToOptionString(RoleArn); + } + if (RoleSessionName is not null) + { + map["role_session_name"] = Utilities.ToOptionString(RoleSessionName); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (SecurityToken is not null) + { + map["security_token"] = Utilities.ToOptionString(SecurityToken); + } + if (ServerSideEncryption is not null) + { + map["server_side_encryption"] = Utilities.ToOptionString(ServerSideEncryption); + } + if (ServerSideEncryptionKeyId is not null) + { + map["server_side_encryption_key_id"] = Utilities.ToOptionString(ServerSideEncryptionKeyId); + } + if (StsEndpoint is not null) + { + map["sts_endpoint"] = Utilities.ToOptionString(StsEndpoint); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/PcloudServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/PcloudServiceConfig.cs new file mode 100644 index 000000000000..c4a9e90c79b5 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/PcloudServiceConfig.cs @@ -0,0 +1,73 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service pcloud. + /// + public sealed class PcloudServiceConfig : IServiceConfig + { + /// + /// pCloud endpoint address. + /// + public string? Endpoint { get; init; } + /// + /// pCloud password. + /// + public string? Password { get; init; } + /// + /// root of this backend. All operations will happen under this root. + /// + public string? Root { get; init; } + /// + /// pCloud username. + /// + public string? Username { get; init; } + + public string Scheme => "pcloud"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Password is not null) + { + map["password"] = Utilities.ToOptionString(Password); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Username is not null) + { + map["username"] = Utilities.ToOptionString(Username); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/PersyServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/PersyServiceConfig.cs new file mode 100644 index 000000000000..e3fe91047569 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/PersyServiceConfig.cs @@ -0,0 +1,65 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service persy. + /// + public sealed class PersyServiceConfig : IServiceConfig + { + /// + /// That path to the persy data file. The directory in the path must already exist. + /// + public string? Datafile { get; init; } + /// + /// That name of the persy index. + /// + public string? Index { get; init; } + /// + /// That name of the persy segment. + /// + public string? Segment { get; init; } + + public string Scheme => "persy"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Datafile is not null) + { + map["datafile"] = Utilities.ToOptionString(Datafile); + } + if (Index is not null) + { + map["index"] = Utilities.ToOptionString(Index); + } + if (Segment is not null) + { + map["segment"] = Utilities.ToOptionString(Segment); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/PostgresqlServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/PostgresqlServiceConfig.cs new file mode 100644 index 000000000000..da8aa8a708e6 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/PostgresqlServiceConfig.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service postgresql. + /// + public sealed class PostgresqlServiceConfig : IServiceConfig + { + /// + /// The URL should be with a scheme of either postgres:// or postgresql://. postgresql://user@localhost postgresql://user:password@%2Fvar%2Flib%2Fpostgresql/mydb?connect_timeout=10 postgresql://user@host1:1234,host2,host3:5678?target_session_attrs=read-write postgresql:///mydb?user=user&host=/var/lib/postgresql For more information, please visit https://docs.rs/sqlx/latest/sqlx/postgres/struct.PgConnectOptions.html . + /// + public string? ConnectionString { get; init; } + /// + /// the key field of postgresql + /// + public string? KeyField { get; init; } + /// + /// Root of this backend. All operations will happen under this root. Default to / if not set. + /// + public string? Root { get; init; } + /// + /// the table of postgresql + /// + public string? Table { get; init; } + /// + /// the value field of postgresql + /// + public string? ValueField { get; init; } + + public string Scheme => "postgresql"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (ConnectionString is not null) + { + map["connection_string"] = Utilities.ToOptionString(ConnectionString); + } + if (KeyField is not null) + { + map["key_field"] = Utilities.ToOptionString(KeyField); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Table is not null) + { + map["table"] = Utilities.ToOptionString(Table); + } + if (ValueField is not null) + { + map["value_field"] = Utilities.ToOptionString(ValueField); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/RedbServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/RedbServiceConfig.cs new file mode 100644 index 000000000000..cc658660b4e1 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/RedbServiceConfig.cs @@ -0,0 +1,65 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service redb. + /// + public sealed class RedbServiceConfig : IServiceConfig + { + /// + /// path to the redb data directory. + /// + public string? Datadir { get; init; } + /// + /// The root for redb. + /// + public string? Root { get; init; } + /// + /// The table name for redb. + /// + public string? Table { get; init; } + + public string Scheme => "redb"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Datadir is not null) + { + map["datadir"] = Utilities.ToOptionString(Datadir); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Table is not null) + { + map["table"] = Utilities.ToOptionString(Table); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/RedisServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/RedisServiceConfig.cs new file mode 100644 index 000000000000..be2cff7d12eb --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/RedisServiceConfig.cs @@ -0,0 +1,102 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service redis. + /// + public sealed class RedisServiceConfig : IServiceConfig + { + /// + /// network address of the Redis cluster service. Can be "tcp://127.0.0.1:6379,tcp://127.0.0.1:6380,tcp://127.0.0.1:6381", e.g. default is None + /// + public string? ClusterEndpoints { get; init; } + /// + /// The maximum number of connections allowed. default is 10 + /// + public int? ConnectionPoolMaxSize { get; init; } + /// + /// the number of DBs redis can take is unlimited default is db 0 + /// + public long Db { get; init; } + /// + /// The default ttl for put operations. + /// + public string? DefaultTtl { get; init; } + /// + /// network address of the Redis service. Can be "tcp://127.0.0.1:6379", e.g. default is "tcp://127.0.0.1:6379" + /// + public string? Endpoint { get; init; } + /// + /// the password for authentication default is None + /// + public string? Password { get; init; } + /// + /// the working directory of the Redis service. Can be "/path/to/dir" default is "/" + /// + public string? Root { get; init; } + /// + /// the username to connect redis service. default is None + /// + public string? Username { get; init; } + + public string Scheme => "redis"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (ClusterEndpoints is not null) + { + map["cluster_endpoints"] = Utilities.ToOptionString(ClusterEndpoints); + } + if (ConnectionPoolMaxSize is not null) + { + map["connection_pool_max_size"] = Utilities.ToOptionString(ConnectionPoolMaxSize); + } + map["db"] = Utilities.ToOptionString(Db); + if (DefaultTtl is not null) + { + map["default_ttl"] = Utilities.ToOptionString(DefaultTtl); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Password is not null) + { + map["password"] = Utilities.ToOptionString(Password); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Username is not null) + { + map["username"] = Utilities.ToOptionString(Username); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/S3ServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/S3ServiceConfig.cs new file mode 100644 index 000000000000..e1e93a99f591 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/S3ServiceConfig.cs @@ -0,0 +1,273 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service s3. + /// + public sealed class S3ServiceConfig : IServiceConfig + { + /// + /// access_key_id of this backend. If access_key_id is set, we will take user's input first. If not, we will try to load it from environment. + /// + public string? AccessKeyId { get; init; } + /// + /// Allow anonymous will allow opendal to send request without signing when credential is not loaded. + /// + public bool? AllowAnonymous { get; init; } + /// + /// Set maximum batch operations of this backend. Some compatible services have a limit on the number of operations in a batch request. For example, R2 could return Internal Error while batch delete 1000 files. Please tune this value based on services' document. + /// + public long? BatchMaxOperations { get; init; } + /// + /// bucket name of this backend. required. + /// + public string? Bucket { get; init; } + /// + /// Checksum Algorithm to use when sending checksums in HTTP headers. This is necessary when writing to AWS S3 Buckets with Object Lock enabled for example. Available options: "crc32c" + /// + public string? ChecksumAlgorithm { get; init; } + /// + /// default storage_class for this backend. Available values: DEEP_ARCHIVE GLACIER GLACIER_IR INTELLIGENT_TIERING ONEZONE_IA EXPRESS_ONEZONE OUTPOSTS REDUCED_REDUNDANCY STANDARD STANDARD_IA S3 compatible services don't support all of them + /// + public string? DefaultStorageClass { get; init; } + /// + /// Set the maximum delete size of this backend. Some compatible services have a limit on the number of operations in a batch request. For example, R2 could return Internal Error while batch delete 1000 files. Please tune this value based on services' document. + /// + public long? DeleteMaxSize { get; init; } + /// + /// Disable config load so that opendal will not load config from environment. For examples: envs like AWS_ACCESS_KEY_ID files like ~/.aws/config + /// + public bool? DisableConfigLoad { get; init; } + /// + /// Disable load credential from ec2 metadata. This option is used to disable the default behavior of opendal to load credential from ec2 metadata, a.k.a, IMDSv2 + /// + public bool? DisableEc2Metadata { get; init; } + /// + /// OpenDAL uses List Objects V2 by default to list objects. However, some legacy services do not yet support V2. This option allows users to switch back to the older List Objects V1. + /// + public bool? DisableListObjectsV2 { get; init; } + /// + /// Disable stat with override so that opendal will not send stat request with override queries. For example, R2 doesn't support stat with response_content_type query. + /// + public bool? DisableStatWithOverride { get; init; } + /// + /// Disable write with if match so that opendal will not send write request with if match headers. For example, Ceph RADOS S3 doesn't support write with if match. + /// + public bool? DisableWriteWithIfMatch { get; init; } + /// + /// Indicates whether the client agrees to pay for the requests made to the S3 bucket. + /// + public bool? EnableRequestPayer { get; init; } + /// + /// is bucket versioning enabled for this bucket + /// + public bool? EnableVersioning { get; init; } + /// + /// Enable virtual host style so that opendal will send API requests in virtual host style instead of path style. By default, opendal will send API to https://s3.us-east-1.amazonaws.com/bucket_name Enabled, opendal will send API to https://bucket_name.s3.us-east-1.amazonaws.com + /// + public bool? EnableVirtualHostStyle { get; init; } + /// + /// Enable write with append so that opendal will send write request with append headers. + /// + public bool? EnableWriteWithAppend { get; init; } + /// + /// endpoint of this backend. Endpoint must be full uri, e.g. AWS S3: https://s3.amazonaws.com or https://s3.{region}.amazonaws.com Cloudflare R2: https://<ACCOUNT_ID>.r2.cloudflarestorage.com Aliyun OSS: https://{region}.aliyuncs.com Tencent COS: https://cos.{region}.myqcloud.com Minio: http://127.0.0.1:9000 If user inputs endpoint without scheme like "s3.amazonaws.com", we will prepend "https://" before it. If endpoint is set, we will take user's input first. If not, we will try to load it from environment. If still not set, default to https://s3.amazonaws.com. + /// + public string? Endpoint { get; init; } + /// + /// external_id for this backend. + /// + public string? ExternalId { get; init; } + /// + /// Region represent the signing region of this endpoint. This is required if you are using the default AWS S3 endpoint. If using a custom endpoint, If region is set, we will take user's input first. If not, we will try to load it from environment. + /// + public string? Region { get; init; } + /// + /// role_arn for this backend. If role_arn is set, we will use already known config as source credential to assume role with role_arn. + /// + public string? RoleArn { get; init; } + /// + /// role_session_name for this backend. + /// + public string? RoleSessionName { get; init; } + /// + /// root of this backend. All operations will happen under this root. default to / if not set. + /// + public string? Root { get; init; } + /// + /// secret_access_key of this backend. If secret_access_key is set, we will take user's input first. If not, we will try to load it from environment. + /// + public string? SecretAccessKey { get; init; } + /// + /// server_side_encryption for this backend. Available values: AES256, aws:kms. + /// + public string? ServerSideEncryption { get; init; } + /// + /// server_side_encryption_aws_kms_key_id for this backend If server_side_encryption set to aws:kms, and server_side_encryption_aws_kms_key_id is not set, S3 will use aws managed kms key to encrypt data. If server_side_encryption set to aws:kms, and server_side_encryption_aws_kms_key_id is a valid kms key id, S3 will use the provided kms key to encrypt data. If the server_side_encryption_aws_kms_key_id is invalid or not found, an error will be returned. If server_side_encryption is not aws:kms, setting server_side_encryption_aws_kms_key_id is a noop. + /// + public string? ServerSideEncryptionAwsKmsKeyId { get; init; } + /// + /// server_side_encryption_customer_algorithm for this backend. Available values: AES256. + /// + public string? ServerSideEncryptionCustomerAlgorithm { get; init; } + /// + /// server_side_encryption_customer_key for this backend. Value: BASE64-encoded key that matches algorithm specified in server_side_encryption_customer_algorithm. + /// + public string? ServerSideEncryptionCustomerKey { get; init; } + /// + /// Set server_side_encryption_customer_key_md5 for this backend. Value: MD5 digest of key specified in server_side_encryption_customer_key. + /// + public string? ServerSideEncryptionCustomerKeyMd5 { get; init; } + /// + /// session_token (aka, security token) of this backend. This token will expire after sometime, it's recommended to set session_token by hand. + /// + public string? SessionToken { get; init; } + + public string Scheme => "s3"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccessKeyId is not null) + { + map["access_key_id"] = Utilities.ToOptionString(AccessKeyId); + } + if (AllowAnonymous is not null) + { + map["allow_anonymous"] = Utilities.ToOptionString(AllowAnonymous); + } + if (BatchMaxOperations is not null) + { + map["batch_max_operations"] = Utilities.ToOptionString(BatchMaxOperations); + } + if (Bucket is not null) + { + map["bucket"] = Utilities.ToOptionString(Bucket); + } + if (ChecksumAlgorithm is not null) + { + map["checksum_algorithm"] = Utilities.ToOptionString(ChecksumAlgorithm); + } + if (DefaultStorageClass is not null) + { + map["default_storage_class"] = Utilities.ToOptionString(DefaultStorageClass); + } + if (DeleteMaxSize is not null) + { + map["delete_max_size"] = Utilities.ToOptionString(DeleteMaxSize); + } + if (DisableConfigLoad is not null) + { + map["disable_config_load"] = Utilities.ToOptionString(DisableConfigLoad); + } + if (DisableEc2Metadata is not null) + { + map["disable_ec2_metadata"] = Utilities.ToOptionString(DisableEc2Metadata); + } + if (DisableListObjectsV2 is not null) + { + map["disable_list_objects_v2"] = Utilities.ToOptionString(DisableListObjectsV2); + } + if (DisableStatWithOverride is not null) + { + map["disable_stat_with_override"] = Utilities.ToOptionString(DisableStatWithOverride); + } + if (DisableWriteWithIfMatch is not null) + { + map["disable_write_with_if_match"] = Utilities.ToOptionString(DisableWriteWithIfMatch); + } + if (EnableRequestPayer is not null) + { + map["enable_request_payer"] = Utilities.ToOptionString(EnableRequestPayer); + } + if (EnableVersioning is not null) + { + map["enable_versioning"] = Utilities.ToOptionString(EnableVersioning); + } + if (EnableVirtualHostStyle is not null) + { + map["enable_virtual_host_style"] = Utilities.ToOptionString(EnableVirtualHostStyle); + } + if (EnableWriteWithAppend is not null) + { + map["enable_write_with_append"] = Utilities.ToOptionString(EnableWriteWithAppend); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (ExternalId is not null) + { + map["external_id"] = Utilities.ToOptionString(ExternalId); + } + if (Region is not null) + { + map["region"] = Utilities.ToOptionString(Region); + } + if (RoleArn is not null) + { + map["role_arn"] = Utilities.ToOptionString(RoleArn); + } + if (RoleSessionName is not null) + { + map["role_session_name"] = Utilities.ToOptionString(RoleSessionName); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (SecretAccessKey is not null) + { + map["secret_access_key"] = Utilities.ToOptionString(SecretAccessKey); + } + if (ServerSideEncryption is not null) + { + map["server_side_encryption"] = Utilities.ToOptionString(ServerSideEncryption); + } + if (ServerSideEncryptionAwsKmsKeyId is not null) + { + map["server_side_encryption_aws_kms_key_id"] = Utilities.ToOptionString(ServerSideEncryptionAwsKmsKeyId); + } + if (ServerSideEncryptionCustomerAlgorithm is not null) + { + map["server_side_encryption_customer_algorithm"] = Utilities.ToOptionString(ServerSideEncryptionCustomerAlgorithm); + } + if (ServerSideEncryptionCustomerKey is not null) + { + map["server_side_encryption_customer_key"] = Utilities.ToOptionString(ServerSideEncryptionCustomerKey); + } + if (ServerSideEncryptionCustomerKeyMd5 is not null) + { + map["server_side_encryption_customer_key_md5"] = Utilities.ToOptionString(ServerSideEncryptionCustomerKeyMd5); + } + if (SessionToken is not null) + { + map["session_token"] = Utilities.ToOptionString(SessionToken); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/SeafileServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/SeafileServiceConfig.cs new file mode 100644 index 000000000000..c414b71534e4 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/SeafileServiceConfig.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service seafile. + /// + public sealed class SeafileServiceConfig : IServiceConfig + { + /// + /// endpoint address of this backend. + /// + public string? Endpoint { get; init; } + /// + /// password of this backend. + /// + public string? Password { get; init; } + /// + /// repo_name of this backend. required. + /// + public string? RepoName { get; init; } + /// + /// root of this backend. All operations will happen under this root. + /// + public string? Root { get; init; } + /// + /// username of this backend. + /// + public string? Username { get; init; } + + public string Scheme => "seafile"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Password is not null) + { + map["password"] = Utilities.ToOptionString(Password); + } + if (RepoName is not null) + { + map["repo_name"] = Utilities.ToOptionString(RepoName); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Username is not null) + { + map["username"] = Utilities.ToOptionString(Username); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/SftpServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/SftpServiceConfig.cs new file mode 100644 index 000000000000..3b1c3ae3d2e4 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/SftpServiceConfig.cs @@ -0,0 +1,89 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service sftp. + /// + public sealed class SftpServiceConfig : IServiceConfig + { + /// + /// enable_copy of this backend + /// + public bool? EnableCopy { get; init; } + /// + /// endpoint of this backend + /// + public string? Endpoint { get; init; } + /// + /// key of this backend + /// + public string? Key { get; init; } + /// + /// known_hosts_strategy of this backend + /// + public string? KnownHostsStrategy { get; init; } + /// + /// root of this backend + /// + public string? Root { get; init; } + /// + /// user of this backend + /// + public string? User { get; init; } + + public string Scheme => "sftp"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (EnableCopy is not null) + { + map["enable_copy"] = Utilities.ToOptionString(EnableCopy); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Key is not null) + { + map["key"] = Utilities.ToOptionString(Key); + } + if (KnownHostsStrategy is not null) + { + map["known_hosts_strategy"] = Utilities.ToOptionString(KnownHostsStrategy); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (User is not null) + { + map["user"] = Utilities.ToOptionString(User); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/SledServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/SledServiceConfig.cs new file mode 100644 index 000000000000..58596703c9d6 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/SledServiceConfig.cs @@ -0,0 +1,65 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service sled. + /// + public sealed class SledServiceConfig : IServiceConfig + { + /// + /// That path to the sled data directory. + /// + public string? Datadir { get; init; } + /// + /// The root for sled. + /// + public string? Root { get; init; } + /// + /// The tree for sled. + /// + public string? Tree { get; init; } + + public string Scheme => "sled"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Datadir is not null) + { + map["datadir"] = Utilities.ToOptionString(Datadir); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Tree is not null) + { + map["tree"] = Utilities.ToOptionString(Tree); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/SqliteServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/SqliteServiceConfig.cs new file mode 100644 index 000000000000..4b70641310f8 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/SqliteServiceConfig.cs @@ -0,0 +1,81 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service sqlite. + /// + public sealed class SqliteServiceConfig : IServiceConfig + { + /// + /// Set the connection_string of the sqlite service. This connection string is used to connect to the sqlite service. The format of connect string resembles the url format of the sqlite client: sqlite::memory: sqlite:data.db sqlite://data.db For more information, please visit https://docs.rs/sqlx/latest/sqlx/sqlite/struct.SqliteConnectOptions.html . + /// + public string? ConnectionString { get; init; } + /// + /// Set the key field name of the sqlite service to read/write. Default to key if not specified. + /// + public string? KeyField { get; init; } + /// + /// set the working directory, all operations will be performed under it. default: "/" + /// + public string? Root { get; init; } + /// + /// Set the table name of the sqlite service to read/write. + /// + public string? Table { get; init; } + /// + /// Set the value field name of the sqlite service to read/write. Default to value if not specified. + /// + public string? ValueField { get; init; } + + public string Scheme => "sqlite"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (ConnectionString is not null) + { + map["connection_string"] = Utilities.ToOptionString(ConnectionString); + } + if (KeyField is not null) + { + map["key_field"] = Utilities.ToOptionString(KeyField); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Table is not null) + { + map["table"] = Utilities.ToOptionString(Table); + } + if (ValueField is not null) + { + map["value_field"] = Utilities.ToOptionString(ValueField); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/SurrealdbServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/SurrealdbServiceConfig.cs new file mode 100644 index 000000000000..8f0823a9ba5a --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/SurrealdbServiceConfig.cs @@ -0,0 +1,113 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service surrealdb. + /// + public sealed class SurrealdbServiceConfig : IServiceConfig + { + /// + /// The connection string for surrealdb. + /// + public string? ConnectionString { get; init; } + /// + /// The database for surrealdb. + /// + public string? Database { get; init; } + /// + /// The key field for surrealdb. + /// + public string? KeyField { get; init; } + /// + /// The namespace for surrealdb. + /// + public string? Namespace { get; init; } + /// + /// The password for surrealdb. + /// + public string? Password { get; init; } + /// + /// The root for surrealdb. + /// + public string? Root { get; init; } + /// + /// The table for surrealdb. + /// + public string? Table { get; init; } + /// + /// The username for surrealdb. + /// + public string? Username { get; init; } + /// + /// The value field for surrealdb. + /// + public string? ValueField { get; init; } + + public string Scheme => "surrealdb"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (ConnectionString is not null) + { + map["connection_string"] = Utilities.ToOptionString(ConnectionString); + } + if (Database is not null) + { + map["database"] = Utilities.ToOptionString(Database); + } + if (KeyField is not null) + { + map["key_field"] = Utilities.ToOptionString(KeyField); + } + if (Namespace is not null) + { + map["namespace"] = Utilities.ToOptionString(Namespace); + } + if (Password is not null) + { + map["password"] = Utilities.ToOptionString(Password); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Table is not null) + { + map["table"] = Utilities.ToOptionString(Table); + } + if (Username is not null) + { + map["username"] = Utilities.ToOptionString(Username); + } + if (ValueField is not null) + { + map["value_field"] = Utilities.ToOptionString(ValueField); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/SwiftServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/SwiftServiceConfig.cs new file mode 100644 index 000000000000..ca35dcd7172f --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/SwiftServiceConfig.cs @@ -0,0 +1,73 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service swift. + /// + public sealed class SwiftServiceConfig : IServiceConfig + { + /// + /// The container for Swift. + /// + public string? Container { get; init; } + /// + /// The endpoint for Swift. + /// + public string? Endpoint { get; init; } + /// + /// The root for Swift. + /// + public string? Root { get; init; } + /// + /// The token for Swift. + /// + public string? Token { get; init; } + + public string Scheme => "swift"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Container is not null) + { + map["container"] = Utilities.ToOptionString(Container); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Token is not null) + { + map["token"] = Utilities.ToOptionString(Token); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/UpyunServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/UpyunServiceConfig.cs new file mode 100644 index 000000000000..87a81968f18c --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/UpyunServiceConfig.cs @@ -0,0 +1,73 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service upyun. + /// + public sealed class UpyunServiceConfig : IServiceConfig + { + /// + /// bucket address of this backend. + /// + public string? Bucket { get; init; } + /// + /// username of this backend. + /// + public string? Operator { get; init; } + /// + /// password of this backend. + /// + public string? Password { get; init; } + /// + /// root of this backend. All operations will happen under this root. + /// + public string? Root { get; init; } + + public string Scheme => "upyun"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Bucket is not null) + { + map["bucket"] = Utilities.ToOptionString(Bucket); + } + if (Operator is not null) + { + map["operator"] = Utilities.ToOptionString(Operator); + } + if (Password is not null) + { + map["password"] = Utilities.ToOptionString(Password); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/VercelArtifactsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/VercelArtifactsServiceConfig.cs new file mode 100644 index 000000000000..279347a9adda --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/VercelArtifactsServiceConfig.cs @@ -0,0 +1,49 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service vercel_artifacts. + /// + public sealed class VercelArtifactsServiceConfig : IServiceConfig + { + /// + /// The access token for Vercel. + /// + public string? AccessToken { get; init; } + + public string Scheme => "vercel_artifacts"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccessToken is not null) + { + map["access_token"] = Utilities.ToOptionString(AccessToken); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/VercelBlobServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/VercelBlobServiceConfig.cs new file mode 100644 index 000000000000..888bcd7b3c43 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/VercelBlobServiceConfig.cs @@ -0,0 +1,57 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service vercel_blob. + /// + public sealed class VercelBlobServiceConfig : IServiceConfig + { + /// + /// root of this backend. All operations will happen under this root. + /// + public string? Root { get; init; } + /// + /// vercel blob token. + /// + public string? Token { get; init; } + + public string Scheme => "vercel_blob"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Token is not null) + { + map["token"] = Utilities.ToOptionString(Token); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/WebdavServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/WebdavServiceConfig.cs new file mode 100644 index 000000000000..6712ae0b0c6e --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/WebdavServiceConfig.cs @@ -0,0 +1,89 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service webdav. + /// + public sealed class WebdavServiceConfig : IServiceConfig + { + /// + /// WebDAV Service doesn't support copy. + /// + public bool? DisableCopy { get; init; } + /// + /// endpoint of this backend + /// + public string? Endpoint { get; init; } + /// + /// password of this backend + /// + public string? Password { get; init; } + /// + /// root of this backend + /// + public string? Root { get; init; } + /// + /// token of this backend + /// + public string? Token { get; init; } + /// + /// username of this backend + /// + public string? Username { get; init; } + + public string Scheme => "webdav"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (DisableCopy is not null) + { + map["disable_copy"] = Utilities.ToOptionString(DisableCopy); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Password is not null) + { + map["password"] = Utilities.ToOptionString(Password); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (Token is not null) + { + map["token"] = Utilities.ToOptionString(Token); + } + if (Username is not null) + { + map["username"] = Utilities.ToOptionString(Username); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/WebhdfsServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/WebhdfsServiceConfig.cs new file mode 100644 index 000000000000..4b63ca3a3c94 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/WebhdfsServiceConfig.cs @@ -0,0 +1,89 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service webhdfs. + /// + public sealed class WebhdfsServiceConfig : IServiceConfig + { + /// + /// atomic_write_dir of this backend + /// + public string? AtomicWriteDir { get; init; } + /// + /// Delegation token for webhdfs. + /// + public string? Delegation { get; init; } + /// + /// Disable batch listing + /// + public bool? DisableListBatch { get; init; } + /// + /// Endpoint for webhdfs. + /// + public string? Endpoint { get; init; } + /// + /// Root for webhdfs. + /// + public string? Root { get; init; } + /// + /// Name of the user for webhdfs. + /// + public string? UserName { get; init; } + + public string Scheme => "webhdfs"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AtomicWriteDir is not null) + { + map["atomic_write_dir"] = Utilities.ToOptionString(AtomicWriteDir); + } + if (Delegation is not null) + { + map["delegation"] = Utilities.ToOptionString(Delegation); + } + if (DisableListBatch is not null) + { + map["disable_list_batch"] = Utilities.ToOptionString(DisableListBatch); + } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + if (UserName is not null) + { + map["user_name"] = Utilities.ToOptionString(UserName); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/ServiceConfig/YandexDiskServiceConfig.cs b/bindings/dotnet/DotOpenDAL/ServiceConfig/YandexDiskServiceConfig.cs new file mode 100644 index 000000000000..5a5c6a7d8d7d --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/ServiceConfig/YandexDiskServiceConfig.cs @@ -0,0 +1,57 @@ +/* + * 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. + */ + +// Generated from bindings/java ServiceConfig definitions. + +using DotOpenDAL.ServiceConfig.Abstractions; + +namespace DotOpenDAL.ServiceConfig +{ + /// + /// Configuration for service yandex_disk. + /// + public sealed class YandexDiskServiceConfig : IServiceConfig + { + /// + /// yandex disk oauth access_token. + /// + public string? AccessToken { get; init; } + /// + /// root of this backend. All operations will happen under this root. + /// + public string? Root { get; init; } + + public string Scheme => "yandex_disk"; + + public IReadOnlyDictionary ToOptions() + { + var map = new Dictionary(); + if (AccessToken is not null) + { + map["access_token"] = Utilities.ToOptionString(AccessToken); + } + if (Root is not null) + { + map["root"] = Utilities.ToOptionString(Root); + } + return map; + } + } + +} diff --git a/bindings/dotnet/DotOpenDAL/Utilities.cs b/bindings/dotnet/DotOpenDAL/Utilities.cs new file mode 100644 index 000000000000..c878e37a8186 --- /dev/null +++ b/bindings/dotnet/DotOpenDAL/Utilities.cs @@ -0,0 +1,92 @@ +/* + * 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. + */ + +using System.Runtime.InteropServices; +using System.Globalization; + +namespace DotOpenDAL; + +/// +/// Shared utility helpers for UTF-8 marshalling and option value formatting. +/// +public static class Utilities +{ + private const long NanosecondsPerTick = 100; + + /// + /// Decodes an unmanaged UTF-8 message pointer and returns null for null pointers. + /// + /// Pointer to an unmanaged UTF-8 message buffer. + /// The decoded message, or when the pointer is null. + public static string? ReadNullableUtf8(IntPtr message) + { + return message == IntPtr.Zero ? null : ReadUtf8(message); + } + + /// + /// Decodes an unmanaged UTF-8 message pointer into managed text. + /// + /// Pointer to an unmanaged UTF-8 message buffer. + /// The decoded message, or an empty string when the pointer is null or decoding returns null. + public static string ReadUtf8(IntPtr message) + { + if (message == IntPtr.Zero) + { + return string.Empty; + } + + return Marshal.PtrToStringUTF8(message) ?? string.Empty; + } + + /// + /// Formats a managed value into the option string expected by OpenDAL service configs. + /// + /// Managed value to format. + /// The formatted option string. + public static string ToOptionString(object value) + { + return value switch + { + bool b => b ? "true" : "false", + IFormattable f => f.ToString(null, CultureInfo.InvariantCulture) ?? string.Empty, + _ => value.ToString() ?? string.Empty, + }; + } + + /// + /// Converts a positive into nanoseconds. + /// + /// Duration to convert. + /// Parameter name used in thrown exceptions. + /// Duration in nanoseconds. + /// Value is zero or negative. + /// Value exceeds the range of nanoseconds. + public static ulong ToNanoseconds(TimeSpan value, string paramName) + { + if (value <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(paramName, "Duration must be greater than zero."); + } + + checked + { + return (ulong)(value.Ticks * NanosecondsPerTick); + } + } +} \ No newline at end of file diff --git a/bindings/dotnet/README.md b/bindings/dotnet/README.md index 1968686e87e1..1752364e6f11 100644 --- a/bindings/dotnet/README.md +++ b/bindings/dotnet/README.md @@ -1,16 +1,15 @@ -# Apache OpenDALâ„¢ .Net Binding (WIP) +# Apache OpenDALâ„¢ .NET Binding [![](https://img.shields.io/badge/status-unreleased-red)](https://opendal.apache.org/bindings/dotnet) > **Note**: This binding has its own independent version number, which may differ from the Rust core version. When checking for updates or compatibility, always refer to this binding's version rather than the core version. -This binding is currently under development. Please check back later. - ## Build To compile OpenDAL .NET binding from source code, you need: -- [.NET](https://dotnet.microsoft.com/en-us/download/dotnet) version 10.0 +- [.NET SDK](https://dotnet.microsoft.com/en-us/download/dotnet) for `net8.0` or `net10.0`. +- Rust toolchain for building the native library. ```bash cargo build @@ -18,6 +17,238 @@ dotnet build dotnet test ``` +## Quickstart + +```csharp +using DotOpenDAL; +using System.Text; + +using var executor = new Executor(2); +using var op = new Operator("memory"); + +await op.WriteAsync("demo.txt", Encoding.UTF8.GetBytes("hello"), executor); + +var bytes = await op.ReadAsync("demo.txt", executor); +var text = Encoding.UTF8.GetString(bytes); + +var meta = await op.StatAsync("demo.txt", executor: executor); +var entries = await op.ListAsync("", executor: executor); + +await op.DeleteAsync("demo.txt", executor); +``` + +If you don't pass an `Executor`, OpenDAL uses the default executor. + +## Creating an Operator + +### With scheme + dictionary options + +```csharp +using DotOpenDAL; + +using var fs = new Operator("fs", new Dictionary +{ + ["root"] = "/tmp/opendal", +}); +``` + +### With typed service config + +```csharp +using DotOpenDAL; +using DotOpenDAL.ServiceConfig; + +using var fs = new Operator(new FsServiceConfig +{ + Root = "/tmp/opendal", +}); +``` + +## Executor and Lifetime + +- Prefer `using` for `Operator`, `Executor`, and stream instances. +- Keep an `Executor` alive for the full lifetime of operations using it. +- Disposing an `Executor` or `Operator` too early causes `ObjectDisposedException`. +- For async calls, ensure disposal happens after awaited operations complete. + +## Core Operations + +`Operator` provides sync and async APIs for common object operations: + +- Read / Write +- Stat / List +- Delete / CreateDir +- Copy / Rename / RemoveAll +- PresignRead / PresignWrite / PresignStat / PresignDelete (async) +- `Info` for scheme/root/name/capabilities +- `Duplicate()` to create a new handle to the same backend configuration + +```csharp +using DotOpenDAL; +using DotOpenDAL.Options; +using System.Text; + +using var op = new Operator("memory"); + +op.CreateDir("logs/"); +op.Write("logs/a.txt", Encoding.UTF8.GetBytes("v1")); +op.Copy("logs/a.txt", "logs/b.txt"); +op.Rename("logs/b.txt", "logs/c.txt"); + +var read = op.Read("logs/c.txt", new ReadOptions { Offset = 0, Length = 2 }); +var list = op.List("logs/", new ListOptions { Recursive = true }); + +op.RemoveAll("logs/"); +``` + +## Options + +Use options types in `DotOpenDAL.Options` to pass operation-specific parameters. + +- `ReadOptions`: range, conditions, concurrency, response-header overrides. +- `WriteOptions`: append, content headers, conditions, concurrency/chunk, user metadata. +- `StatOptions`: conditional headers and response-header overrides. +- `ListOptions`: recursive, limit, start-after, versions, deleted. + +```csharp +using DotOpenDAL.Options; + +var writeOptions = new WriteOptions +{ + ContentType = "text/plain", + CacheControl = "no-cache", + IfNotExists = true, +}; + +await op.WriteAsync("docs/readme.txt", content, writeOptions); + +var listOptions = new ListOptions +{ + Recursive = true, + Limit = 100, +}; + +var entries = await op.ListAsync("docs/", listOptions); +``` + +## Streams + +OpenDAL exposes .NET `Stream` wrappers: + +- `OpenReadStream(path, readOptions, executor)` returns `OperatorInputStream`. +- `OpenWriteStream(path, writeOptions, bufferSize, executor)` returns `OperatorOutputStream`. + +```csharp +using DotOpenDAL; +using System.Text; + +using var op = new Operator("memory"); + +await using (var writer = op.OpenWriteStream("stream.txt")) +{ + var payload = Encoding.UTF8.GetBytes("stream-data"); + await writer.WriteAsync(payload, 0, payload.Length); + await writer.FlushAsync(); +} + +using var reader = op.OpenReadStream("stream.txt"); +using var ms = new MemoryStream(); +reader.CopyTo(ms); +``` + +Notes: + +- Dispose write streams to flush/close native resources deterministically. +- `ReadAsync` / `WriteAsync` on these streams are synchronous wrappers that still honor cancellation checks before execution. + +## Presign + +Generate presigned HTTP requests with expiration: + +```csharp +using DotOpenDAL; + +using var op = new Operator("s3", new Dictionary +{ + ["bucket"] = "my-bucket", + ["region"] = "us-east-1", + ["access_key_id"] = "", + ["secret_access_key"] = "", +}); + +var request = await op.PresignReadAsync("data/file.txt", TimeSpan.FromMinutes(10)); + +Console.WriteLine(request.Method); +Console.WriteLine(request.Uri); +foreach (var header in request.Headers) +{ + Console.WriteLine($"{header.Key}: {header.Value}"); +} +``` + +Available APIs: + +- `PresignReadAsync` +- `PresignWriteAsync` +- `PresignStatAsync` +- `PresignDeleteAsync` + +## Layers + +Apply middleware-like behavior with `WithLayer`: + +```csharp +using DotOpenDAL; +using DotOpenDAL.Layer; + +using var baseOp = new Operator("memory"); + +using var op = baseOp + .WithLayer(new ConcurrentLimitLayer(64)) + .WithLayer(new RetryLayer + { + MaxTimes = 5, + MinDelay = TimeSpan.FromMilliseconds(100), + MaxDelay = TimeSpan.FromSeconds(5), + Factor = 2f, + Jitter = true, + }) + .WithLayer(new TimeoutLayer + { + Timeout = TimeSpan.FromSeconds(30), + IoTimeout = TimeSpan.FromSeconds(5), + }); +``` + +## Error Handling and Capability Checks + +Most failures are surfaced as `OpenDALException` with a typed `Code` (`ErrorCode`). + +```csharp +try +{ + await op.ReadAsync("missing.txt"); +} +catch (OpenDALException ex) when (ex.Code == ErrorCode.NotFound) +{ + // Handle missing object +} +``` + +Some features depend on backend capability. Check `op.Info.FullCapability` before using optional options/features: + +```csharp +var cap = op.Info.FullCapability; +if (cap.PresignRead) +{ + var req = await op.PresignReadAsync("a.txt", TimeSpan.FromMinutes(5)); +} +``` + +## Path Conventions + +- Use backend-native object keys (for example, `a/b/c.txt`). +- For directory-like operations (`CreateDir`, directory `Stat`, `List` roots), prefer trailing slash paths such as `logs/`. ## License and Trademarks diff --git a/bindings/dotnet/src/byte_buffer.rs b/bindings/dotnet/src/byte_buffer.rs new file mode 100644 index 000000000000..d3e8c2678d88 --- /dev/null +++ b/bindings/dotnet/src/byte_buffer.rs @@ -0,0 +1,95 @@ +// 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. + +#[repr(C)] +/// FFI-safe representation of a Rust `Vec` buffer. +/// +/// The buffer ownership is transferred to the caller and must be released by +/// calling `opendal_read_result_release` exactly once. +pub struct ByteBuffer { + /// Pointer to the start of the allocated bytes. + pub data: *mut u8, + /// Number of initialized bytes. + pub len: usize, + /// Total allocated capacity in bytes. + pub capacity: usize, +} + +impl ByteBuffer { + /// Create an empty buffer that does not own any allocation. + pub fn empty() -> Self { + Self { + data: std::ptr::null_mut(), + len: 0, + capacity: 0, + } + } + + /// Convert a vector into a raw FFI buffer without copying. + /// + /// The returned memory must be released by the C# side via + /// `opendal_read_result_release`. + pub fn from_vec(mut value: Vec) -> Self { + if value.is_empty() { + return Self::empty(); + } + + let data = value.as_mut_ptr(); + let len = value.len(); + let capacity = value.capacity(); + std::mem::forget(value); + + Self { + data, + len, + capacity, + } + } +} + +/// # Safety +/// +/// - `data`, `len`, and `capacity` must come from `ByteBuffer::from_vec`. +/// - This function must be called at most once for the same allocation. +/// - Callers must not access `data` after this function returns. +pub unsafe fn buffer_free(data: *mut u8, len: usize, capacity: usize) { + if data.is_null() { + debug_assert_eq!(len, 0, "len must be zero when data is null"); + debug_assert_eq!(capacity, 0, "capacity must be zero when data is null"); + return; + } + + if capacity == 0 { + debug_assert!( + capacity > 0, + "capacity must be greater than zero when data is not null" + ); + return; + } + + if capacity < len { + debug_assert!( + capacity >= len, + "capacity must be greater than or equal to len" + ); + return; + } + + unsafe { + drop(Vec::from_raw_parts(data, len, capacity)); + } +} diff --git a/bindings/dotnet/src/capability.rs b/bindings/dotnet/src/capability.rs new file mode 100644 index 000000000000..1f0625bfb21c --- /dev/null +++ b/bindings/dotnet/src/capability.rs @@ -0,0 +1,150 @@ +// 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. + +#[repr(C)] +#[derive(Default, Clone, Copy)] +/// FFI-safe mirror of `opendal::Capability` used by the .NET binding. +/// +/// This struct intentionally mirrors fields from OpenDAL core capability, +/// but uses a stable C-compatible layout for cross-language interop. +/// +/// We keep this dedicated mirror because Rust internal type layout is not an +/// FFI contract and must not be marshaled directly across the ABI boundary. +pub struct Capability { + pub stat: bool, + pub stat_with_if_match: bool, + pub stat_with_if_none_match: bool, + pub stat_with_if_modified_since: bool, + pub stat_with_if_unmodified_since: bool, + pub stat_with_override_cache_control: bool, + pub stat_with_override_content_disposition: bool, + pub stat_with_override_content_type: bool, + pub stat_with_version: bool, + + pub read: bool, + pub read_with_if_match: bool, + pub read_with_if_none_match: bool, + pub read_with_if_modified_since: bool, + pub read_with_if_unmodified_since: bool, + pub read_with_override_cache_control: bool, + pub read_with_override_content_disposition: bool, + pub read_with_override_content_type: bool, + pub read_with_version: bool, + + pub write: bool, + pub write_can_multi: bool, + pub write_can_empty: bool, + pub write_can_append: bool, + pub write_with_content_type: bool, + pub write_with_content_disposition: bool, + pub write_with_content_encoding: bool, + pub write_with_cache_control: bool, + pub write_with_if_match: bool, + pub write_with_if_none_match: bool, + pub write_with_if_not_exists: bool, + pub write_with_user_metadata: bool, + + pub write_multi_max_size: usize, + pub write_multi_min_size: usize, + pub write_total_max_size: usize, + + pub create_dir: bool, + pub delete: bool, + pub delete_with_version: bool, + pub delete_with_recursive: bool, + pub delete_max_size: usize, + + pub copy: bool, + pub copy_with_if_not_exists: bool, + pub rename: bool, + + pub list: bool, + pub list_with_limit: bool, + pub list_with_start_after: bool, + pub list_with_recursive: bool, + pub list_with_versions: bool, + pub list_with_deleted: bool, + + pub presign: bool, + pub presign_read: bool, + pub presign_stat: bool, + pub presign_write: bool, + pub presign_delete: bool, + + pub shared: bool, +} + +impl Capability { + /// Convert OpenDAL core capability into the FFI mirror payload. + pub fn new(cap: opendal::Capability) -> Self { + Self { + stat: cap.stat, + stat_with_if_match: cap.stat_with_if_match, + stat_with_if_none_match: cap.stat_with_if_none_match, + stat_with_if_modified_since: cap.stat_with_if_modified_since, + stat_with_if_unmodified_since: cap.stat_with_if_unmodified_since, + stat_with_override_cache_control: cap.stat_with_override_cache_control, + stat_with_override_content_disposition: cap.stat_with_override_content_disposition, + stat_with_override_content_type: cap.stat_with_override_content_type, + stat_with_version: cap.stat_with_version, + read: cap.read, + read_with_if_match: cap.read_with_if_match, + read_with_if_none_match: cap.read_with_if_none_match, + read_with_if_modified_since: cap.read_with_if_modified_since, + read_with_if_unmodified_since: cap.read_with_if_unmodified_since, + read_with_override_cache_control: cap.read_with_override_cache_control, + read_with_override_content_disposition: cap.read_with_override_content_disposition, + read_with_override_content_type: cap.read_with_override_content_type, + read_with_version: cap.read_with_version, + write: cap.write, + write_can_multi: cap.write_can_multi, + write_can_empty: cap.write_can_empty, + write_can_append: cap.write_can_append, + write_with_content_type: cap.write_with_content_type, + write_with_content_disposition: cap.write_with_content_disposition, + write_with_content_encoding: cap.write_with_content_encoding, + write_with_cache_control: cap.write_with_cache_control, + write_with_if_match: cap.write_with_if_match, + write_with_if_none_match: cap.write_with_if_none_match, + write_with_if_not_exists: cap.write_with_if_not_exists, + write_with_user_metadata: cap.write_with_user_metadata, + write_multi_max_size: cap.write_multi_max_size.unwrap_or(usize::MIN), + write_multi_min_size: cap.write_multi_min_size.unwrap_or(usize::MIN), + write_total_max_size: cap.write_total_max_size.unwrap_or(usize::MIN), + create_dir: cap.create_dir, + delete: cap.delete, + delete_with_version: cap.delete_with_version, + delete_with_recursive: cap.delete_with_recursive, + delete_max_size: cap.delete_max_size.unwrap_or(usize::MIN), + copy: cap.copy, + copy_with_if_not_exists: cap.copy_with_if_not_exists, + rename: cap.rename, + list: cap.list, + list_with_limit: cap.list_with_limit, + list_with_start_after: cap.list_with_start_after, + list_with_recursive: cap.list_with_recursive, + list_with_versions: cap.list_with_versions, + list_with_deleted: cap.list_with_deleted, + presign: cap.presign, + presign_read: cap.presign_read, + presign_stat: cap.presign_stat, + presign_write: cap.presign_write, + presign_delete: cap.presign_delete, + shared: cap.shared, + } + } +} diff --git a/bindings/dotnet/src/entry.rs b/bindings/dotnet/src/entry.rs new file mode 100644 index 000000000000..55d543e68c91 --- /dev/null +++ b/bindings/dotnet/src/entry.rs @@ -0,0 +1,111 @@ +// 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. + +use std::ffi::{c_char, c_void}; + +use crate::metadata::{OpendalMetadata, metadata_free}; +use crate::utils::into_string_ptr; + +#[repr(C)] +/// FFI representation of an OpenDAL entry. +/// +/// String and metadata fields are heap-allocated and owned by Rust until +/// released via `entry_list_free`. +pub struct OpendalEntry { + pub path: *mut c_char, + pub metadata: *mut OpendalMetadata, +} + +#[repr(C)] +/// FFI representation of a list of entries. +pub struct OpendalEntryList { + pub entries: *mut *mut OpendalEntry, + pub len: usize, +} + +impl OpendalEntry { + pub fn from_entry(entry: opendal::Entry) -> Self { + let path = into_string_ptr(entry.path().to_string()); + let metadata = Box::into_raw(Box::new(OpendalMetadata::from_metadata( + entry.metadata().clone(), + ))); + + Self { path, metadata } + } +} + +/// Convert OpenDAL entries into an owned FFI list pointer. +/// +/// The returned pointer must be released by `entry_list_free`. +pub fn into_entry_list_ptr(entries: Vec) -> *mut c_void { + let mut entry_ptrs: Vec<*mut OpendalEntry> = entries + .into_iter() + .map(|entry| Box::into_raw(Box::new(OpendalEntry::from_entry(entry)))) + .collect(); + + let len = entry_ptrs.len(); + let entries_ptr = entry_ptrs.as_mut_ptr(); + std::mem::forget(entry_ptrs); + + Box::into_raw(Box::new(OpendalEntryList { + entries: entries_ptr, + len, + })) as *mut c_void +} + +/// Release one FFI entry payload. +/// +/// # Safety +/// +/// - `entry` must be null or a pointer previously produced by this crate. +/// - This function must be called at most once per non-null pointer. +unsafe fn free_entry(entry: *mut OpendalEntry) { + if entry.is_null() { + return; + } + + unsafe { + let entry = Box::from_raw(entry); + if !entry.path.is_null() { + drop(std::ffi::CString::from_raw(entry.path)); + } + if !entry.metadata.is_null() { + metadata_free(entry.metadata); + } + } +} + +/// # Safety +/// +/// - `list` must be null or a pointer returned by Rust as `OpendalEntryList`. +/// - Must be called at most once for the same pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entry_list_free(list: *mut OpendalEntryList) { + if list.is_null() { + return; + } + + unsafe { + let list = Box::from_raw(list); + if !list.entries.is_null() { + let entries = Vec::from_raw_parts(list.entries, list.len, list.len); + for entry in entries { + free_entry(entry); + } + } + } +} diff --git a/bindings/dotnet/src/error.rs b/bindings/dotnet/src/error.rs new file mode 100644 index 000000000000..e87830241c6b --- /dev/null +++ b/bindings/dotnet/src/error.rs @@ -0,0 +1,97 @@ +// 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. + +use opendal::ErrorKind; +use std::os::raw::c_char; + +use crate::utils::into_string_ptr; + +#[repr(C)] +/// Error payload returned by exported FFI functions. +pub struct OpenDALError { + /// `1` when an error is present, otherwise `0`. + pub has_error: u8, + /// Numeric error code mapped from `ErrorCode`. + pub code: i32, + /// Heap-allocated UTF-8 message created by Rust. + /// + /// The message is released by the corresponding FFI result release API. + pub message: *mut c_char, +} + +impl OpenDALError { + pub fn ok() -> Self { + OpenDALError { + has_error: 0, + code: 0, + message: std::ptr::null_mut(), + } + } + + pub fn from_error(code: ErrorCode, message: impl Into) -> Self { + OpenDALError { + has_error: 1, + code: code as i32, + message: into_string_ptr(message), + } + } + + pub fn from_opendal_error(error: opendal::Error) -> OpenDALError { + OpenDALError::from_error(ErrorCode::from_error_kind(error.kind()), error.to_string()) + } +} + +#[repr(i32)] +#[derive(Clone, Copy)] +/// Error codes exposed to the .NET binding. +/// +/// The numeric values are part of the FFI contract and must stay stable. +pub enum ErrorCode { + Unexpected = 0, + Unsupported = 1, + ConfigInvalid = 2, + NotFound = 3, + PermissionDenied = 4, + IsADirectory = 5, + NotADirectory = 6, + AlreadyExists = 7, + RateLimited = 8, + IsSameFile = 9, + ConditionNotMatch = 10, + RangeNotSatisfied = 11, +} + +impl ErrorCode { + /// Convert OpenDAL's internal error kind to an FFI-stable error code. + pub fn from_error_kind(kind: ErrorKind) -> Self { + match kind { + ErrorKind::Unexpected => ErrorCode::Unexpected, + ErrorKind::Unsupported => ErrorCode::Unsupported, + ErrorKind::ConfigInvalid => ErrorCode::ConfigInvalid, + ErrorKind::NotFound => ErrorCode::NotFound, + ErrorKind::PermissionDenied => ErrorCode::PermissionDenied, + ErrorKind::IsADirectory => ErrorCode::IsADirectory, + ErrorKind::NotADirectory => ErrorCode::NotADirectory, + ErrorKind::AlreadyExists => ErrorCode::AlreadyExists, + ErrorKind::RateLimited => ErrorCode::RateLimited, + ErrorKind::IsSameFile => ErrorCode::IsSameFile, + ErrorKind::ConditionNotMatch => ErrorCode::ConditionNotMatch, + ErrorKind::RangeNotSatisfied => ErrorCode::RangeNotSatisfied, + _ => ErrorCode::Unexpected, + } + } +} diff --git a/bindings/dotnet/src/executor.rs b/bindings/dotnet/src/executor.rs new file mode 100644 index 000000000000..fd857b02c55d --- /dev/null +++ b/bindings/dotnet/src/executor.rs @@ -0,0 +1,154 @@ +// 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. + +use std::collections::HashMap; +use std::ffi::c_void; +use std::future::Future; +use std::num::NonZeroUsize; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, LazyLock, Mutex, OnceLock}; +use std::thread::available_parallelism; + +use crate::error::OpenDALError; +use crate::result::OpendalExecutorResult; +use crate::utils::config_invalid_error; + +static DEFAULT_EXECUTOR: OnceLock> = OnceLock::new(); + +static EXECUTOR_REGISTRY: LazyLock>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); +static NEXT_EXECUTOR_ID: AtomicUsize = AtomicUsize::new(1); + +pub struct Executor { + runtime: tokio::runtime::Runtime, +} + +impl Executor { + fn new(threads: usize) -> Result { + if threads == 0 { + return Err(config_invalid_error( + "executor threads must be greater than 0", + )); + } + + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(threads) + .enable_all() + .build() + .map_err(|e| { + OpenDALError::from_opendal_error( + opendal::Error::new( + opendal::ErrorKind::Unexpected, + "failed to create tokio runtime", + ) + .set_source(e), + ) + })?; + + Ok(Self { runtime }) + } + + pub fn block_on(&self, future: F) -> F::Output { + self.runtime.block_on(future) + } + + pub fn spawn(&self, future: F) -> tokio::task::JoinHandle + where + F: Future + Send + 'static, + F::Output: Send + 'static, + { + self.runtime.spawn(future) + } + + pub fn enter(&self) -> tokio::runtime::EnterGuard<'_> { + self.runtime.enter() + } +} + +fn default_executor() -> Result, OpenDALError> { + if let Some(executor) = DEFAULT_EXECUTOR.get() { + return Ok(executor.clone()); + } + + let threads = available_parallelism().map(NonZeroUsize::get).unwrap_or(1); + let executor = Arc::new(Executor::new(threads)?); + + if DEFAULT_EXECUTOR.set(executor.clone()).is_ok() { + return Ok(executor); + } + + if let Some(existing) = DEFAULT_EXECUTOR.get() { + return Ok(existing.clone()); + } + + Ok(executor) +} + +/// # Safety +/// +/// `executor` must be either null or a valid handle previously returned by +/// `executor_create`. +pub fn executor_or_default(executor: *const c_void) -> Result, OpenDALError> { + if executor.is_null() { + return default_executor(); + } + + let id = executor as usize; + let registry = EXECUTOR_REGISTRY + .lock() + .map_err(|_| config_invalid_error("executor registry is poisoned"))?; + + registry + .get(&id) + .cloned() + .ok_or_else(|| config_invalid_error("executor handle is invalid or disposed")) +} + +#[unsafe(no_mangle)] +pub extern "C" fn executor_create(threads: usize) -> OpendalExecutorResult { + match Executor::new(threads) { + Ok(executor) => { + let id = NEXT_EXECUTOR_ID.fetch_add(1, Ordering::Relaxed); + match EXECUTOR_REGISTRY.lock() { + Ok(mut registry) => { + registry.insert(id, Arc::new(executor)); + OpendalExecutorResult::ok(id as *mut c_void) + } + Err(_) => OpendalExecutorResult::from_error(config_invalid_error( + "executor registry is poisoned", + )), + } + } + Err(error) => OpendalExecutorResult::from_error(error), + } +} + +/// # Safety +/// +/// `executor` must be either null or a pointer-like handle returned by +/// `executor_create`. +/// This function is idempotent for unknown handles and null pointers. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn executor_free(executor: *mut c_void) { + if executor.is_null() { + return; + } + + if let Ok(mut registry) = EXECUTOR_REGISTRY.lock() { + registry.remove(&(executor as usize)); + } +} diff --git a/bindings/dotnet/src/lib.rs b/bindings/dotnet/src/lib.rs index 2bce2a9c1ed3..42c80c68056e 100644 --- a/bindings/dotnet/src/lib.rs +++ b/bindings/dotnet/src/lib.rs @@ -15,85 +15,21 @@ // specific language governing permissions and limitations // under the License. -use std::collections::HashMap; -use std::os::raw::c_char; -use std::sync::LazyLock; - -static RUNTIME: LazyLock = LazyLock::new(|| { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() -}); - -/// # Safety -/// -/// Not yet. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn blocking_operator_construct( - scheme: *const c_char, -) -> *const opendal::blocking::Operator { - if scheme.is_null() { - return std::ptr::null(); - } - - let scheme = unsafe { std::ffi::CStr::from_ptr(scheme) } - .to_str() - .unwrap(); - - let mut map = HashMap::::default(); - map.insert("root".to_string(), "/tmp".to_string()); - let op = match opendal::Operator::via_iter(scheme, map) { - Ok(op) => op, - Err(err) => { - println!("err={err:?}"); - return std::ptr::null(); - } - }; - - let handle = RUNTIME.handle(); - let _enter = handle.enter(); - let blocking_op = match opendal::blocking::Operator::new(op) { - Ok(op) => op, - Err(err) => { - println!("err={err:?}"); - return std::ptr::null(); - } - }; - - Box::leak(Box::new(blocking_op)) -} - -/// # Safety -/// -/// Not yet. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn blocking_operator_write( - op: *const opendal::blocking::Operator, - path: *const c_char, - content: *const c_char, -) { - let op = unsafe { &*op }; - let path = unsafe { std::ffi::CStr::from_ptr(path) }.to_str().unwrap(); - let content = unsafe { std::ffi::CStr::from_ptr(content) } - .to_str() - .unwrap(); - op.write(path, content.to_owned()).map(|_| ()).unwrap() -} - -/// # Safety -/// -/// Not yet. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn blocking_operator_read( - op: *const opendal::blocking::Operator, - path: *const c_char, -) -> *const c_char { - let op = unsafe { &*op }; - let path = unsafe { std::ffi::CStr::from_ptr(path) }.to_str().unwrap(); - let mut res = op.read(path).unwrap().to_vec(); - res.push(0); - std::ffi::CString::from_vec_with_nul(res) - .unwrap() - .into_raw() -} +//! Rust FFI layer backing the OpenDAL .NET binding. +//! +//! This crate exposes `extern "C"` APIs consumed by C# via P/Invoke and keeps +//! interop memory ownership explicit through dedicated release functions. + +mod byte_buffer; +mod capability; +mod entry; +mod error; +mod executor; +mod metadata; +mod operator; +mod operator_info; +mod options; +mod presign; +mod result; +mod utils; +mod validators; diff --git a/bindings/dotnet/src/metadata.rs b/bindings/dotnet/src/metadata.rs new file mode 100644 index 000000000000..36dd7cf4ca91 --- /dev/null +++ b/bindings/dotnet/src/metadata.rs @@ -0,0 +1,118 @@ +// 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. + +use std::ffi::c_char; + +use crate::utils::into_string_ptr; + +#[repr(C)] +/// FFI representation of OpenDAL metadata. +pub struct OpendalMetadata { + pub mode: i32, + pub content_length: u64, + pub content_disposition: *mut c_char, + pub content_md5: *mut c_char, + pub content_type: *mut c_char, + pub content_encoding: *mut c_char, + pub cache_control: *mut c_char, + pub etag: *mut c_char, + pub last_modified_has_value: u8, + pub last_modified_second: i64, + pub last_modified_nanosecond: i32, + pub version: *mut c_char, +} + +impl OpendalMetadata { + pub fn from_metadata(metadata: opendal::Metadata) -> Self { + let mode = match metadata.mode() { + opendal::EntryMode::FILE => 0, + opendal::EntryMode::DIR => 1, + opendal::EntryMode::Unknown => 2, + }; + + let (last_modified_has_value, last_modified_second, last_modified_nanosecond) = + if let Some(last_modified) = metadata.last_modified() { + ( + 1, + last_modified.into_inner().as_second(), + last_modified.into_inner().subsec_nanosecond(), + ) + } else { + (0, 0, 0) + }; + + Self { + mode, + content_length: metadata.content_length(), + content_disposition: optional_string_to_ptr(metadata.content_disposition()), + content_md5: optional_string_to_ptr(metadata.content_md5()), + content_type: optional_string_to_ptr(metadata.content_type()), + content_encoding: optional_string_to_ptr(metadata.content_encoding()), + cache_control: optional_string_to_ptr(metadata.cache_control()), + etag: optional_string_to_ptr(metadata.etag()), + last_modified_has_value, + last_modified_second, + last_modified_nanosecond, + version: optional_string_to_ptr(metadata.version()), + } + } +} + +/// Convert an optional Rust string into an owned UTF-8 C string pointer. +/// +/// Returns null when the option is `None`. +fn optional_string_to_ptr(value: Option<&str>) -> *mut c_char { + value + .map(|v| into_string_ptr(v.to_string())) + .unwrap_or(std::ptr::null_mut()) +} + +/// # Safety +/// +/// - `metadata` must be null or a pointer returned by Rust for `OpendalMetadata`. +/// - Must be called at most once for the same pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn metadata_free(metadata: *mut OpendalMetadata) { + if metadata.is_null() { + return; + } + + unsafe { + let metadata = Box::from_raw(metadata); + if !metadata.content_disposition.is_null() { + drop(std::ffi::CString::from_raw(metadata.content_disposition)); + } + if !metadata.content_md5.is_null() { + drop(std::ffi::CString::from_raw(metadata.content_md5)); + } + if !metadata.content_type.is_null() { + drop(std::ffi::CString::from_raw(metadata.content_type)); + } + if !metadata.content_encoding.is_null() { + drop(std::ffi::CString::from_raw(metadata.content_encoding)); + } + if !metadata.cache_control.is_null() { + drop(std::ffi::CString::from_raw(metadata.cache_control)); + } + if !metadata.etag.is_null() { + drop(std::ffi::CString::from_raw(metadata.etag)); + } + if !metadata.version.is_null() { + drop(std::ffi::CString::from_raw(metadata.version)); + } + } +} diff --git a/bindings/dotnet/src/operator.rs b/bindings/dotnet/src/operator.rs new file mode 100644 index 000000000000..af725a3a23cc --- /dev/null +++ b/bindings/dotnet/src/operator.rs @@ -0,0 +1,1846 @@ +// 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. + +use crate::{ + byte_buffer::ByteBuffer, + entry::into_entry_list_ptr, + error::OpenDALError, + executor::executor_or_default, + metadata::OpendalMetadata, + operator_info::{OpendalOperatorInfo, into_operator_info}, + options::{parse_list_options, parse_read_options, parse_stat_options, parse_write_options}, + presign::into_presigned_request_ptr, + result::{ + OpendalEntryListResult, OpendalMetadataResult, OpendalOperatorInfoResult, + OpendalOperatorResult, OpendalOptionsResult, OpendalPresignedRequestResult, + OpendalReadResult, OpendalResult, + }, + utils::{collect_options, require_callback, require_cstr, require_data_ptr, require_operator}, + validators::prelude::{ + validate_concurrent_limit_options, validate_retry_options, validate_timeout_options, + }, +}; + +use std::collections::HashMap; +use std::ffi::c_void; +use std::os::raw::c_char; +use std::time::Duration; + +/// Callback signature for async write completion. +/// +/// The callback is provided by the .NET side and must remain valid until +/// invoked by Rust. +type WriteCallback = extern "C" fn(context: i64, result: OpendalResult); +type ReadCallback = extern "C" fn(context: i64, result: OpendalReadResult); +type StatCallback = extern "C" fn(context: i64, result: OpendalMetadataResult); +type ListCallback = extern "C" fn(context: i64, result: OpendalEntryListResult); +type PresignCallback = extern "C" fn(context: i64, result: OpendalPresignedRequestResult); + +/// Build constructor options from raw C string key/value arrays. +/// +/// On success, the returned pointer must be released by +/// `constructor_option_free`. +/// # Safety +/// +/// - When `len > 0`, `keys` and `values` must be non-null pointers to arrays +/// containing at least `len` C-string pointers. +/// - Each key/value entry must be a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn constructor_option_build( + keys: *const *const c_char, + values: *const *const c_char, + len: usize, +) -> OpendalOptionsResult { + match unsafe { collect_options(keys, values, len) } { + Ok(options) => OpendalOptionsResult::ok(Box::into_raw(Box::new(options)) as *mut c_void), + Err(error) => OpendalOptionsResult::from_error(error), + } +} + +/// # Safety +/// +/// - `options` must be null or a pointer returned by +/// `constructor_option_build`. +/// - This function must be called at most once for the same pointer. +#[unsafe(no_mangle)] +pub extern "C" fn constructor_option_free(options: *mut HashMap) { + if options.is_null() { + return; + } + unsafe { + drop(Box::from_raw(options)); + } +} + +/// Build read options from raw C string key/value arrays. +/// +/// On success, the returned pointer must be released by `read_option_free`. +/// # Safety +/// +/// - When `len > 0`, `keys` and `values` must be non-null pointers to arrays +/// containing at least `len` C-string pointers. +/// - Each key/value entry must be a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn read_option_build( + keys: *const *const c_char, + values: *const *const c_char, + len: usize, +) -> OpendalOptionsResult { + match unsafe { collect_options(keys, values, len) } + .and_then(|values| parse_read_options(&values)) + { + Ok(options) => OpendalOptionsResult::ok(Box::into_raw(Box::new(options)) as *mut c_void), + Err(error) => OpendalOptionsResult::from_error(error), + } +} + +/// # Safety +/// +/// - `options` must be null or a pointer returned by `read_option_build`. +/// - This function must be called at most once for the same pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn read_option_free(options: *mut opendal::options::ReadOptions) { + if options.is_null() { + return; + } + unsafe { + drop(Box::from_raw(options)); + } +} + +/// Build write options from raw C string key/value arrays. +/// +/// On success, the returned pointer must be released by `write_option_free`. +/// # Safety +/// +/// - When `len > 0`, `keys` and `values` must be non-null pointers to arrays +/// containing at least `len` C-string pointers. +/// - Each key/value entry must be a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn write_option_build( + keys: *const *const c_char, + values: *const *const c_char, + len: usize, +) -> OpendalOptionsResult { + match unsafe { collect_options(keys, values, len) } + .and_then(|values| parse_write_options(&values)) + { + Ok(options) => OpendalOptionsResult::ok(Box::into_raw(Box::new(options)) as *mut c_void), + Err(error) => OpendalOptionsResult::from_error(error), + } +} + +/// # Safety +/// +/// - `options` must be null or a pointer returned by `write_option_build`. +/// - This function must be called at most once for the same pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn write_option_free(options: *mut opendal::options::WriteOptions) { + if options.is_null() { + return; + } + unsafe { + drop(Box::from_raw(options)); + } +} + +/// Build stat options from raw C string key/value arrays. +/// +/// On success, the returned pointer must be released by `stat_option_free`. +/// # Safety +/// +/// - When `len > 0`, `keys` and `values` must be non-null pointers to arrays +/// containing at least `len` C-string pointers. +/// - Each key/value entry must be a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn stat_option_build( + keys: *const *const c_char, + values: *const *const c_char, + len: usize, +) -> OpendalOptionsResult { + match unsafe { collect_options(keys, values, len) } + .and_then(|values| parse_stat_options(&values)) + { + Ok(options) => OpendalOptionsResult::ok(Box::into_raw(Box::new(options)) as *mut c_void), + Err(error) => OpendalOptionsResult::from_error(error), + } +} + +/// # Safety +/// +/// - `options` must be null or a pointer returned by `stat_option_build`. +/// - This function must be called at most once for the same pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn stat_option_free(options: *mut opendal::options::StatOptions) { + if options.is_null() { + return; + } + unsafe { + drop(Box::from_raw(options)); + } +} + +/// Build list options from raw C string key/value arrays. +/// +/// On success, the returned pointer must be released by `list_option_free`. +/// # Safety +/// +/// - When `len > 0`, `keys` and `values` must be non-null pointers to arrays +/// containing at least `len` C-string pointers. +/// - Each key/value entry must be a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn list_option_build( + keys: *const *const c_char, + values: *const *const c_char, + len: usize, +) -> OpendalOptionsResult { + match unsafe { collect_options(keys, values, len) } + .and_then(|values| parse_list_options(&values)) + { + Ok(options) => OpendalOptionsResult::ok(Box::into_raw(Box::new(options)) as *mut c_void), + Err(error) => OpendalOptionsResult::from_error(error), + } +} + +/// # Safety +/// +/// - `options` must be null or a pointer returned by `list_option_build`. +/// - This function must be called at most once for the same pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn list_option_free(options: *mut opendal::options::ListOptions) { + if options.is_null() { + return; + } + unsafe { + drop(Box::from_raw(options)); + } +} + +/// Construct an OpenDAL operator instance from a scheme and key/value options. +/// +/// Returns a pointer that must be released with `operator_free`. +/// # Safety +/// +/// - `scheme` must be a valid null-terminated UTF-8 string. +/// - When `len > 0`, `keys` and `values` must be non-null and point to arrays +/// of at least `len` entries. +/// - Every key/value entry in those arrays must be a valid null-terminated +/// UTF-8 string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn operator_construct( + scheme: *const c_char, + options: *const HashMap, +) -> OpendalOperatorResult { + match operator_construct_inner(scheme, options) { + Ok(op) => OpendalOperatorResult::ok(op), + Err(error) => OpendalOperatorResult::from_error(error), + } +} + +fn operator_construct_inner( + scheme: *const c_char, + options: *const HashMap, +) -> Result<*mut c_void, OpenDALError> { + let scheme = require_cstr(scheme, "scheme")?; + let options = if options.is_null() { + HashMap::default() + } else { + unsafe { (&*options).clone() } + }; + let op = + opendal::Operator::via_iter(scheme, options).map_err(OpenDALError::from_opendal_error)?; + Ok(Box::into_raw(Box::new(op)) as *mut c_void) +} + +/// # Safety +/// +/// - `op` must be either null or a pointer returned by `operator_construct`. +/// - The pointer must not be used after this call. +/// - This function must be called at most once for the same pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn operator_free(op: *mut opendal::Operator) { + if op.is_null() { + return; + } + + unsafe { + drop(Box::from_raw(op)); + } +} + +/// Get operator info payload. +/// +/// On success, payload must be released by `opendal_operator_info_result_release`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn operator_info_get( + op: *const opendal::Operator, +) -> OpendalOperatorInfoResult { + match operator_info_get_inner(op) { + Ok(value) => OpendalOperatorInfoResult::ok(value), + Err(error) => OpendalOperatorInfoResult::from_error(error), + } +} + +fn operator_info_get_inner(op: *const opendal::Operator) -> Result<*mut c_void, OpenDALError> { + let op = require_operator(op)?; + let info = into_operator_info(op.info()); + Ok(Box::into_raw(Box::new(info)) as *mut c_void) +} + +/// # Safety +/// +/// - `info` must be either null or a pointer returned by `operator_info_get`. +/// - The pointer must not be used after this call. +/// - This function must be called at most once for the same pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn operator_info_free(info: *mut OpendalOperatorInfo) { + if info.is_null() { + return; + } + + unsafe { + let info = Box::from_raw(info); + if !info.scheme.is_null() { + drop(std::ffi::CString::from_raw(info.scheme)); + } + if !info.root.is_null() { + drop(std::ffi::CString::from_raw(info.root)); + } + if !info.name.is_null() { + drop(std::ffi::CString::from_raw(info.name)); + } + } +} + +/// Create a new operator layered with retry behavior. +/// +/// The current operator is not modified. Returned pointer must be released with +/// `operator_free`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +#[unsafe(no_mangle)] +pub extern "C" fn operator_layer_retry( + op: *const opendal::Operator, + jitter: bool, + factor: f32, + min_delay_nanos: u64, + max_delay_nanos: u64, + max_times: usize, +) -> OpendalOperatorResult { + match operator_layer_retry_inner( + op, + jitter, + factor, + min_delay_nanos, + max_delay_nanos, + max_times, + ) { + Ok(value) => OpendalOperatorResult::ok(value), + Err(error) => OpendalOperatorResult::from_error(error), + } +} + +fn operator_layer_retry_inner( + op: *const opendal::Operator, + jitter: bool, + factor: f32, + min_delay_nanos: u64, + max_delay_nanos: u64, + max_times: usize, +) -> Result<*mut c_void, OpenDALError> { + let op = require_operator(op)?; + validate_retry_options(factor, min_delay_nanos, max_delay_nanos)?; + + let mut retry = opendal::layers::RetryLayer::new(); + retry = retry.with_factor(factor); + retry = retry.with_min_delay(Duration::from_nanos(min_delay_nanos)); + retry = retry.with_max_delay(Duration::from_nanos(max_delay_nanos)); + retry = retry.with_max_times(max_times); + if jitter { + retry = retry.with_jitter(); + } + + Ok(Box::into_raw(Box::new(op.clone().layer(retry))) as *mut c_void) +} + +/// Create a new operator layered with concurrent-limit behavior. +/// +/// The current operator is not modified. Returned pointer must be released with +/// `operator_free`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +#[unsafe(no_mangle)] +pub extern "C" fn operator_layer_concurrent_limit( + op: *const opendal::Operator, + permits: usize, +) -> OpendalOperatorResult { + match operator_layer_concurrent_limit_inner(op, permits) { + Ok(value) => OpendalOperatorResult::ok(value), + Err(error) => OpendalOperatorResult::from_error(error), + } +} + +fn operator_layer_concurrent_limit_inner( + op: *const opendal::Operator, + permits: usize, +) -> Result<*mut c_void, OpenDALError> { + let op = require_operator(op)?; + validate_concurrent_limit_options(permits)?; + + let concurrent_limit = opendal::layers::ConcurrentLimitLayer::new(permits); + Ok(Box::into_raw(Box::new(op.clone().layer(concurrent_limit))) as *mut c_void) +} + +/// Create a new operator layered with timeout behavior. +/// +/// The current operator is not modified. Returned pointer must be released with +/// `operator_free`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +#[unsafe(no_mangle)] +pub extern "C" fn operator_layer_timeout( + op: *const opendal::Operator, + timeout_nanos: u64, + io_timeout_nanos: u64, +) -> OpendalOperatorResult { + match operator_layer_timeout_inner(op, timeout_nanos, io_timeout_nanos) { + Ok(value) => OpendalOperatorResult::ok(value), + Err(error) => OpendalOperatorResult::from_error(error), + } +} + +fn operator_layer_timeout_inner( + op: *const opendal::Operator, + timeout_nanos: u64, + io_timeout_nanos: u64, +) -> Result<*mut c_void, OpenDALError> { + let op = require_operator(op)?; + validate_timeout_options(timeout_nanos, io_timeout_nanos)?; + + let timeout = opendal::layers::TimeoutLayer::new() + .with_timeout(Duration::from_nanos(timeout_nanos)) + .with_io_timeout(Duration::from_nanos(io_timeout_nanos)); + + Ok(Box::into_raw(Box::new(op.clone().layer(timeout))) as *mut c_void) +} + +/// Duplicate an operator instance. +/// +/// Returned pointer must be released with `operator_free`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +#[unsafe(no_mangle)] +pub extern "C" fn operator_duplicate(op: *const opendal::Operator) -> OpendalOperatorResult { + match operator_duplicate_inner(op) { + Ok(value) => OpendalOperatorResult::ok(value), + Err(error) => OpendalOperatorResult::from_error(error), + } +} + +fn operator_duplicate_inner(op: *const opendal::Operator) -> Result<*mut c_void, OpenDALError> { + let op = require_operator(op)?; + Ok(Box::into_raw(Box::new(op.clone())) as *mut c_void) +} + +/// Delete `path` synchronously. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub extern "C" fn operator_delete( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, +) -> OpendalResult { + match operator_delete_inner(op, executor, path) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_delete_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?; + + executor + .block_on(op.delete(path)) + .map_err(OpenDALError::from_opendal_error) +} + +/// Delete `path` asynchronously. +/// +/// The callback is invoked exactly once with the final result. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub extern "C" fn operator_delete_async( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_delete_async_inner(op, executor, path, callback, context) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_delete_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + let callback = require_callback(callback)?; + + let op = op.clone(); + executor.spawn(async move { + let result = op + .delete(&path) + .await + .map_err(OpenDALError::from_opendal_error); + + callback( + context, + match result { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + }, + ); + }); + + Ok(()) +} + +/// Create directory at `path` synchronously. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub extern "C" fn operator_create_dir( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, +) -> OpendalResult { + match operator_create_dir_inner(op, executor, path) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_create_dir_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?; + + executor + .block_on(op.create_dir(path)) + .map_err(OpenDALError::from_opendal_error) +} + +/// Create directory at `path` asynchronously. +/// +/// The callback is invoked exactly once with the final result. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub extern "C" fn operator_create_dir_async( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_create_dir_async_inner(op, executor, path, callback, context) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_create_dir_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + let callback = require_callback(callback)?; + + let op = op.clone(); + executor.spawn(async move { + let result = op + .create_dir(&path) + .await + .map_err(OpenDALError::from_opendal_error); + + callback( + context, + match result { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + }, + ); + }); + + Ok(()) +} + +/// Copy from `source_path` to `target_path` synchronously. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `source_path` and `target_path` must be valid null-terminated UTF-8 strings. +#[unsafe(no_mangle)] +pub extern "C" fn operator_copy( + op: *const opendal::Operator, + executor: *const c_void, + source_path: *const c_char, + target_path: *const c_char, +) -> OpendalResult { + match operator_copy_inner(op, executor, source_path, target_path) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_copy_inner( + op: *const opendal::Operator, + executor: *const c_void, + source_path: *const c_char, + target_path: *const c_char, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let source_path = require_cstr(source_path, "source_path")?; + let target_path = require_cstr(target_path, "target_path")?; + + executor + .block_on(op.copy(source_path, target_path)) + .map_err(OpenDALError::from_opendal_error) +} + +/// Copy from `source_path` to `target_path` asynchronously. +/// +/// The callback is invoked exactly once with the final result. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `source_path` and `target_path` must be valid null-terminated UTF-8 strings. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub extern "C" fn operator_copy_async( + op: *const opendal::Operator, + executor: *const c_void, + source_path: *const c_char, + target_path: *const c_char, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_copy_async_inner(op, executor, source_path, target_path, callback, context) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_copy_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + source_path: *const c_char, + target_path: *const c_char, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let source_path = require_cstr(source_path, "source_path")?.to_string(); + let target_path = require_cstr(target_path, "target_path")?.to_string(); + let callback = require_callback(callback)?; + + let op = op.clone(); + executor.spawn(async move { + let result = op + .copy(&source_path, &target_path) + .await + .map_err(OpenDALError::from_opendal_error); + + callback( + context, + match result { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + }, + ); + }); + + Ok(()) +} + +/// Rename from `source_path` to `target_path` synchronously. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `source_path` and `target_path` must be valid null-terminated UTF-8 strings. +#[unsafe(no_mangle)] +pub extern "C" fn operator_rename( + op: *const opendal::Operator, + executor: *const c_void, + source_path: *const c_char, + target_path: *const c_char, +) -> OpendalResult { + match operator_rename_inner(op, executor, source_path, target_path) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_rename_inner( + op: *const opendal::Operator, + executor: *const c_void, + source_path: *const c_char, + target_path: *const c_char, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let source_path = require_cstr(source_path, "source_path")?; + let target_path = require_cstr(target_path, "target_path")?; + + executor + .block_on(op.rename(source_path, target_path)) + .map_err(OpenDALError::from_opendal_error) +} + +/// Rename from `source_path` to `target_path` asynchronously. +/// +/// The callback is invoked exactly once with the final result. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `source_path` and `target_path` must be valid null-terminated UTF-8 strings. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub extern "C" fn operator_rename_async( + op: *const opendal::Operator, + executor: *const c_void, + source_path: *const c_char, + target_path: *const c_char, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_rename_async_inner(op, executor, source_path, target_path, callback, context) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_rename_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + source_path: *const c_char, + target_path: *const c_char, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let source_path = require_cstr(source_path, "source_path")?.to_string(); + let target_path = require_cstr(target_path, "target_path")?.to_string(); + let callback = require_callback(callback)?; + + let op = op.clone(); + executor.spawn(async move { + let result = op + .rename(&source_path, &target_path) + .await + .map_err(OpenDALError::from_opendal_error); + + callback( + context, + match result { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + }, + ); + }); + + Ok(()) +} + +/// Remove `path` recursively synchronously. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub extern "C" fn operator_remove_all( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, +) -> OpendalResult { + match operator_remove_all_inner(op, executor, path) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_remove_all_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?; + + let options = opendal::options::DeleteOptions { + recursive: true, + ..Default::default() + }; + + executor + .block_on(op.delete_options(path, options)) + .map_err(OpenDALError::from_opendal_error) +} + +/// Remove `path` recursively asynchronously. +/// +/// The callback is invoked exactly once with the final result. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub extern "C" fn operator_remove_all_async( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_remove_all_async_inner(op, executor, path, callback, context) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_remove_all_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + let callback = require_callback(callback)?; + let options = opendal::options::DeleteOptions { + recursive: true, + ..Default::default() + }; + + let op = op.clone(); + executor.spawn(async move { + let result = op + .delete_options(&path, options) + .await + .map_err(OpenDALError::from_opendal_error); + + callback( + context, + match result { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + }, + ); + }); + + Ok(()) +} + +/// Presign read asynchronously. +/// +/// The callback is invoked exactly once with the final result. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub extern "C" fn operator_presign_read_async( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + expire_nanos: u64, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_presign_read_async_inner(op, executor, path, expire_nanos, callback, context) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_presign_read_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + expire_nanos: u64, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + let callback = require_callback(callback)?; + let expire = Duration::from_nanos(expire_nanos); + + let op = op.clone(); + executor.spawn(async move { + let result = op + .presign_read(&path, expire) + .await + .map_err(OpenDALError::from_opendal_error) + .and_then(into_presigned_request_ptr); + + callback( + context, + match result { + Ok(value) => OpendalPresignedRequestResult::ok(value), + Err(error) => OpendalPresignedRequestResult::from_error(error), + }, + ); + }); + + Ok(()) +} + +/// Presign write asynchronously. +/// +/// The callback is invoked exactly once with the final result. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub extern "C" fn operator_presign_write_async( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + expire_nanos: u64, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_presign_write_async_inner(op, executor, path, expire_nanos, callback, context) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_presign_write_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + expire_nanos: u64, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + let callback = require_callback(callback)?; + let expire = Duration::from_nanos(expire_nanos); + + let op = op.clone(); + executor.spawn(async move { + let result = op + .presign_write(&path, expire) + .await + .map_err(OpenDALError::from_opendal_error) + .and_then(into_presigned_request_ptr); + + callback( + context, + match result { + Ok(value) => OpendalPresignedRequestResult::ok(value), + Err(error) => OpendalPresignedRequestResult::from_error(error), + }, + ); + }); + + Ok(()) +} + +/// Presign stat asynchronously. +/// +/// The callback is invoked exactly once with the final result. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub extern "C" fn operator_presign_stat_async( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + expire_nanos: u64, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_presign_stat_async_inner(op, executor, path, expire_nanos, callback, context) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_presign_stat_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + expire_nanos: u64, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + let callback = require_callback(callback)?; + let expire = Duration::from_nanos(expire_nanos); + + let op = op.clone(); + executor.spawn(async move { + let result = op + .presign_stat(&path, expire) + .await + .map_err(OpenDALError::from_opendal_error) + .and_then(into_presigned_request_ptr); + + callback( + context, + match result { + Ok(value) => OpendalPresignedRequestResult::ok(value), + Err(error) => OpendalPresignedRequestResult::from_error(error), + }, + ); + }); + + Ok(()) +} + +/// Presign delete asynchronously. +/// +/// The callback is invoked exactly once with the final result. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub extern "C" fn operator_presign_delete_async( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + expire_nanos: u64, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_presign_delete_async_inner(op, executor, path, expire_nanos, callback, context) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_presign_delete_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + expire_nanos: u64, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + let callback = require_callback(callback)?; + let expire = Duration::from_nanos(expire_nanos); + + let op = op.clone(); + executor.spawn(async move { + let result = op + .presign_delete(&path, expire) + .await + .map_err(OpenDALError::from_opendal_error) + .and_then(into_presigned_request_ptr); + + callback( + context, + match result { + Ok(value) => OpendalPresignedRequestResult::ok(value), + Err(error) => OpendalPresignedRequestResult::from_error(error), + }, + ); + }); + + Ok(()) +} + +/// Create an input stream for `path` with read options. +/// +/// Returned pointer must be released by `operator_input_stream_free`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub extern "C" fn operator_input_stream_create( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::ReadOptions, +) -> OpendalOperatorResult { + match operator_input_stream_create_inner(op, executor, path, options) { + Ok(value) => OpendalOperatorResult::ok(value), + Err(error) => OpendalOperatorResult::from_error(error), + } +} + +fn operator_input_stream_create_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::ReadOptions, +) -> Result<*mut c_void, OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + let options = if options.is_null() { + opendal::options::ReadOptions::default() + } else { + unsafe { (&*options).clone() } + }; + + let _guard = executor.enter(); + + let blocking_op = + opendal::blocking::Operator::new(op.clone()).map_err(OpenDALError::from_opendal_error)?; + let reader = blocking_op + .reader_options(&path, opendal::options::ReaderOptions::default()) + .map_err(OpenDALError::from_opendal_error)?; + let stream = reader + .into_bytes_iterator(options.range.to_range()) + .map_err(OpenDALError::from_opendal_error)?; + + Ok(Box::into_raw(Box::new(stream)) as *mut c_void) +} + +/// Read next bytes chunk from input stream. +/// +/// Returns an empty buffer when EOF is reached. +/// # Safety +/// +/// - `stream` must be a valid pointer returned by `operator_input_stream_create`. +#[unsafe(no_mangle)] +pub extern "C" fn operator_input_stream_read_next( + stream: *mut opendal::blocking::StdBytesIterator, +) -> OpendalReadResult { + match operator_input_stream_read_next_inner(stream) { + Ok(buffer) => OpendalReadResult::ok(buffer), + Err(error) => OpendalReadResult::from_error(error), + } +} + +fn operator_input_stream_read_next_inner( + stream: *mut opendal::blocking::StdBytesIterator, +) -> Result { + if stream.is_null() { + return Err(crate::utils::config_invalid_error( + "input stream pointer is null", + )); + } + + let stream = unsafe { &mut *stream }; + + let value = stream + .next() + .transpose() + .map_err(|err| { + OpenDALError::from_opendal_error(opendal::Error::new( + opendal::ErrorKind::Unexpected, + err.to_string(), + )) + })? + .map(|v| ByteBuffer::from_vec(v.to_vec())) + .unwrap_or_else(ByteBuffer::empty); + + Ok(value) +} + +/// # Safety +/// +/// - `stream` must be null or a pointer returned by `operator_input_stream_create`. +/// - Must be called at most once for the same pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn operator_input_stream_free( + stream: *mut opendal::blocking::StdBytesIterator, +) { + if stream.is_null() { + return; + } + + unsafe { + drop(Box::from_raw(stream)); + } +} + +/// Create an output stream for `path` with write options. +/// +/// Returned pointer must be released by `operator_output_stream_free`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +#[unsafe(no_mangle)] +pub extern "C" fn operator_output_stream_create( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::WriteOptions, +) -> OpendalOperatorResult { + match operator_output_stream_create_inner(op, executor, path, options) { + Ok(value) => OpendalOperatorResult::ok(value), + Err(error) => OpendalOperatorResult::from_error(error), + } +} + +fn operator_output_stream_create_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::WriteOptions, +) -> Result<*mut c_void, OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + let options = if options.is_null() { + opendal::options::WriteOptions::default() + } else { + unsafe { (&*options).clone() } + }; + + let _guard = executor.enter(); + + let blocking_op = + opendal::blocking::Operator::new(op.clone()).map_err(OpenDALError::from_opendal_error)?; + let stream = blocking_op + .writer_options(&path, options) + .map_err(OpenDALError::from_opendal_error)?; + + Ok(Box::into_raw(Box::new(stream)) as *mut c_void) +} + +/// Write bytes to output stream. +/// # Safety +/// +/// - `stream` must be a valid pointer returned by `operator_output_stream_create`. +/// - When `len > 0`, `data` must be non-null and readable for `len` bytes. +#[unsafe(no_mangle)] +pub extern "C" fn operator_output_stream_write( + stream: *mut opendal::blocking::Writer, + data: *const u8, + len: usize, +) -> OpendalResult { + match operator_output_stream_write_inner(stream, data, len) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_output_stream_write_inner( + stream: *mut opendal::blocking::Writer, + data: *const u8, + len: usize, +) -> Result<(), OpenDALError> { + if stream.is_null() { + return Err(crate::utils::config_invalid_error( + "output stream pointer is null", + )); + } + require_data_ptr(data, len)?; + + let stream = unsafe { &mut *stream }; + let payload = if len == 0 { + &[][..] + } else { + unsafe { std::slice::from_raw_parts(data, len) } + }; + + stream + .write(payload) + .map(|_| ()) + .map_err(OpenDALError::from_opendal_error) +} + +/// Flush output stream. +/// # Safety +/// +/// - `stream` must be a valid pointer returned by `operator_output_stream_create`. +#[unsafe(no_mangle)] +pub extern "C" fn operator_output_stream_flush( + stream: *mut opendal::blocking::Writer, +) -> OpendalResult { + match operator_output_stream_flush_inner(stream) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_output_stream_flush_inner( + stream: *mut opendal::blocking::Writer, +) -> Result<(), OpenDALError> { + if stream.is_null() { + return Err(crate::utils::config_invalid_error( + "output stream pointer is null", + )); + } + + Ok(()) +} + +/// Close output stream. +/// # Safety +/// +/// - `stream` must be a valid pointer returned by `operator_output_stream_create`. +#[unsafe(no_mangle)] +pub extern "C" fn operator_output_stream_close( + stream: *mut opendal::blocking::Writer, +) -> OpendalResult { + match operator_output_stream_close_inner(stream) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_output_stream_close_inner( + stream: *mut opendal::blocking::Writer, +) -> Result<(), OpenDALError> { + if stream.is_null() { + return Err(crate::utils::config_invalid_error( + "output stream pointer is null", + )); + } + + let stream = unsafe { &mut *stream }; + stream + .close() + .map(|_| ()) + .map_err(OpenDALError::from_opendal_error) +} + +/// # Safety +/// +/// - `stream` must be null or a pointer returned by `operator_output_stream_create`. +/// - Must be called at most once for the same pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn operator_output_stream_free(stream: *mut opendal::blocking::Writer) { + if stream.is_null() { + return; + } + + unsafe { + drop(Box::from_raw(stream)); + } +} + +/// Write bytes to `path` synchronously with options. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - When `len > 0`, `data` must be non-null and readable for `len` bytes. +/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays. +#[unsafe(no_mangle)] +pub extern "C" fn operator_write_with_options( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + data: *const u8, + len: usize, + options: *const opendal::options::WriteOptions, +) -> OpendalResult { + match operator_write_with_options_inner(op, executor, path, data, len, options) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_write_with_options_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + data: *const u8, + len: usize, + options: *const opendal::options::WriteOptions, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?; + require_data_ptr(data, len)?; + let options = if options.is_null() { + opendal::options::WriteOptions::default() + } else { + unsafe { (&*options).clone() } + }; + + let payload = if len == 0 { + &[][..] + } else { + unsafe { std::slice::from_raw_parts(data, len) } + }; + + executor + .block_on(op.write_options(path, payload, options)) + .map(|_| ()) + .map_err(OpenDALError::from_opendal_error) +} + +/// Write bytes to `path` asynchronously with options. +/// +/// The callback is invoked exactly once with the final result. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - `data.data` must be non-null and readable for `data.len` bytes when `data.len > 0`. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn operator_write_with_options_async( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + data: ByteBuffer, + options: *const opendal::options::WriteOptions, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_write_with_options_async_inner( + op, executor, path, data, options, callback, context, + ) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_write_with_options_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + data: ByteBuffer, + options: *const opendal::options::WriteOptions, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + require_data_ptr(data.data.cast_const(), data.len)?; + let callback = require_callback(callback)?; + let options = if options.is_null() { + opendal::options::WriteOptions::default() + } else { + unsafe { (&*options).clone() } + }; + + let payload = if data.len == 0 { + Vec::new() + } else { + unsafe { std::slice::from_raw_parts(data.data.cast_const(), data.len) }.to_vec() + }; + + let op = op.clone(); + executor.spawn(async move { + let result = op + .write_options(&path, payload, options) + .await + .map(|_| ()) + .map_err(OpenDALError::from_opendal_error); + + callback( + context, + match result { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + }, + ); + }); + + Ok(()) +} + +/// Read bytes from `path` synchronously with options. +/// +/// On success, the returned buffer must be released with `opendal_read_result_release`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn operator_read_with_options( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::ReadOptions, +) -> OpendalReadResult { + match operator_read_with_options_inner(op, executor, path, options) { + Ok(value) => OpendalReadResult::ok(value), + Err(error) => OpendalReadResult::from_error(error), + } +} + +fn operator_read_with_options_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::ReadOptions, +) -> Result { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?; + let options = if options.is_null() { + opendal::options::ReadOptions::default() + } else { + unsafe { (&*options).clone() } + }; + + let value = executor + .block_on(op.read_options(path, options)) + .map(|v| v.to_vec()) + .map_err(OpenDALError::from_opendal_error)?; + + Ok(ByteBuffer::from_vec(value)) +} + +/// Read bytes from `path` asynchronously with options. +/// +/// The callback is invoked exactly once. On successful reads, the callback +/// result must be released with `opendal_read_result_release`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub extern "C" fn operator_read_with_options_async( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::ReadOptions, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_read_with_options_async_inner(op, executor, path, options, callback, context) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_read_with_options_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::ReadOptions, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + let callback = require_callback(callback)?; + let options = if options.is_null() { + opendal::options::ReadOptions::default() + } else { + unsafe { (&*options).clone() } + }; + + let op = op.clone(); + executor.spawn(async move { + let result = op + .read_options(&path, options) + .await + .map(|v| ByteBuffer::from_vec(v.to_vec())) + .map_err(OpenDALError::from_opendal_error); + + callback( + context, + match result { + Ok(value) => OpendalReadResult::ok(value), + Err(error) => OpendalReadResult::from_error(error), + }, + ); + }); + + Ok(()) +} + +/// Stat `path` synchronously with options. +/// +/// On success, returned payload must be released with `opendal_metadata_result_release`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays. +#[unsafe(no_mangle)] +pub extern "C" fn operator_stat_with_options( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::StatOptions, +) -> OpendalMetadataResult { + match operator_stat_with_options_inner(op, executor, path, options) { + Ok(value) => OpendalMetadataResult::ok(value as *mut c_void), + Err(error) => OpendalMetadataResult::from_error(error), + } +} + +fn operator_stat_with_options_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::StatOptions, +) -> Result<*mut OpendalMetadata, OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?; + let options = if options.is_null() { + opendal::options::StatOptions::default() + } else { + unsafe { (&*options).clone() } + }; + + let metadata = executor + .block_on(op.stat_options(path, options)) + .map_err(OpenDALError::from_opendal_error)?; + Ok(Box::into_raw(Box::new(OpendalMetadata::from_metadata( + metadata, + )))) +} + +/// Stat `path` asynchronously with options. +/// +/// The callback is invoked exactly once. On success, callback result must be +/// released with `opendal_metadata_result_release`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub extern "C" fn operator_stat_with_options_async( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::StatOptions, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_stat_with_options_async_inner(op, executor, path, options, callback, context) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_stat_with_options_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::StatOptions, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + let callback = require_callback(callback)?; + let options = if options.is_null() { + opendal::options::StatOptions::default() + } else { + unsafe { (&*options).clone() } + }; + + let op = op.clone(); + executor.spawn(async move { + let result = op + .stat_options(&path, options) + .await + .map(OpendalMetadata::from_metadata) + .map(|v| Box::into_raw(Box::new(v))) + .map_err(OpenDALError::from_opendal_error); + + callback( + context, + match result { + Ok(value) => OpendalMetadataResult::ok(value as *mut c_void), + Err(error) => OpendalMetadataResult::from_error(error), + }, + ); + }); + + Ok(()) +} + +/// List entries from `path` synchronously with options. +/// +/// On success, returned payload must be released with `opendal_entry_list_result_release`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays. +#[unsafe(no_mangle)] +pub extern "C" fn operator_list_with_options( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::ListOptions, +) -> OpendalEntryListResult { + match operator_list_with_options_inner(op, executor, path, options) { + Ok(value) => OpendalEntryListResult::ok(value), + Err(error) => OpendalEntryListResult::from_error(error), + } +} + +fn operator_list_with_options_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::ListOptions, +) -> Result<*mut c_void, OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?; + let options = if options.is_null() { + opendal::options::ListOptions::default() + } else { + unsafe { (&*options).clone() } + }; + + let entries = executor + .block_on(op.list_options(path, options)) + .map_err(OpenDALError::from_opendal_error)?; + + Ok(into_entry_list_ptr(entries)) +} + +/// List entries from `path` asynchronously with options. +/// +/// The callback is invoked exactly once. On success, callback result must be +/// released with `opendal_entry_list_result_release`. +/// # Safety +/// +/// - `op` must be a valid operator pointer from `operator_construct`. +/// - `path` must be a valid null-terminated UTF-8 string. +/// - When `option_len > 0`, `option_keys` and `option_values` must be valid arrays. +/// - `callback` must be a valid function pointer and remain callable until invoked. +#[unsafe(no_mangle)] +pub extern "C" fn operator_list_with_options_async( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::ListOptions, + callback: Option, + context: i64, +) -> OpendalResult { + match operator_list_with_options_async_inner(op, executor, path, options, callback, context) { + Ok(()) => OpendalResult::ok(), + Err(error) => OpendalResult::from_error(error), + } +} + +fn operator_list_with_options_async_inner( + op: *const opendal::Operator, + executor: *const c_void, + path: *const c_char, + options: *const opendal::options::ListOptions, + callback: Option, + context: i64, +) -> Result<(), OpenDALError> { + let op = require_operator(op)?; + let executor = executor_or_default(executor)?; + let path = require_cstr(path, "path")?.to_string(); + let callback = require_callback(callback)?; + let options = if options.is_null() { + opendal::options::ListOptions::default() + } else { + unsafe { (&*options).clone() } + }; + + let op = op.clone(); + executor.spawn(async move { + let result = op + .list_options(&path, options) + .await + .map(into_entry_list_ptr) + .map_err(OpenDALError::from_opendal_error); + + callback( + context, + match result { + Ok(value) => OpendalEntryListResult::ok(value), + Err(error) => OpendalEntryListResult::from_error(error), + }, + ); + }); + + Ok(()) +} diff --git a/bindings/dotnet/src/operator_info.rs b/bindings/dotnet/src/operator_info.rs new file mode 100644 index 000000000000..b29b38165670 --- /dev/null +++ b/bindings/dotnet/src/operator_info.rs @@ -0,0 +1,40 @@ +// 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. + +use std::os::raw::c_char; + +use crate::{capability::Capability, utils::into_string_ptr}; + +#[repr(C)] +/// Operator info payload exposed to the .NET binding. +pub struct OpendalOperatorInfo { + pub scheme: *mut c_char, + pub root: *mut c_char, + pub name: *mut c_char, + pub full_capability: Capability, + pub native_capability: Capability, +} + +pub fn into_operator_info(info: opendal::OperatorInfo) -> OpendalOperatorInfo { + OpendalOperatorInfo { + scheme: into_string_ptr(info.scheme().to_string()), + root: into_string_ptr(info.root()), + name: into_string_ptr(info.name()), + full_capability: Capability::new(info.full_capability()), + native_capability: Capability::new(info.native_capability()), + } +} diff --git a/bindings/dotnet/src/options.rs b/bindings/dotnet/src/options.rs new file mode 100644 index 000000000000..3648f8a93c38 --- /dev/null +++ b/bindings/dotnet/src/options.rs @@ -0,0 +1,212 @@ +// 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. + +use std::collections::HashMap; + +use opendal::raw::Timestamp; + +use crate::error::OpenDALError; +use crate::validators::prelude::{ + validate_list_limit, validate_read_chunk, validate_read_concurrent, validate_read_gap, + validate_read_range_end, validate_write_chunk, validate_write_concurrent, +}; + +fn parse_bool(values: &HashMap, key: &str) -> Result, OpenDALError> { + let Some(raw) = values.get(key) else { + return Ok(None); + }; + + match raw.to_ascii_lowercase().as_str() { + "true" | "1" => Ok(Some(true)), + "false" | "0" => Ok(Some(false)), + _ => Err(crate::utils::config_invalid_error(format!( + "invalid boolean value for {key}: {raw}" + ))), + } +} + +fn parse_usize(values: &HashMap, key: &str) -> Result, OpenDALError> { + let Some(raw) = values.get(key) else { + return Ok(None); + }; + + raw.parse::().map(Some).map_err(|err| { + crate::utils::config_invalid_error(format!("invalid usize value for {key}: {raw}, {err}")) + }) +} + +fn parse_u64(values: &HashMap, key: &str) -> Result, OpenDALError> { + let Some(raw) = values.get(key) else { + return Ok(None); + }; + + raw.parse::().map(Some).map_err(|err| { + crate::utils::config_invalid_error(format!("invalid u64 value for {key}: {raw}, {err}")) + }) +} + +fn parse_timestamp( + values: &HashMap, + key: &str, +) -> Result, OpenDALError> { + let Some(raw) = values.get(key) else { + return Ok(None); + }; + + let millis = raw.parse::().map_err(|err| { + crate::utils::config_invalid_error(format!( + "invalid timestamp milliseconds for {key}: {raw}, {err}" + )) + })?; + + Timestamp::from_millisecond(millis) + .map(Some) + .map_err(OpenDALError::from_opendal_error) +} + +fn parse_string(values: &HashMap, key: &str) -> Option { + values.get(key).cloned() +} + +pub fn parse_read_options( + values: &HashMap, +) -> Result { + let mut options = opendal::options::ReadOptions::default(); + + let offset = parse_u64(values, "offset")?.unwrap_or_default(); + let length = parse_u64(values, "length")?; + if offset > 0 || length.is_some() { + options.range = match validate_read_range_end(offset, length)? { + Some(end) => (offset..end).into(), + None => (offset..).into(), + }; + } + + options.version = parse_string(values, "version"); + options.if_match = parse_string(values, "if_match"); + options.if_none_match = parse_string(values, "if_none_match"); + options.if_modified_since = parse_timestamp(values, "if_modified_since")?; + options.if_unmodified_since = parse_timestamp(values, "if_unmodified_since")?; + + if let Some(concurrent) = parse_usize(values, "concurrent")? { + validate_read_concurrent(concurrent)?; + options.concurrent = concurrent; + } + + if let Some(chunk) = parse_usize(values, "chunk")? { + validate_read_chunk(chunk)?; + options.chunk = Some(chunk); + } + + if let Some(gap) = parse_usize(values, "gap")? { + validate_read_gap(gap)?; + options.gap = Some(gap); + } + + options.override_content_type = parse_string(values, "override_content_type"); + options.override_cache_control = parse_string(values, "override_cache_control"); + options.override_content_disposition = parse_string(values, "override_content_disposition"); + + Ok(options) +} + +pub fn parse_write_options( + values: &HashMap, +) -> Result { + let mut options = opendal::options::WriteOptions::default(); + + if let Some(append) = parse_bool(values, "append")? { + options.append = append; + } + + options.cache_control = parse_string(values, "cache_control"); + options.content_type = parse_string(values, "content_type"); + options.content_disposition = parse_string(values, "content_disposition"); + options.content_encoding = parse_string(values, "content_encoding"); + options.if_match = parse_string(values, "if_match"); + options.if_none_match = parse_string(values, "if_none_match"); + + if let Some(if_not_exists) = parse_bool(values, "if_not_exists")? { + options.if_not_exists = if_not_exists; + } + + if let Some(concurrent) = parse_usize(values, "concurrent")? { + validate_write_concurrent(concurrent)?; + options.concurrent = concurrent; + } + + if let Some(chunk) = parse_usize(values, "chunk")? { + validate_write_chunk(chunk)?; + options.chunk = Some(chunk); + } + + let user_metadata = values + .iter() + .filter_map(|(key, value)| { + key.strip_prefix("user_metadata.") + .map(|k| (k.to_string(), value.to_string())) + }) + .collect::>(); + + if !user_metadata.is_empty() { + options.user_metadata = Some(user_metadata); + } + + Ok(options) +} + +pub fn parse_stat_options( + values: &HashMap, +) -> Result { + Ok(opendal::options::StatOptions { + version: parse_string(values, "version"), + if_match: parse_string(values, "if_match"), + if_none_match: parse_string(values, "if_none_match"), + if_modified_since: parse_timestamp(values, "if_modified_since")?, + if_unmodified_since: parse_timestamp(values, "if_unmodified_since")?, + override_content_type: parse_string(values, "override_content_type"), + override_cache_control: parse_string(values, "override_cache_control"), + override_content_disposition: parse_string(values, "override_content_disposition"), + }) +} + +pub fn parse_list_options( + values: &HashMap, +) -> Result { + let mut options = opendal::options::ListOptions::default(); + + if let Some(recursive) = parse_bool(values, "recursive")? { + options.recursive = recursive; + } + + if let Some(limit) = parse_usize(values, "limit")? { + validate_list_limit(limit)?; + options.limit = Some(limit); + } + + options.start_after = parse_string(values, "start_after"); + + if let Some(versions) = parse_bool(values, "versions")? { + options.versions = versions; + } + + if let Some(deleted) = parse_bool(values, "deleted")? { + options.deleted = deleted; + } + + Ok(options) +} diff --git a/bindings/dotnet/src/presign.rs b/bindings/dotnet/src/presign.rs new file mode 100644 index 000000000000..540b078faccd --- /dev/null +++ b/bindings/dotnet/src/presign.rs @@ -0,0 +1,129 @@ +// 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. + +use std::ffi::{c_char, c_void}; + +use crate::error::OpenDALError; +use crate::utils::into_string_ptr; + +#[repr(C)] +pub struct OpendalPresignedRequest { + pub method: *mut c_char, + pub uri: *mut c_char, + pub headers_keys: *mut *mut c_char, + pub headers_values: *mut *mut c_char, + pub headers_len: usize, +} + +pub fn into_presigned_request_ptr( + request: opendal::raw::PresignedRequest, +) -> Result<*mut c_void, OpenDALError> { + let method = into_string_ptr(request.method().as_str().to_string()); + let uri = into_string_ptr(request.uri().to_string()); + + let mut keys = Vec::new(); + let mut values = Vec::new(); + + for (key, value) in request.header() { + let value = value + .to_str() + .map_err(|err| { + OpenDALError::from_opendal_error(opendal::Error::new( + opendal::ErrorKind::Unexpected, + err.to_string(), + )) + })? + .to_string(); + + keys.push(into_string_ptr(key.to_string())); + values.push(into_string_ptr(value)); + } + + let len = keys.len(); + let (headers_keys, headers_values, headers_len) = if len == 0 { + (std::ptr::null_mut(), std::ptr::null_mut(), 0) + } else { + let mut keys = keys; + let mut values = values; + let keys_ptr = keys.as_mut_ptr(); + let values_ptr = values.as_mut_ptr(); + std::mem::forget(keys); + std::mem::forget(values); + + (keys_ptr, values_ptr, len) + }; + + let request = OpendalPresignedRequest { + method, + uri, + headers_keys, + headers_values, + headers_len, + }; + + Ok(Box::into_raw(Box::new(request)) as *mut c_void) +} + +/// # Safety +/// +/// - `request` must be null or a pointer produced by `into_presigned_request_ptr`. +/// - This function must be called at most once per non-null pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn presigned_request_free(request: *mut OpendalPresignedRequest) { + if request.is_null() { + return; + } + + unsafe { + let request = Box::from_raw(request); + + if !request.method.is_null() { + drop(std::ffi::CString::from_raw(request.method)); + } + if !request.uri.is_null() { + drop(std::ffi::CString::from_raw(request.uri)); + } + + if request.headers_len > 0 { + if !request.headers_keys.is_null() { + let keys = Vec::from_raw_parts( + request.headers_keys, + request.headers_len, + request.headers_len, + ); + for key in keys { + if !key.is_null() { + drop(std::ffi::CString::from_raw(key)); + } + } + } + + if !request.headers_values.is_null() { + let values = Vec::from_raw_parts( + request.headers_values, + request.headers_len, + request.headers_len, + ); + for value in values { + if !value.is_null() { + drop(std::ffi::CString::from_raw(value)); + } + } + } + } + } +} diff --git a/bindings/dotnet/src/result.rs b/bindings/dotnet/src/result.rs new file mode 100644 index 000000000000..9568246cf1ea --- /dev/null +++ b/bindings/dotnet/src/result.rs @@ -0,0 +1,294 @@ +// 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. + +use std::ffi::c_void; + +use crate::byte_buffer::{ByteBuffer, buffer_free}; +use crate::entry::entry_list_free; +use crate::error::OpenDALError; +use crate::metadata::metadata_free; +use crate::operator::operator_info_free; +use crate::presign::presigned_request_free; + +#[repr(C)] +/// Result for operations that only report success or failure. +pub struct OpendalResult { + pub error: OpenDALError, +} + +#[repr(C)] +/// Result for operations returning an operator handle pointer. +pub struct OpendalOperatorResult { + pub ptr: *mut c_void, + pub error: OpenDALError, +} + +#[repr(C)] +/// Result for operations returning an options handle pointer. +pub struct OpendalOptionsResult { + pub ptr: *mut c_void, + pub error: OpenDALError, +} + +#[repr(C)] +/// Result for operations returning an executor handle pointer. +pub struct OpendalExecutorResult { + pub ptr: *mut c_void, + pub error: OpenDALError, +} + +#[repr(C)] +/// Result for operations returning an operator info payload pointer. +pub struct OpendalOperatorInfoResult { + pub ptr: *mut c_void, + pub error: OpenDALError, +} + +#[repr(C)] +/// Result for operations returning a metadata payload pointer. +pub struct OpendalMetadataResult { + pub ptr: *mut c_void, + pub error: OpenDALError, +} + +#[repr(C)] +/// Result for operations returning an entry list payload pointer. +pub struct OpendalEntryListResult { + pub ptr: *mut c_void, + pub error: OpenDALError, +} + +#[repr(C)] +/// Result for operations returning a byte buffer payload. +pub struct OpendalReadResult { + pub buffer: ByteBuffer, + pub error: OpenDALError, +} + +#[repr(C)] +/// Result for operations returning a presigned request payload pointer. +pub struct OpendalPresignedRequestResult { + pub ptr: *mut c_void, + pub error: OpenDALError, +} + +macro_rules! define_result { + ($result_ty:ident) => { + impl $result_ty { + pub fn ok() -> Self { + Self { + error: OpenDALError::ok(), + } + } + + pub fn from_error(error: OpenDALError) -> Self { + Self { error } + } + } + }; + + ( + $result_ty:ident, + field = $field:ident : $payload_ty:ty, + error_value = $error_value:expr + ) => { + impl $result_ty { + pub fn ok($field: $payload_ty) -> Self { + Self { + $field, + error: OpenDALError::ok(), + } + } + + pub fn from_error(error: OpenDALError) -> Self { + Self { + $field: $error_value, + error, + } + } + } + }; +} + +define_result!(OpendalResult); + +define_result!( + OpendalOperatorResult, + field = ptr: *mut c_void, + error_value = std::ptr::null_mut() +); + +define_result!( + OpendalOptionsResult, + field = ptr: *mut c_void, + error_value = std::ptr::null_mut() +); + +define_result!( + OpendalExecutorResult, + field = ptr: *mut c_void, + error_value = std::ptr::null_mut() +); + +define_result!( + OpendalOperatorInfoResult, + field = ptr: *mut c_void, + error_value = std::ptr::null_mut() +); + +define_result!( + OpendalMetadataResult, + field = ptr: *mut c_void, + error_value = std::ptr::null_mut() +); + +define_result!( + OpendalEntryListResult, + field = ptr: *mut c_void, + error_value = std::ptr::null_mut() +); + +define_result!( + OpendalReadResult, + field = buffer: ByteBuffer, + error_value = ByteBuffer::empty() +); + +define_result!( + OpendalPresignedRequestResult, + field = ptr: *mut c_void, + error_value = std::ptr::null_mut() +); + +fn release_error_message(error: &mut OpenDALError) { + if error.message.is_null() { + return; + } + + unsafe { + drop(std::ffi::CString::from_raw(error.message)); + } + error.message = std::ptr::null_mut(); +} + +/// Release an error message allocated by this FFI layer. +/// +/// This function only releases `OpenDALError.message` and does not touch any +/// payload pointer carried by result structs. +#[unsafe(no_mangle)] +pub extern "C" fn opendal_error_release(mut error: OpenDALError) { + release_error_message(&mut error); +} + +/// Release an operator-info result payload and its error message. +/// +/// This function is idempotent for null payload pointers. +/// # Safety +/// +/// - `result.ptr`, when non-null, must come from `operator_info_get`. +/// - The payload pointer must not be used after this call. +#[unsafe(no_mangle)] +pub extern "C" fn opendal_operator_info_result_release(mut result: OpendalOperatorInfoResult) { + if !result.ptr.is_null() { + unsafe { + operator_info_free(result.ptr.cast()); + } + } + + release_error_message(&mut result.error); +} + +/// Release a metadata result payload and its error message. +/// +/// This function is idempotent for null payload pointers. +/// # Safety +/// +/// - `result.ptr`, when non-null, must come from metadata-producing APIs in +/// this crate. +/// - The payload pointer must not be used after this call. +#[unsafe(no_mangle)] +pub extern "C" fn opendal_metadata_result_release(mut result: OpendalMetadataResult) { + if !result.ptr.is_null() { + unsafe { + metadata_free(result.ptr.cast()); + } + } + + release_error_message(&mut result.error); +} + +/// Release an entry-list result payload and its error message. +/// +/// This function is idempotent for null payload pointers. +/// # Safety +/// +/// - `result.ptr`, when non-null, must come from list-producing APIs in this +/// crate. +/// - The payload pointer must not be used after this call. +#[unsafe(no_mangle)] +pub extern "C" fn opendal_entry_list_result_release(mut result: OpendalEntryListResult) { + if !result.ptr.is_null() { + unsafe { + entry_list_free(result.ptr.cast()); + } + } + + release_error_message(&mut result.error); +} + +/// Release a read result buffer and its error message. +/// +/// This function is idempotent for empty/null buffers. +/// # Safety +/// +/// - `result.buffer` must originate from `ByteBuffer::from_vec` in this crate. +/// - The buffer memory must not be accessed after this call. +#[unsafe(no_mangle)] +pub extern "C" fn opendal_read_result_release(mut result: OpendalReadResult) { + if !result.buffer.data.is_null() { + unsafe { + buffer_free( + result.buffer.data, + result.buffer.len, + result.buffer.capacity, + ); + } + } + + release_error_message(&mut result.error); +} + +/// Release a presigned-request result payload and its error message. +/// +/// This function is idempotent for null payload pointers. +/// # Safety +/// +/// - `result.ptr`, when non-null, must come from presign-producing APIs in +/// this crate. +/// - The payload pointer must not be used after this call. +#[unsafe(no_mangle)] +pub extern "C" fn opendal_presigned_request_result_release( + mut result: OpendalPresignedRequestResult, +) { + if !result.ptr.is_null() { + unsafe { + presigned_request_free(result.ptr.cast()); + } + } + + release_error_message(&mut result.error); +} diff --git a/bindings/dotnet/src/utils.rs b/bindings/dotnet/src/utils.rs new file mode 100644 index 000000000000..a236e9e13e19 --- /dev/null +++ b/bindings/dotnet/src/utils.rs @@ -0,0 +1,128 @@ +// 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. + +use std::os::raw::c_char; +use std::{collections::HashMap, ffi::CStr}; + +use crate::error::{ErrorCode, OpenDALError}; + +pub fn cstr_to_str<'a>(value: *const c_char) -> Option<&'a str> { + if value.is_null() { + return None; + } + + let cstr = unsafe { std::ffi::CStr::from_ptr(value) }; + cstr.to_str().ok() +} + +pub fn config_invalid_error(message: impl Into) -> OpenDALError { + OpenDALError::from_error(ErrorCode::ConfigInvalid, message.into()) +} + +pub fn invalid_utf8_message(field: &str) -> String { + format!("{field} is null or invalid UTF-8") +} + +pub fn invalid_utf8_message_at(field: &str, index: usize) -> String { + format!("{field} at index {index} is null or invalid UTF-8") +} + +pub fn require_cstr<'a>(value: *const c_char, field: &str) -> Result<&'a str, OpenDALError> { + cstr_to_str(value).ok_or_else(|| { + OpenDALError::from_error(ErrorCode::ConfigInvalid, invalid_utf8_message(field)) + }) +} + +pub fn require_operator<'a>( + op: *const opendal::Operator, +) -> Result<&'a opendal::Operator, OpenDALError> { + if op.is_null() { + return Err(config_invalid_error("operator pointer is null")); + } + + Ok(unsafe { &*op }) +} + +pub fn require_callback(callback: Option) -> Result { + callback.ok_or_else(|| config_invalid_error("callback pointer is null")) +} + +pub fn require_data_ptr(data: *const u8, len: usize) -> Result<(), OpenDALError> { + if len > 0 && data.is_null() { + return Err(config_invalid_error("data pointer is null while len > 0")); + } + + Ok(()) +} + +/// # Safety +/// +/// - When `len > 0`, `keys` and `values` must be non-null pointers to arrays +/// containing at least `len` C-string pointers. +/// - Each entry pointer must be non-null and valid UTF-8. +pub unsafe fn collect_options( + keys: *const *const c_char, + values: *const *const c_char, + len: usize, +) -> Result, OpenDALError> { + if len == 0 { + return Ok(HashMap::new()); + } + + if keys.is_null() { + return Err(config_invalid_error("keys pointer is null while len > 0")); + } + + if values.is_null() { + return Err(config_invalid_error("values pointer is null while len > 0")); + } + + let mut map = HashMap::with_capacity(len); + for index in 0..len { + let key_ptr = unsafe { *keys.add(index) }; + let value_ptr = unsafe { *values.add(index) }; + + if key_ptr.is_null() { + return Err(config_invalid_error(invalid_utf8_message_at("key", index))); + } + if value_ptr.is_null() { + return Err(config_invalid_error(invalid_utf8_message_at( + "value", index, + ))); + } + + let key = unsafe { CStr::from_ptr(key_ptr) } + .to_str() + .map_err(|_| config_invalid_error(invalid_utf8_message_at("key", index)))?; + let value = unsafe { CStr::from_ptr(value_ptr) } + .to_str() + .map_err(|_| config_invalid_error(invalid_utf8_message_at("value", index)))?; + + map.insert(key.to_string(), value.to_string()); + } + + Ok(map) +} + +pub fn into_string_ptr(message: impl Into) -> *mut c_char { + match std::ffi::CString::new(message.into()) { + Ok(msg) => msg.into_raw(), + Err(_) => std::ffi::CString::new("invalid error message") + .unwrap() + .into_raw(), + } +} diff --git a/bindings/dotnet/src/validators.rs b/bindings/dotnet/src/validators.rs new file mode 100644 index 000000000000..52a71f72dc60 --- /dev/null +++ b/bindings/dotnet/src/validators.rs @@ -0,0 +1,71 @@ +// 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 mod layer; +pub mod options; + +use crate::error::OpenDALError; +use crate::utils::config_invalid_error; + +pub(crate) fn validate_non_zero_u64(value: u64, field: &str) -> Result<(), OpenDALError> { + if value == 0 { + return Err(config_invalid_error(format!( + "{field} must be greater than zero" + ))); + } + + Ok(()) +} + +pub(crate) fn validate_non_zero_usize(value: usize, field: &str) -> Result<(), OpenDALError> { + if value == 0 { + return Err(config_invalid_error(format!( + "{field} must be greater than zero" + ))); + } + + Ok(()) +} + +pub(crate) fn validate_positive_finite_f32(value: f32, field: &str) -> Result<(), OpenDALError> { + if !value.is_finite() || value <= 0.0 { + return Err(config_invalid_error(format!( + "{field} must be a positive finite number" + ))); + } + + Ok(()) +} + +pub(crate) fn validate_checked_add_u64( + left: u64, + right: u64, + context: &str, +) -> Result { + left.checked_add(right) + .ok_or_else(|| config_invalid_error(format!("{context} overflow"))) +} + +pub(crate) mod prelude { + pub(crate) use super::layer::{ + validate_concurrent_limit_options, validate_retry_options, validate_timeout_options, + }; + pub(crate) use super::options::{ + validate_list_limit, validate_read_chunk, validate_read_concurrent, validate_read_gap, + validate_read_range_end, validate_write_chunk, validate_write_concurrent, + }; +} diff --git a/bindings/dotnet/src/validators/layer.rs b/bindings/dotnet/src/validators/layer.rs new file mode 100644 index 000000000000..7424fef8ac34 --- /dev/null +++ b/bindings/dotnet/src/validators/layer.rs @@ -0,0 +1,57 @@ +// 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. + +use crate::error::OpenDALError; +use crate::utils::config_invalid_error; +use crate::validators::{ + validate_non_zero_u64, validate_non_zero_usize, validate_positive_finite_f32, +}; + +/// Validate retry layer options. +pub fn validate_retry_options( + factor: f32, + min_delay_nanos: u64, + max_delay_nanos: u64, +) -> Result<(), OpenDALError> { + validate_positive_finite_f32(factor, "retry factor")?; + + if max_delay_nanos < min_delay_nanos { + return Err(config_invalid_error( + "max_delay_nanos must be greater than or equal to min_delay_nanos", + )); + } + + Ok(()) +} + +/// Validate timeout layer options. +pub fn validate_timeout_options( + timeout_nanos: u64, + io_timeout_nanos: u64, +) -> Result<(), OpenDALError> { + validate_non_zero_u64(timeout_nanos, "timeout_nanos")?; + validate_non_zero_u64(io_timeout_nanos, "io_timeout_nanos")?; + + Ok(()) +} + +/// Validate concurrent-limit layer options. +pub fn validate_concurrent_limit_options(permits: usize) -> Result<(), OpenDALError> { + validate_non_zero_usize(permits, "permits")?; + + Ok(()) +} diff --git a/bindings/dotnet/src/validators/options.rs b/bindings/dotnet/src/validators/options.rs new file mode 100644 index 000000000000..2abbf62ed62e --- /dev/null +++ b/bindings/dotnet/src/validators/options.rs @@ -0,0 +1,58 @@ +// 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. + +//! Validators for operation option payloads. + +use crate::error::OpenDALError; +use crate::validators::{validate_checked_add_u64, validate_non_zero_usize}; + +pub fn validate_read_range_end( + offset: u64, + length: Option, +) -> Result, OpenDALError> { + let Some(size) = length else { + return Ok(None); + }; + + let end = validate_checked_add_u64(offset, size, "offset + length in read options")?; + + Ok(Some(end)) +} + +pub fn validate_read_concurrent(concurrent: usize) -> Result<(), OpenDALError> { + validate_non_zero_usize(concurrent, "read concurrent") +} + +pub fn validate_read_chunk(chunk: usize) -> Result<(), OpenDALError> { + validate_non_zero_usize(chunk, "read chunk") +} + +pub fn validate_read_gap(gap: usize) -> Result<(), OpenDALError> { + validate_non_zero_usize(gap, "read gap") +} + +pub fn validate_write_concurrent(concurrent: usize) -> Result<(), OpenDALError> { + validate_non_zero_usize(concurrent, "write concurrent") +} + +pub fn validate_write_chunk(chunk: usize) -> Result<(), OpenDALError> { + validate_non_zero_usize(chunk, "write chunk") +} + +pub fn validate_list_limit(limit: usize) -> Result<(), OpenDALError> { + validate_non_zero_usize(limit, "list limit") +}