From 37fd0f8ce85ff76fb1961d73d58fac3d4bf170a1 Mon Sep 17 00:00:00 2001 From: yaochenfeng <282696845@qq.com> Date: Wed, 18 Jun 2025 01:24:36 +0800 Subject: [PATCH 1/3] feat: add JSONValue --- Sources/DFService/JSONValue.swift | 213 +++++++++++ ...rviceStateType.swift => DFStateType.swift} | 2 +- Tests/DFServiceTests/JSONValueTests.swift | 362 ++++++++++++++++++ 3 files changed, 576 insertions(+), 1 deletion(-) create mode 100644 Sources/DFService/JSONValue.swift rename Sources/DFService/types/{ServiceStateType.swift => DFStateType.swift} (94%) create mode 100644 Tests/DFServiceTests/JSONValueTests.swift diff --git a/Sources/DFService/JSONValue.swift b/Sources/DFService/JSONValue.swift new file mode 100644 index 0000000..2ef39dd --- /dev/null +++ b/Sources/DFService/JSONValue.swift @@ -0,0 +1,213 @@ +import Foundation + +public enum JSONValue: Codable, Hashable { + public static var jsonEncode = JSONEncoder() + public static var jsonDecoder = JSONDecoder() + case string(String) + case int(Int) + case double(Double) + case bool(Bool) + case list([JSONValue]) + case map([String: JSONValue]) + case data(Data) + case null + + // MARK: - Init from Any + public init?(obj: Any) { + switch obj { + case let value as String: + self = .string(value) + case let value as Bool: + self = .bool(value) + case let value as Int: + self = .int(value) + case let value as Double: + self = .double(value) + + case let value as Data: + self = .data(value) + case let value as [Any]: + self = .list(value.compactMap { JSONValue(obj: $0) }) + case let value as [String: Any]: + var object = [String: JSONValue]() + // map 根据key 排序 + for (key, val) in value { + guard let jv = JSONValue(obj: val) else { return nil } + object[key] = jv + } + self = .map(object) + case let value as Encodable: + do { + let data = try JSONValue.jsonEncode.encode(value) + if let jsonObject = try? JSONDecoder().decode(JSONValue.self, from: data) { + self = jsonObject + } else { + self = .data(data) + } + + } catch { + return nil + } + case _ as NSNull: + self = .null + default: + return nil + } + } + + // MARK: - To Any + public func toAny() -> Any { + switch self { + case .string(let value): return value + case .int(let value): return value + case .double(let value): return value + case .bool(let value): return value + case .list(let array): return array.map { $0.toAny() } + case .map(let dict): return dict.mapValues { $0.toAny() } + case .data(let data): return data.base64EncodedString() + case .null: return NSNull() + } + } + + // MARK: - Encode + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .string(let value): try container.encode(value) + case .bool(let value): try container.encode(value) + case .int(let value): try container.encode(value) + case .double(let value): try container.encode(value) + case .list(let value): try container.encode(value) + case .map(let value): try container.encode(value) + case .data(let value): try container.encode(value.base64EncodedString()) + case .null: try container.encodeNil() + } + } + + // MARK: - Decode + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if container.decodeNil() { + self = .null + } else if let value = try? container.decode(Int.self) { + self = .int(value) + } else if let value = try? container.decode(Double.self) { + self = .double(value) + } else if let value = try? container.decode(Bool.self) { + self = .bool(value) + } else if let value = try? container.decode(String.self) { + if let data = Data(base64Encoded: value), !data.isEmpty { + self = .data(data) + } else { + self = .string(value) + } + } else if let value = try? container.decode([JSONValue].self) { + self = .list(value) + } else if let value = try? container.decode([String: JSONValue].self) { + self = .map(value) + } else { + throw DecodingError.dataCorruptedError( + in: container, debugDescription: "Unknown JSONValue") + } + } + + // MARK: - Path Access + public func value(at path: [String]) -> JSONValue? { + guard !path.isEmpty else { return self } + if case .map(let dict) = self { + guard let first = path.first, let next = dict[first] else { return nil } + return next.value(at: Array(path.dropFirst())) + } else if case .list(let array) = self { + guard let first = path.first, let index = Int(first), array.indices.contains(index) + else { return nil } + return array[index].value(at: Array(path.dropFirst())) + } + return nil + } + + public func value(at path: String) -> JSONValue? { + return value(at: path.components(separatedBy: ".")) + } + + // MARK: - Get + public func optional(_ type: T.Type = T.self) -> T? { + return try? to() + } + + public func to(type: T.Type = T.self) throws -> T { + if let value = self as? T { + return value + } + let value = toAny() + if let obj: T = value as? T { + return obj + } + throw ServiceError.castError(from: value, to: T.Type.self) + } + + // MARK: - Decode to Model + public func decode(_ type: T.Type) throws -> T { + if let value: T = optional() { + return value + } else if case .data(let data) = self { + return try Self.jsonDecoder.decode(T.self, from: data) + } else if case .string(let value) = self { + guard let data = value.data(using: .utf8) else { + throw NSError( + domain: "JSONValue", code: 1, + userInfo: [NSLocalizedDescriptionKey: "Invalid string encoding"]) + } + return try Self.jsonDecoder.decode(T.self, from: data) + } + let obj = self.toAny() + let data = try JSONSerialization.data(withJSONObject: obj, options: []) + return try Self.jsonDecoder.decode(T.self, from: data) + } + + // MARK: - Init from Model + public init(from model: T) throws { + let data = try Self.jsonEncode.encode(model) + let any = try JSONSerialization.jsonObject(with: data) + guard let json = JSONValue(obj: any) else { + throw NSError( + domain: "JSONValue", code: 1, + userInfo: [NSLocalizedDescriptionKey: "Cannot convert model to JSONValue"]) + } + self = json + } +} + +extension JSONValue: Equatable {} + +extension JSONValue: ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, Any)...) { + var dict = [String: JSONValue]() + for (key, value) in elements { + dict[key] = JSONValue(obj: value) + } + self = .map(dict) + } +} +extension JSONValue: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: Any...) { + self = .list(elements.compactMap { JSONValue(obj: $0) }) + } +} + +extension JSONValue: CustomStringConvertible { + public var description: String { + switch self { + case .string(let value): return "\"\(value)\"" + case .int(let value): return "\(value)" + case .double(let value): return "\(value)" + case .bool(let value): return "\(value)" + case .list(let array): + return "[" + array.map { $0.description }.joined(separator: ", ") + "]" + case .map(let dict): + let items = dict.map { "\"\($0)\": \($1.description)" }.sorted().joined(separator: ", ") + return "{\(items)}" + case .data(let data): return "\"\(data.base64EncodedString())\"" + case .null: return "null" + } + } +} diff --git a/Sources/DFService/types/ServiceStateType.swift b/Sources/DFService/types/DFStateType.swift similarity index 94% rename from Sources/DFService/types/ServiceStateType.swift rename to Sources/DFService/types/DFStateType.swift index 3451b49..8bf69c8 100644 --- a/Sources/DFService/types/ServiceStateType.swift +++ b/Sources/DFService/types/DFStateType.swift @@ -1,7 +1,7 @@ /// A protocol that defines the required interface for representing the state of a service. /// Types conforming to `ServiceStateType` should encapsulate all necessary information /// about the current state of a service, enabling state management and transitions. -public protocol ServiceStateType { +public protocol DFStateType { associatedtype Action typealias Reducer = (_ state: Self, _ action: Action) -> Self? diff --git a/Tests/DFServiceTests/JSONValueTests.swift b/Tests/DFServiceTests/JSONValueTests.swift new file mode 100644 index 0000000..defa955 --- /dev/null +++ b/Tests/DFServiceTests/JSONValueTests.swift @@ -0,0 +1,362 @@ +// +// JSONValueTests.swift +// DFService +// +// Created by yaochenfeng on 2025/6/17. +// + +import XCTest + +@testable import DFService + +final class JSONValueTests: XCTestCase { + func testInitFromPrimitiveTypes() { + XCTAssertEqual(JSONValue(obj: "hello"), .string("hello")) + XCTAssertEqual(JSONValue(obj: 42), .int(42)) + XCTAssertEqual(JSONValue(obj: 3.14), .double(3.14)) + XCTAssertEqual(JSONValue(obj: true), .bool(true)) + XCTAssertEqual(JSONValue(obj: NSNull()), .null) + } + + func testInitFromArray() { + let arr: [Any] = ["a", 1, false] + let json = JSONValue(obj: arr) + XCTAssertEqual(json, .list([.string("a"), .int(1), .bool(false)])) + } + + func testInitFromDictionary() { + let dict: [String: Any] = ["a": 1, "b": "str", "c": false] + let json = JSONValue(obj: dict) + XCTAssertEqual( + json, + .map([ + "a": .int(1), + "b": .string("str"), + "c": .bool(false), + ])) + } + + func testToAny() { + let json: JSONValue = .map([ + "a": .int(1), + "b": .string("str"), + "c": .list([.bool(true), .null]), + ]) + let any = json.toAny() as? [String: Any] + XCTAssertEqual(any?["a"] as? Int, 1) + XCTAssertEqual(any?["b"] as? String, "str") + let arr = any?["c"] as? [Any] + XCTAssertEqual(arr?.first as? Bool, true) + } + + func testEncodeDecode() throws { + let original: JSONValue = .map([ + "int": .int(1), + "double": .double(2.5), + "string": .string("abc"), + "bool": .bool(false), + "array": .list([.int(1), .null]), + "null": .null, + ]) + let data = try JSONEncoder().encode(original) + let decoded = try JSONDecoder().decode(JSONValue.self, from: data) + XCTAssertEqual(original, decoded) + } + + func testDataEncodingDecoding() throws { + let raw = "hello".data(using: .utf8)! + let json = JSONValue.data(raw) + let data = try JSONEncoder().encode(json) + let decoded = try JSONDecoder().decode(JSONValue.self, from: data) + if case .data(let d) = decoded { + XCTAssertEqual(d, raw) + } else { + XCTFail("Expected .data case") + } + } + + func testValueAtPath() { + let json: JSONValue = .map([ + "a": .map([ + "b": .list([ + .map(["c": .string("value")]) + ]) + ]) + ]) + XCTAssertEqual(json.value(at: ["a", "b", "0", "c"]), .string("value")) + XCTAssertNil(json.value(at: ["a", "x"])) + } + + func testGetGeneric() { + XCTAssertEqual(JSONValue.int(5).optional(Int.self), 5) + XCTAssertEqual(JSONValue.string("abc").optional(String.self), "abc") + XCTAssertNil(JSONValue.null.optional(Int.self)) + } + + struct Dummy: Codable, Equatable { + let a: Int + let b: String + } + + func testDecodeToModel() throws { + let json: JSONValue = .map([ + "a": .int(10), + "b": .string("test"), + ]) + let model = try json.decode(Dummy.self) + XCTAssertEqual(model, Dummy(a: 10, b: "test")) + } + + func testInitFromModel() throws { + let model = Dummy(a: 7, b: "foo") + let json = try JSONValue(from: model) + XCTAssertEqual( + json, + .map([ + "a": .int(7), + "b": .string("foo"), + ])) + } + + func testEquatable() { + let json1: JSONValue = .map(["key": .int(1)]) + let json2: JSONValue = .map(["key": .int(1)]) + let json3: JSONValue = .map(["key": .int(2)]) + + XCTAssertEqual(json1, json2) + XCTAssertNotEqual(json1, json3) + XCTAssertNotEqual(json2, json3) + } + func testHashable() { + let json1: JSONValue = .map(["key": .int(1)]) + let json2: JSONValue = .map(["key": .int(1)]) + let json3: JSONValue = .map(["key": .int(2)]) + + let set: Set = [json1, json2, json3] + XCTAssertEqual(set.count, 2) // json1 and json2 are equal + } + + //测试模型转换 + func testModelConversion() throws { + _ = UserProfile(id: 123, name: "Alice", email: "sds") + } + func testNullEqualityAndHashing() { + XCTAssertEqual(JSONValue.null, JSONValue.null) + XCTAssertEqual(JSONValue.null.hashValue, JSONValue.null.hashValue) + } + + func testArrayWithNulls() { + let arr: [Any] = [NSNull(), 1, "a"] + let json = JSONValue(obj: arr) + XCTAssertEqual(json, .list([.null, .int(1), .string("a")])) + } + + func testObjectWithNestedArrayAndObject() { + let dict: [String: Any] = [ + "arr": [1, 2, 3], + "obj": ["x": "y"], + ] + let json = JSONValue(obj: dict) + XCTAssertEqual( + json, + .map([ + "arr": .list([.int(1), .int(2), .int(3)]), + "obj": .map(["x": .string("y")]), + ]) + ) + } + + func testGetWithWrongType() { + XCTAssertNil(JSONValue.int(1).optional(String.self)) + XCTAssertNil(JSONValue.string("abc").optional(Int.self)) + } + + func testDecodeInvalidModelThrows() { + let json: JSONValue = .string("{\"id\":1,name:\"test\",email:\"sd@qq.com\"}") + // Decoding a JSONValue that does not match the expected model should throw an error + XCTAssertThrowsError(try json.decode(UserProfile.self)) + } + + func testInitFromInvalidObjReturnsNil() { + class DummyClass {} + let dummy = DummyClass() + XCTAssertNil(JSONValue(obj: dummy)) + } + + func testDataCaseToAny() { + let data = "abc".data(using: .utf8)! + let json = JSONValue.data(data) + let any = json.toAny() + XCTAssertEqual(any as? String, data.base64EncodedString()) + } + + func testValueAtPathString() { + let json: JSONValue = .map([ + "a": .map([ + "b": .string("found") + ]) + ]) + XCTAssertEqual(json.value(at: "a.b"), .string("found")) + XCTAssertNil(json.value(at: "a.c")) + } + + func testDecodeDataFromBase64String() throws { + let data = "hello".data(using: .utf8)! + let base64 = data.base64EncodedString() + let jsonString = "\"\(base64)\"" + let decoded = try JSONDecoder().decode(JSONValue.self, from: jsonString.data(using: .utf8)!) + if case .data(let d) = decoded { + XCTAssertEqual(d, data) + } else { + XCTFail("Expected .data case") + } + } + + func testInitFromModelWithNestedStruct() throws { + struct Nested: Codable, Equatable { + let user: UserProfile + let active: Bool + } + let json1: JSONValue = ["a": 1, "b": 2] + let json2: JSONValue = ["b": 2, "a": 1] + XCTAssertEqual(json1, json2) + let nested = Nested(user: UserProfile(id: 1, name: "n", email: "e"), active: true) + let obj1 = JSONValue(obj: nested) + let obj2: JSONValue = .map([ + "user": .map([ + "id": .int(1), + "name": .string("n"), + "email": .string("e"), + ]), + "active": .bool(true), + ]) + let obj3: JSONValue = .map([ + "active": .bool(true), + "user": .map([ + "id": .int(1), + "name": .string("n"), + "email": .string("e"), + ]), + + ]) + XCTAssertEqual( + obj3, + obj2 + ) + XCTAssertEqual( + obj1, + obj2 + ) + } + + func testExpressibleByDictionaryLiteral() { + let json: JSONValue = ["foo": 1, "bar": "baz", "flag": true] + XCTAssertEqual( + json, + .map([ + "foo": .int(1), + "bar": .string("baz"), + "flag": .bool(true), + ]) + ) + } + + func testExpressibleByArrayLiteral() { + let json: JSONValue = [1, "a", false] + XCTAssertEqual(json, .list([.int(1), .string("a"), .bool(false)])) + } + + func testCustomStringConvertible() { + let json: JSONValue = .map([ + "num": .int(1), + "str": .string("abc"), + "arr": .list([.bool(true), .null]), + ]) + let desc = json.description + XCTAssertTrue(desc.contains("\"num\": 1")) + XCTAssertTrue(desc.contains("\"str\": \"abc\"")) + XCTAssertTrue(desc.contains("\"arr\": [true, null]")) + } + + func testNullDescription() { + XCTAssertEqual(JSONValue.null.description, "null") + } + + func testDecodeInvalidJSONThrows() { + let invalidData = "not a json".data(using: .utf8)! + XCTAssertThrowsError(try JSONDecoder().decode(JSONValue.self, from: invalidData)) + } + + func testInitFromEncodableFallbackToData() { + struct NotCodable {} + // NotCodable is not Encodable, so JSONValue(obj:) should return nil + XCTAssertNil(JSONValue(obj: NotCodable())) + } + + func testMapWithUnconvertibleValueReturnsNil() { + class DummyClass {} + let dict: [String: Any] = ["a": DummyClass()] + XCTAssertNil(JSONValue(obj: dict)) + } + + func testListWithUnconvertibleValueSkipsNil() { + class DummyClass {} + let arr: [Any] = [1, DummyClass(), 2] + let json = JSONValue(obj: arr) + // Only convertible values should be present + XCTAssertEqual(json, .list([.int(1), .int(2)])) + } + + func testValueAtPathWithArrayIndexOutOfBounds() { + let json: JSONValue = .list([.int(1)]) + XCTAssertNil(json.value(at: ["2"])) + } + + func testValueAtPathWithNonIntArrayIndex() { + let json: JSONValue = .list([.int(1)]) + XCTAssertNil(json.value(at: ["foo"])) + } + + func testGetWithArrayAndMap() { + let arr: JSONValue = .list([.int(1), .int(2)]) + let dict: JSONValue = .map(["a": .int(1)]) + XCTAssertEqual(arr.optional(JSONValue.self), [JSONValue.int(1), JSONValue.int(2)]) + XCTAssertEqual(dict.optional(JSONValue.self), ["a": JSONValue.int(1)]) + } + + func testDecodeStringAsModel() throws { + struct S: Codable, Equatable { let value: String } + let json: JSONValue = .string("{\"value\":\"abc\"}") + XCTAssertNoThrow(try json.decode(S.self)) + } + + func testEncodeDecodeNull() throws { + let json: JSONValue = .null + let data = try JSONEncoder().encode(json) + let decoded = try JSONDecoder().decode(JSONValue.self, from: data) + XCTAssertEqual(decoded, .null) + } + + func testEncodeDecodeEmptyArrayAndObject() throws { + let arr: JSONValue = .list([]) + let obj: JSONValue = .map([:]) + let arrData = try JSONEncoder().encode(arr) + let objData = try JSONEncoder().encode(obj) + let arrDecoded = try JSONDecoder().decode(JSONValue.self, from: arrData) + let objDecoded = try JSONDecoder().decode(JSONValue.self, from: objData) + XCTAssertEqual(arr, arrDecoded) + XCTAssertEqual(obj, objDecoded) + } + + func testDescriptionForData() { + let data = "abc".data(using: .utf8)! + let json = JSONValue.data(data) + XCTAssertTrue(json.description.contains(data.base64EncodedString())) + } +} + +struct UserProfile: Codable, Equatable { + let id: Int + let name: String + let email: String +} From 83a8f8ea165c7a7d1a1ed260552ffc57820d11a8 Mon Sep 17 00:00:00 2001 From: yaochenfeng <282696845@qq.com> Date: Wed, 18 Jun 2025 01:25:41 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E9=87=8D=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E4=BB=A5DF=E5=BC=80=E5=A4=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 ++++--- Sources/DFService/ServiceError.swift | 2 ++ Sources/DFService/ServiceManager.swift | 10 +++++----- Sources/DFService/ServiceStore.swift | 2 +- .../{ServiceModuleType.swift => DFModuleType.swift} | 4 ++-- Sources/DFService/types/DFStateType.swift | 2 +- Tests/DFServiceTests/ServiceStateTypeTests.swift | 2 +- 7 files changed, 16 insertions(+), 13 deletions(-) rename Sources/DFService/types/{ServiceModuleType.swift => DFModuleType.swift} (95%) diff --git a/README.md b/README.md index 52419d9..6cc296a 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,16 @@ 开发App常用工具和默认服务. - +## 任务列表 +- JSONValue 处理通用序列化和反序列化 ## 常用组件 ServiceValues管理常用服务 - 实现ServiceStateType 纯Swift Redux -### 实现ServiceStateType 使用 +### 实现DFStateType 使用 ```swift -struct CounterState: ServiceStateType { +struct CounterState: DFStateType { enum Action { case increment case decrement diff --git a/Sources/DFService/ServiceError.swift b/Sources/DFService/ServiceError.swift index fad26c7..4bf1a38 100644 --- a/Sources/DFService/ServiceError.swift +++ b/Sources/DFService/ServiceError.swift @@ -15,4 +15,6 @@ public enum ServiceError: Error { /// 未实现 case notImplemented(String = #fileID + " " + #function) + /// 转换类型失败 + case castError(from: Any, to: Any.Type) } diff --git a/Sources/DFService/ServiceManager.swift b/Sources/DFService/ServiceManager.swift index 5921556..338cce3 100644 --- a/Sources/DFService/ServiceManager.swift +++ b/Sources/DFService/ServiceManager.swift @@ -2,7 +2,7 @@ public final class ServiceManager { public static var shared = ServiceManager() public init() {} - private var modules: [String: ServiceModuleType] = [:] + private var modules: [String: DFModuleType] = [:] @MainActor public func runAll(phase: ServicePhase) { @@ -13,20 +13,20 @@ public final class ServiceManager { } extension ServiceManager { - public func register(_ module: Module) { + public func register(_ module: Module) { modules[Module.name] = module } - public func register(_ module: Module.Type = Module.self) { + public func register(_ module: Module.Type = Module.self) { let value = module.init(self) register(value) } // 通过模块名称获取模块实例 - public func getModule(named name: String) -> ServiceModuleType? { + public func getModule(named name: String) -> DFModuleType? { return modules[name] } // 通过模块类型获取模块实例 - public func getModule(byType type: T.Type) -> T? { + public func getModule(byType type: T.Type) -> T? { return modules.values.first(where: { $0 is T }) as? T } public func sendEvent(_ event: ServiceEvent, to moduleName: String? = nil) { diff --git a/Sources/DFService/ServiceStore.swift b/Sources/DFService/ServiceStore.swift index 11738fe..64b10ea 100644 --- a/Sources/DFService/ServiceStore.swift +++ b/Sources/DFService/ServiceStore.swift @@ -1,7 +1,7 @@ import Foundation -public final class ServiceStore { +public final class ServiceStore { public private(set) var state: State { didSet { diff --git a/Sources/DFService/types/ServiceModuleType.swift b/Sources/DFService/types/DFModuleType.swift similarity index 95% rename from Sources/DFService/types/ServiceModuleType.swift rename to Sources/DFService/types/DFModuleType.swift index fcb3979..85273d5 100644 --- a/Sources/DFService/types/ServiceModuleType.swift +++ b/Sources/DFService/types/DFModuleType.swift @@ -37,7 +37,7 @@ public struct ServicePhase: Comparable, RawRepresentable { } } -public protocol ServiceModuleType: AnyObject { +public protocol DFModuleType: AnyObject { init(_ manager: ServiceManager) static var name: String { get } // ren阶段 @@ -48,7 +48,7 @@ public protocol ServiceModuleType: AnyObject { func handle(event: ServiceEvent) } -extension ServiceModuleType { +extension DFModuleType { public static var name: String { return String(describing: self) } diff --git a/Sources/DFService/types/DFStateType.swift b/Sources/DFService/types/DFStateType.swift index 8bf69c8..07eaf01 100644 --- a/Sources/DFService/types/DFStateType.swift +++ b/Sources/DFService/types/DFStateType.swift @@ -1,5 +1,5 @@ /// A protocol that defines the required interface for representing the state of a service. -/// Types conforming to `ServiceStateType` should encapsulate all necessary information +/// Types conforming to `DFStateType` should encapsulate all necessary information /// about the current state of a service, enabling state management and transitions. public protocol DFStateType { associatedtype Action diff --git a/Tests/DFServiceTests/ServiceStateTypeTests.swift b/Tests/DFServiceTests/ServiceStateTypeTests.swift index fe08bdb..bba62ba 100644 --- a/Tests/DFServiceTests/ServiceStateTypeTests.swift +++ b/Tests/DFServiceTests/ServiceStateTypeTests.swift @@ -70,7 +70,7 @@ final class ServiceStateTypeTests: XCTestCase { } } -struct CounterState: ServiceStateType { +struct CounterState: DFStateType { static func effect(action: Action, context: DFService.ServiceStore.EffectContext) { print("effect state:\(context.state) action:\(action)") From 40e051e5cf127f5402996fba88cbb08331275654 Mon Sep 17 00:00:00 2001 From: yaochenfeng <282696845@qq.com> Date: Wed, 18 Jun 2025 01:37:15 +0800 Subject: [PATCH 3/3] fix: ServiceError --- Sources/DFService/ServiceError.swift | 30 +++++++--------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/Sources/DFService/ServiceError.swift b/Sources/DFService/ServiceError.swift index 4bcc6ee..ae25182 100644 --- a/Sources/DFService/ServiceError.swift +++ b/Sources/DFService/ServiceError.swift @@ -7,6 +7,7 @@ import Foundation public enum ServiceError: Error { + public static var invalidParameters = 1000 /// API 错误,包含错误代码和消息 /// - Parameters: /// - code: 错误代码 @@ -14,12 +15,9 @@ public enum ServiceError: Error { /// - detail: 可选的调试消息,默认为 nil case api(code: Int, message: String, detail: String? = nil) - /// 未实现 -<<<<<<< feat/jsonvalue - case notImplemented(String = #fileID + " " + #function) /// 转换类型失败 case castError(from: Any, to: Any.Type) -======= + /// 未实现 case notImplemented(String = #function) /// 自定义错误 case custom(Error) @@ -34,15 +32,13 @@ public enum ServiceError: Error { } /// 扩展 ServiceError 以提供错误描述 -extension ServiceError: CustomStringConvertible, CustomDebugStringConvertible { +extension ServiceError: CustomStringConvertible { public var errorCode: Int { switch self { case .api(let code, _, _): return code - case .notImplemented: - return 0 // 未实现的错误没有特定的错误代码 - case .custom(let error): - return (error as NSError).code // 使用 NSError 的代码 + default: + return ServiceError.invalidParameters } } public var description: String { @@ -53,20 +49,8 @@ extension ServiceError: CustomStringConvertible, CustomDebugStringConvertible { return "未实现: \(location)" case .custom(let error): return error.localizedDescription + case .castError: + return "参数错误" } } - - public var debugDescription: String { - - switch self { - case .api(let code, let message, let detail): - return - "ServiceError api - Code: \(code), Message: \(message), Detail: \(detail ?? "No detail")" - case .notImplemented(let location): - return "ServiceError - Location: \(location)" - case .custom(let error): - return "ServiceError - \(error.localizedDescription)" - } - } ->>>>>>> main }