From b7c65e73ef9435986c7b2f765b440ede34d48bdd Mon Sep 17 00:00:00 2001 From: Almaju Date: Wed, 31 Jan 2024 13:10:43 +0100 Subject: [PATCH] iOS TodoList --- .gitignore | 4 + Cargo.lock | 1 - application/Cargo.toml | 1 - application/src/command.rs | 4 +- application/src/lib.rs | 2 - application/src/projection.rs | 8 +- application/src/query.rs | 1 + clients/mobile/build-ios.sh | 11 +- .../ios/TodoList.xcodeproj/project.pbxproj | 8 +- .../UserInterfaceState.xcuserstate | Bin 26481 -> 34231 bytes clients/mobile/ios/TodoList/ContentView.swift | 103 ++++-- clients/mobile/ios/TodoList/Mobile.swift | 317 +++++++++++++++++- clients/mobile/src/client.rs | 54 +++ clients/mobile/src/dto.rs | 65 ++++ clients/mobile/src/lib.rs | 35 +- domain/src/todolist_scalar.rs | 6 +- 16 files changed, 515 insertions(+), 105 deletions(-) create mode 100644 clients/mobile/src/client.rs create mode 100644 clients/mobile/src/dto.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..2963b92 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ +# Rust /target + +# IDE +.vscode diff --git a/Cargo.lock b/Cargo.lock index ca68da8..4f96ff3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,7 +84,6 @@ dependencies = [ "domain", "framework", "serde", - "uniffi", ] [[package]] diff --git a/application/Cargo.toml b/application/Cargo.toml index 307dc90..3e9eb2d 100644 --- a/application/Cargo.toml +++ b/application/Cargo.toml @@ -9,4 +9,3 @@ edition = "2021" domain = { path = "../domain" } framework = { path = "../crates/framework" } serde = { version = "1.0", features = ["derive"] } -uniffi = { version = "0.25.3" } diff --git a/application/src/command.rs b/application/src/command.rs index eb868f8..82b1ee0 100644 --- a/application/src/command.rs +++ b/application/src/command.rs @@ -6,8 +6,8 @@ use serde::Deserialize; #[derive(Deserialize)] pub enum Command { AddTask { name: String }, - RemoveTask { index: u32 }, - CompleteTask { index: u32 }, + RemoveTask { index: usize }, + CompleteTask { index: usize }, } #[derive(Debug, Error)] diff --git a/application/src/lib.rs b/application/src/lib.rs index ebfe2d1..d0a1cd6 100644 --- a/application/src/lib.rs +++ b/application/src/lib.rs @@ -3,5 +3,3 @@ pub mod port; pub mod projection; pub mod query; pub mod snapshot; - -uniffi::setup_scaffolding!(); diff --git a/application/src/projection.rs b/application/src/projection.rs index 93dfc7a..93b97a8 100644 --- a/application/src/projection.rs +++ b/application/src/projection.rs @@ -2,19 +2,19 @@ use domain::todolist_event::TodoListEvent; use framework::*; use serde::{Deserialize, Serialize}; -#[derive(Clone, Default, Debug, Serialize, Deserialize, uniffi::Record)] +#[derive(Clone, Default, Debug, Serialize, Deserialize)] pub struct TodoList { pub tasks: Vec, } -#[derive(Clone, Debug, Serialize, Deserialize, uniffi::Record)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Task { - pub index: u32, + pub index: usize, pub name: String, pub status: TaskStatus, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, uniffi::Enum)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum TaskStatus { Created, Completed, diff --git a/application/src/query.rs b/application/src/query.rs index 01deacf..6297d1b 100644 --- a/application/src/query.rs +++ b/application/src/query.rs @@ -2,6 +2,7 @@ use crate::{port::TodoListRepository, projection::TodoList}; use framework::*; use serde::Deserialize; +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] #[derive(Deserialize)] pub struct GetTodoListQuery {} diff --git a/clients/mobile/build-ios.sh b/clients/mobile/build-ios.sh index 6c0a4cf..072610e 100755 --- a/clients/mobile/build-ios.sh +++ b/clients/mobile/build-ios.sh @@ -8,8 +8,8 @@ cargo run --bin uniffi-bindgen generate --library ../../target/debug/libmobile.d # Add the ios targets and build for TARGET in \ - aarch64-apple-ios \ - aarch64-apple-ios-sim + aarch64-apple-ios \ + aarch64-apple-ios-sim do rustup target add $TARGET # Apple's App Sandbox disallows SysV semaphores; use POSIX semaphores instead @@ -19,6 +19,7 @@ done # Rename *.modulemap to module.modulemap mv ./bindings/mobileFFI.modulemap ./bindings/module.modulemap + # Move the Swift file to the project rm ./ios/TodoList/Mobile.swift mv ./bindings/mobile.swift ./ios/TodoList/Mobile.swift @@ -26,9 +27,9 @@ mv ./bindings/mobile.swift ./ios/TodoList/Mobile.swift # # Recreate XCFramework rm -rf "ios/Mobile.xcframework" xcodebuild -create-xcframework \ - -library ../../target/aarch64-apple-ios-sim/release/libmobile.a -headers ./bindings \ - -library ../../target/aarch64-apple-ios/release/libmobile.a -headers ./bindings \ - -output "ios/Mobile.xcframework" + -library ../../target/aarch64-apple-ios-sim/release/libmobile.a -headers ./bindings \ + -library ../../target/aarch64-apple-ios/release/libmobile.a -headers ./bindings \ + -output "ios/Mobile.xcframework" # # Cleanup rm -rf bindings diff --git a/clients/mobile/ios/TodoList.xcodeproj/project.pbxproj b/clients/mobile/ios/TodoList.xcodeproj/project.pbxproj index 81f40de..8f21ac7 100644 --- a/clients/mobile/ios/TodoList.xcodeproj/project.pbxproj +++ b/clients/mobile/ios/TodoList.xcodeproj/project.pbxproj @@ -7,13 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 0CB5ABC72B6A507100E96E34 /* Mobile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB5ABC62B6A507100E96E34 /* Mobile.swift */; }; 0CE410A42B59CAE400FD01AF /* TodoListApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE410A32B59CAE400FD01AF /* TodoListApp.swift */; }; 0CE410A62B59CAE400FD01AF /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE410A52B59CAE400FD01AF /* ContentView.swift */; }; 0CE410A82B59CAE500FD01AF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CE410A72B59CAE500FD01AF /* Assets.xcassets */; }; 0CE410AB2B59CAE600FD01AF /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CE410AA2B59CAE600FD01AF /* Preview Assets.xcassets */; }; 0CE410B32B59CB1D00FD01AF /* Mobile.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CE410B12B59CB0B00FD01AF /* Mobile.xcframework */; }; 0CE410B42B59CB1D00FD01AF /* Mobile.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0CE410B12B59CB0B00FD01AF /* Mobile.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 0CE410B72B59CDB600FD01AF /* Mobile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE410B62B59CDB600FD01AF /* Mobile.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -31,13 +31,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0CB5ABC62B6A507100E96E34 /* Mobile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mobile.swift; sourceTree = ""; }; 0CE410A02B59CAE400FD01AF /* TodoList.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TodoList.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0CE410A32B59CAE400FD01AF /* TodoListApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoListApp.swift; sourceTree = ""; }; 0CE410A52B59CAE400FD01AF /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 0CE410A72B59CAE500FD01AF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0CE410AA2B59CAE600FD01AF /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 0CE410B12B59CB0B00FD01AF /* Mobile.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Mobile.xcframework; sourceTree = SOURCE_ROOT; }; - 0CE410B62B59CDB600FD01AF /* Mobile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mobile.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -71,9 +71,9 @@ 0CE410A22B59CAE400FD01AF /* TodoList */ = { isa = PBXGroup; children = ( + 0CB5ABC62B6A507100E96E34 /* Mobile.swift */, 0CE410B12B59CB0B00FD01AF /* Mobile.xcframework */, 0CE410A32B59CAE400FD01AF /* TodoListApp.swift */, - 0CE410B62B59CDB600FD01AF /* Mobile.swift */, 0CE410A52B59CAE400FD01AF /* ContentView.swift */, 0CE410A72B59CAE500FD01AF /* Assets.xcassets */, 0CE410A92B59CAE600FD01AF /* Preview Content */, @@ -160,7 +160,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0CE410B72B59CDB600FD01AF /* Mobile.swift in Sources */, + 0CB5ABC72B6A507100E96E34 /* Mobile.swift in Sources */, 0CE410A62B59CAE400FD01AF /* ContentView.swift in Sources */, 0CE410A42B59CAE400FD01AF /* TodoListApp.swift in Sources */, ); diff --git a/clients/mobile/ios/TodoList.xcodeproj/project.xcworkspace/xcuserdata/alexandrehanot.xcuserdatad/UserInterfaceState.xcuserstate b/clients/mobile/ios/TodoList.xcodeproj/project.xcworkspace/xcuserdata/alexandrehanot.xcuserdatad/UserInterfaceState.xcuserstate index ad6db578fceaa97d7459e5f47b0d547403f2584d..708c5617517041ad776355f697b3a6ca7eb6d11d 100644 GIT binary patch literal 34231 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 UInt32 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + fileprivate struct FfiConverterString: FfiConverter { typealias SwiftType = String typealias FfiType = RustBuffer @@ -337,8 +350,10 @@ fileprivate struct FfiConverterString: FfiConverter { public protocol ClientProtocol { - func addTask(name: String) async - func getTodolist() async -> String + func addTask(name: String) async throws + func completeTask(index: UInt32) async throws + func getTodoList() async throws -> TodoListDto + func removeTask(index: UInt32) async throws } @@ -366,8 +381,8 @@ public class Client: ClientProtocol { - public func addTask(name: String) async { - return try! await uniffiRustCallAsync( + public func addTask(name: String) async throws { + return try await uniffiRustCallAsync( rustFutureFunc: { uniffi_mobile_fn_method_client_add_task( self.pointer, @@ -378,26 +393,60 @@ public class Client: ClientProtocol { completeFunc: ffi_mobile_rust_future_complete_void, freeFunc: ffi_mobile_rust_future_free_void, liftFunc: { $0 }, - errorHandler: nil - + errorHandler: FfiConverterTypeErrorDTO.lift + ) + } + + + + public func completeTask(index: UInt32) async throws { + return try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_mobile_fn_method_client_complete_task( + self.pointer, + FfiConverterUInt32.lower(index) + ) + }, + pollFunc: ffi_mobile_rust_future_poll_void, + completeFunc: ffi_mobile_rust_future_complete_void, + freeFunc: ffi_mobile_rust_future_free_void, + liftFunc: { $0 }, + errorHandler: FfiConverterTypeErrorDTO.lift ) } - public func getTodolist() async -> String { - return try! await uniffiRustCallAsync( + public func getTodoList() async throws -> TodoListDto { + return try await uniffiRustCallAsync( rustFutureFunc: { - uniffi_mobile_fn_method_client_get_todolist( + uniffi_mobile_fn_method_client_get_todo_list( self.pointer ) }, pollFunc: ffi_mobile_rust_future_poll_rust_buffer, completeFunc: ffi_mobile_rust_future_complete_rust_buffer, freeFunc: ffi_mobile_rust_future_free_rust_buffer, - liftFunc: FfiConverterString.lift, - errorHandler: nil - + liftFunc: FfiConverterTypeTodoListDTO.lift, + errorHandler: FfiConverterTypeErrorDTO.lift + ) + } + + + + public func removeTask(index: UInt32) async throws { + return try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_mobile_fn_method_client_remove_task( + self.pointer, + FfiConverterUInt32.lower(index) + ) + }, + pollFunc: ffi_mobile_rust_future_poll_void, + completeFunc: ffi_mobile_rust_future_complete_void, + freeFunc: ffi_mobile_rust_future_free_void, + liftFunc: { $0 }, + errorHandler: FfiConverterTypeErrorDTO.lift ) } @@ -442,6 +491,240 @@ public func FfiConverterTypeClient_lift(_ pointer: UnsafeMutableRawPointer) thro public func FfiConverterTypeClient_lower(_ value: Client) -> UnsafeMutableRawPointer { return FfiConverterTypeClient.lower(value) } + + +public struct TaskDto { + public var index: UInt32 + public var name: String + public var status: TaskStatusDto + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(index: UInt32, name: String, status: TaskStatusDto) { + self.index = index + self.name = name + self.status = status + } +} + + +extension TaskDto: Equatable, Hashable { + public static func ==(lhs: TaskDto, rhs: TaskDto) -> Bool { + if lhs.index != rhs.index { + return false + } + if lhs.name != rhs.name { + return false + } + if lhs.status != rhs.status { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(index) + hasher.combine(name) + hasher.combine(status) + } +} + + +public struct FfiConverterTypeTaskDTO: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TaskDto { + return try TaskDto( + index: FfiConverterUInt32.read(from: &buf), + name: FfiConverterString.read(from: &buf), + status: FfiConverterTypeTaskStatusDTO.read(from: &buf) + ) + } + + public static func write(_ value: TaskDto, into buf: inout [UInt8]) { + FfiConverterUInt32.write(value.index, into: &buf) + FfiConverterString.write(value.name, into: &buf) + FfiConverterTypeTaskStatusDTO.write(value.status, into: &buf) + } +} + + +public func FfiConverterTypeTaskDTO_lift(_ buf: RustBuffer) throws -> TaskDto { + return try FfiConverterTypeTaskDTO.lift(buf) +} + +public func FfiConverterTypeTaskDTO_lower(_ value: TaskDto) -> RustBuffer { + return FfiConverterTypeTaskDTO.lower(value) +} + + +public struct TodoListDto { + public var tasks: [TaskDto] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(tasks: [TaskDto]) { + self.tasks = tasks + } +} + + +extension TodoListDto: Equatable, Hashable { + public static func ==(lhs: TodoListDto, rhs: TodoListDto) -> Bool { + if lhs.tasks != rhs.tasks { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(tasks) + } +} + + +public struct FfiConverterTypeTodoListDTO: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TodoListDto { + return try TodoListDto( + tasks: FfiConverterSequenceTypeTaskDTO.read(from: &buf) + ) + } + + public static func write(_ value: TodoListDto, into buf: inout [UInt8]) { + FfiConverterSequenceTypeTaskDTO.write(value.tasks, into: &buf) + } +} + + +public func FfiConverterTypeTodoListDTO_lift(_ buf: RustBuffer) throws -> TodoListDto { + return try FfiConverterTypeTodoListDTO.lift(buf) +} + +public func FfiConverterTypeTodoListDTO_lower(_ value: TodoListDto) -> RustBuffer { + return FfiConverterTypeTodoListDTO.lower(value) +} + +public enum ErrorDto { + + + + case Error(message: String) + + fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error { + return try FfiConverterTypeErrorDTO.lift(error) + } +} + + +public struct FfiConverterTypeErrorDTO: FfiConverterRustBuffer { + typealias SwiftType = ErrorDto + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ErrorDto { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .Error( + message: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: ErrorDto, into buf: inout [UInt8]) { + switch value { + + + + + + case let .Error(message): + writeInt(&buf, Int32(1)) + FfiConverterString.write(message, into: &buf) + + } + } +} + + +extension ErrorDto: Equatable, Hashable {} + +extension ErrorDto: Error { } + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum TaskStatusDto { + + case created + case completed +} + +public struct FfiConverterTypeTaskStatusDTO: FfiConverterRustBuffer { + typealias SwiftType = TaskStatusDto + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TaskStatusDto { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .created + + case 2: return .completed + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: TaskStatusDto, into buf: inout [UInt8]) { + switch value { + + + case .created: + writeInt(&buf, Int32(1)) + + + case .completed: + writeInt(&buf, Int32(2)) + + } + } +} + + +public func FfiConverterTypeTaskStatusDTO_lift(_ buf: RustBuffer) throws -> TaskStatusDto { + return try FfiConverterTypeTaskStatusDTO.lift(buf) +} + +public func FfiConverterTypeTaskStatusDTO_lower(_ value: TaskStatusDto) -> RustBuffer { + return FfiConverterTypeTaskStatusDTO.lower(value) +} + + +extension TaskStatusDto: Equatable, Hashable {} + + + +fileprivate struct FfiConverterSequenceTypeTaskDTO: FfiConverterRustBuffer { + typealias SwiftType = [TaskDto] + + public static func write(_ value: [TaskDto], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeTaskDTO.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [TaskDto] { + let len: Int32 = try readInt(&buf) + var seq = [TaskDto]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeTaskDTO.read(from: &buf)) + } + return seq + } +} private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0 private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 @@ -520,10 +803,16 @@ private var initializationResult: InitializationResult { if bindings_contract_version != scaffolding_contract_version { return InitializationResult.contractVersionMismatch } - if (uniffi_mobile_checksum_method_client_add_task() != 3747) { + if (uniffi_mobile_checksum_method_client_add_task() != 11478) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_checksum_method_client_complete_task() != 8176) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_checksum_method_client_get_todo_list() != 36812) { return InitializationResult.apiChecksumMismatch } - if (uniffi_mobile_checksum_method_client_get_todolist() != 64446) { + if (uniffi_mobile_checksum_method_client_remove_task() != 23859) { return InitializationResult.apiChecksumMismatch } if (uniffi_mobile_checksum_constructor_client_new() != 1538) { diff --git a/clients/mobile/src/client.rs b/clients/mobile/src/client.rs new file mode 100644 index 0000000..2d58cbb --- /dev/null +++ b/clients/mobile/src/client.rs @@ -0,0 +1,54 @@ +use crate::dto::ErrorDTO; +use crate::{dto::TodoListDTO, runtime::Runtime}; +use application::command::Command; +use application::query::GetTodoListQuery; +use framework::*; +use std::sync::Arc; + +#[derive(uniffi::Object)] +pub struct Client { + runtime: Runtime, +} + +#[uniffi::export] +impl Client { + #[uniffi::constructor] + pub fn new() -> Arc { + Arc::new(Self { + runtime: Runtime::default(), + }) + } + + pub async fn add_task(&self, name: String) -> Result<(), ErrorDTO> { + Command::AddTask { name } + .execute(&self.runtime) + .await + .map_err(ErrorDTO::from) + } + + pub async fn remove_task(&self, index: u32) -> Result<(), ErrorDTO> { + Command::RemoveTask { + index: index as usize, + } + .execute(&self.runtime) + .await + .map_err(ErrorDTO::from) + } + + pub async fn complete_task(&self, index: u32) -> Result<(), ErrorDTO> { + Command::CompleteTask { + index: index as usize, + } + .execute(&self.runtime) + .await + .map_err(ErrorDTO::from) + } + + pub async fn get_todo_list(&self) -> Result { + GetTodoListQuery {} + .execute(&self.runtime) + .await + .map_err(ErrorDTO::from) + .map(TodoListDTO::from) + } +} diff --git a/clients/mobile/src/dto.rs b/clients/mobile/src/dto.rs new file mode 100644 index 0000000..80dbfdf --- /dev/null +++ b/clients/mobile/src/dto.rs @@ -0,0 +1,65 @@ +use application::projection::*; +use framework::*; + +#[derive(uniffi::Record)] +pub struct TodoListDTO { + pub tasks: Vec, +} + +impl From for TodoListDTO { + fn from(todo_list: TodoList) -> Self { + Self { + tasks: todo_list + .tasks + .into_iter() + .map(|task| task.into()) + .collect(), + } + } +} + +#[derive(uniffi::Record)] +pub struct TaskDTO { + pub index: u32, + pub name: String, + pub status: TaskStatusDTO, +} + +impl From for TaskDTO { + fn from(task: Task) -> Self { + Self { + index: task.index as u32, + name: task.name, + status: task.status.into(), + } + } +} + +#[derive(uniffi::Enum)] +pub enum TaskStatusDTO { + Created, + Completed, +} + +impl From for TaskStatusDTO { + fn from(task_status: TaskStatus) -> Self { + match task_status { + TaskStatus::Created => Self::Created, + TaskStatus::Completed => Self::Completed, + } + } +} + +#[derive(Debug, Error, uniffi::Error)] +pub enum ErrorDTO { + #[error("{message}")] + Error { message: String }, +} + +impl From for ErrorDTO { + fn from(error: framework::AnyError) -> Self { + Self::Error { + message: error.to_string(), + } + } +} diff --git a/clients/mobile/src/lib.rs b/clients/mobile/src/lib.rs index 81e81c2..37b3a41 100644 --- a/clients/mobile/src/lib.rs +++ b/clients/mobile/src/lib.rs @@ -1,36 +1,5 @@ -use application::command::Command; -use application::projection::TodoList; -use application::query::GetTodoListQuery; -use framework::*; -use std::sync::Arc; - +mod client; +mod dto; mod runtime; uniffi::setup_scaffolding!(); - -#[derive(uniffi::Object)] -pub struct Client { - runtime: runtime::Runtime, -} - -#[uniffi::export] -impl Client { - #[uniffi::constructor] - pub fn new() -> Arc { - Arc::new(Self { - runtime: runtime::Runtime::default(), - }) - } - - pub async fn add_task(&self, name: String) { - Command::AddTask { name } - .execute(&self.runtime) - .await - .unwrap(); - } - - pub async fn get_todolist(&self) -> TodoList { - GetTodoListQuery {}.execute(&self.runtime).await.unwrap(); - todo!() - } -} diff --git a/domain/src/todolist_scalar.rs b/domain/src/todolist_scalar.rs index a96dfef..8a3f65d 100644 --- a/domain/src/todolist_scalar.rs +++ b/domain/src/todolist_scalar.rs @@ -2,10 +2,10 @@ use framework::*; use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] -pub struct TaskIndex(pub u32); +pub struct TaskIndex(pub usize); -impl From<&u32> for TaskIndex { - fn from(value: &u32) -> Self { +impl From<&usize> for TaskIndex { + fn from(value: &usize) -> Self { Self(*value) } }