diff --git a/Samples/Demo/App/Keychain/CompositeCredentials.swift b/Samples/Demo/App/Keychain/CompositeCredentials.swift index 9cb5e87..772a983 100644 --- a/Samples/Demo/App/Keychain/CompositeCredentials.swift +++ b/Samples/Demo/App/Keychain/CompositeCredentials.swift @@ -1,128 +1,128 @@ import StealthyStash - -struct CompositeCredentials: StealthyModel { +@StealthyModel +struct CompositeCredentials { //typealias QueryBuilder = CompositeCredentialsQueryBuilder - struct QueryBuilder: ModelQueryBuilder { - static func updates( - from previousItem: CompositeCredentials, - to newItem: CompositeCredentials - ) -> [StealthyPropertyUpdate] { - let newPasswordData = newItem.password.flatMap { - $0.data(using: .utf8) - }.map { - InternetPasswordItem(account: newItem.userName, data: $0) - } - - let oldPasswordData = previousItem.password.flatMap { - $0.data(using: .utf8) - }.map { - InternetPasswordItem(account: previousItem.userName, data: $0) - } - - let previousTokenData = previousItem.token.flatMap { - $0.data(using: .utf8) - }.map { - GenericPasswordItem(account: previousItem.userName, data: $0) - } - - let newTokenData = newItem.token.flatMap { - $0.data(using: .utf8) - }.map { - GenericPasswordItem(account: newItem.userName, data: $0) - } - - let passwordUpdate = StealthyPropertyUpdate( - previousProperty: oldPasswordData, - newProperty: newPasswordData - ) - let tokenUpdate = StealthyPropertyUpdate( - previousProperty: previousTokenData, - newProperty: newTokenData - ) - return [passwordUpdate, tokenUpdate] - } - - static func properties( - from model: CompositeCredentials, - for _: ModelOperation - ) -> [AnyStealthyProperty] { - let passwordData = model.password.flatMap { - $0.data(using: .utf8) - } - - let passwordProperty: AnyStealthyProperty = .init( - property: InternetPasswordItem( - account: model.userName, - data: passwordData ?? .init() - ) - ) - - let tokenData = model.token.flatMap { - $0.data(using: .utf8) - } - - let tokenProperty: AnyStealthyProperty = .init( - property: GenericPasswordItem( - account: model.userName, - data: tokenData ?? .init() - ) - ) - - return [passwordProperty, tokenProperty] - } - - static func queries(from _: Void) -> [String: Query] { - [ - "password": TypeQuery(type: .internet), - "token": TypeQuery(type: .generic) - ] - } - - static func model( - from properties: [String: [AnyStealthyProperty]] - ) throws -> CompositeCredentials? { - for internet in properties["password"] ?? [] { - for generic in properties["token"] ?? [] { - if internet.account == generic.account { - return .init( - userName: internet.account, - password: internet.dataString, - token: generic.dataString - ) - } - } - } - let properties = properties.values.flatMap { $0 }.enumerated().sorted { lhs, rhs in - if lhs.element.propertyType == rhs.element.propertyType { - return lhs.offset < rhs.offset - } else { - return lhs.element.propertyType == .internet - } - }.map(\.element) - - guard let username = properties.map(\.account).first else { - return nil - } - let password = properties - .first { $0.propertyType == .internet }? - .data - let token = properties.first { - $0.propertyType == .generic && $0.account == username - }?.data - - return CompositeCredentials( - userName: username, - password: password?.string(), - token: token?.string() - ) - } - - typealias QueryType = Void - - typealias StealthyModelType = CompositeCredentials - } +// struct QueryBuilder: ModelQueryBuilder { +// static func updates( +// from previousItem: CompositeCredentials, +// to newItem: CompositeCredentials +// ) -> [StealthyPropertyUpdate] { +// let newPasswordData = newItem.password.flatMap { +// $0.data(using: .utf8) +// }.map { +// InternetPasswordItem(account: newItem.userName, data: $0) +// } +// +// let oldPasswordData = previousItem.password.flatMap { +// $0.data(using: .utf8) +// }.map { +// InternetPasswordItem(account: previousItem.userName, data: $0) +// } +// +// let previousTokenData = previousItem.token.flatMap { +// $0.data(using: .utf8) +// }.map { +// GenericPasswordItem(account: previousItem.userName, data: $0) +// } +// +// let newTokenData = newItem.token.flatMap { +// $0.data(using: .utf8) +// }.map { +// GenericPasswordItem(account: newItem.userName, data: $0) +// } +// +// let passwordUpdate = StealthyPropertyUpdate( +// previousProperty: oldPasswordData, +// newProperty: newPasswordData +// ) +// let tokenUpdate = StealthyPropertyUpdate( +// previousProperty: previousTokenData, +// newProperty: newTokenData +// ) +// return [passwordUpdate, tokenUpdate] +// } +// +// static func properties( +// from model: CompositeCredentials, +// for _: ModelOperation +// ) -> [AnyStealthyProperty] { +// let passwordData = model.password.flatMap { +// $0.data(using: .utf8) +// } +// +// let passwordProperty: AnyStealthyProperty = .init( +// property: InternetPasswordItem( +// account: model.userName, +// data: passwordData ?? .init() +// ) +// ) +// +// let tokenData = model.token.flatMap { +// $0.data(using: .utf8) +// } +// +// let tokenProperty: AnyStealthyProperty = .init( +// property: GenericPasswordItem( +// account: model.userName, +// data: tokenData ?? .init() +// ) +// ) +// +// return [passwordProperty, tokenProperty] +// } +// +// static func queries(from _: Void) -> [String: Query] { +// [ +// "password": TypeQuery(type: .internet), +// "token": TypeQuery(type: .generic) +// ] +// } +// +// static func model( +// from properties: [String: [AnyStealthyProperty]] +// ) throws -> CompositeCredentials? { +// for internet in properties["password"] ?? [] { +// for generic in properties["token"] ?? [] { +// if internet.account == generic.account { +// return .init( +// userName: internet.account, +// password: internet.dataString, +// token: generic.dataString +// ) +// } +// } +// } +// let properties = properties.values.flatMap { $0 }.enumerated().sorted { lhs, rhs in +// if lhs.element.propertyType == rhs.element.propertyType { +// return lhs.offset < rhs.offset +// } else { +// return lhs.element.propertyType == .internet +// } +// }.map(\.element) +// +// guard let username = properties.map(\.account).first else { +// return nil +// } +// let password = properties +// .first { $0.propertyType == .internet }? +// .data +// let token = properties.first { +// $0.propertyType == .generic && $0.account == username +// }?.data +// +// return CompositeCredentials( +// userName: username, +// password: password?.string(), +// token: token?.string() +// ) +// } +// +// typealias QueryType = Void +// +// typealias StealthyModelType = CompositeCredentials +// } internal init(userName: String, password: String?, token: String?) { self.userName = userName diff --git a/Samples/Demo/App/Keychain/CompositeCredentialsQueryBuilder.swift b/Samples/Demo/App/Keychain/CompositeCredentialsQueryBuilder.swift index 928672f..4d3b10f 100644 --- a/Samples/Demo/App/Keychain/CompositeCredentialsQueryBuilder.swift +++ b/Samples/Demo/App/Keychain/CompositeCredentialsQueryBuilder.swift @@ -1,122 +1,122 @@ -import StealthyStash - -@available(*, deprecated, message: "Use internal builder.") -struct CompositeCredentialsQueryBuilder: ModelQueryBuilder { - static func updates( - from previousItem: CompositeCredentials, - to newItem: CompositeCredentials - ) -> [StealthyPropertyUpdate] { - let newPasswordData = newItem.password.flatMap { - $0.data(using: .utf8) - }.map { - InternetPasswordItem(account: newItem.userName, data: $0) - } - - let oldPasswordData = previousItem.password.flatMap { - $0.data(using: .utf8) - }.map { - InternetPasswordItem(account: previousItem.userName, data: $0) - } - - let previousTokenData = previousItem.token.flatMap { - $0.data(using: .utf8) - }.map { - GenericPasswordItem(account: previousItem.userName, data: $0) - } - - let newTokenData = newItem.token.flatMap { - $0.data(using: .utf8) - }.map { - GenericPasswordItem(account: newItem.userName, data: $0) - } - - let passwordUpdate = StealthyPropertyUpdate( - previousProperty: oldPasswordData, - newProperty: newPasswordData - ) - let tokenUpdate = StealthyPropertyUpdate( - previousProperty: previousTokenData, - newProperty: newTokenData - ) - return [passwordUpdate, tokenUpdate] - } - - static func properties( - from model: CompositeCredentials, - for _: ModelOperation - ) -> [AnyStealthyProperty] { - let passwordData = model.password.flatMap { - $0.data(using: .utf8) - } - - let passwordProperty: AnyStealthyProperty = .init( - property: InternetPasswordItem( - account: model.userName, - data: passwordData ?? .init() - ) - ) - - let tokenData = model.token.flatMap { - $0.data(using: .utf8) - } - - let tokenProperty: AnyStealthyProperty = .init( - property: GenericPasswordItem( - account: model.userName, - data: tokenData ?? .init() - ) - ) - - return [passwordProperty, tokenProperty] - } - - static func queries(from _: Void) -> [String: Query] { - [ - "password": TypeQuery(type: .internet), - "token": TypeQuery(type: .generic) - ] - } - - static func model( - from properties: [String: [AnyStealthyProperty]] - ) throws -> CompositeCredentials? { - for internet in properties["password"] ?? [] { - for generic in properties["token"] ?? [] { - if internet.account == generic.account { - return .init( - userName: internet.account, - password: internet.dataString, - token: generic.dataString - ) - } - } - } - let properties = properties.values.flatMap { $0 }.enumerated().sorted { lhs, rhs in - if lhs.element.propertyType == rhs.element.propertyType { - return lhs.offset < rhs.offset - } else { - return lhs.element.propertyType == .internet - } - }.map(\.element) - - guard let username = properties.map(\.account).first else { - return nil - } - let password = properties - .first { $0.propertyType == .internet }? - .data - let token = properties.first { - $0.propertyType == .generic && $0.account == username - }?.data - - return CompositeCredentials( - userName: username, - password: password?.string(), - token: token?.string() - ) - } - - typealias QueryType = Void - - typealias StealthyModelType = CompositeCredentials -} +//import StealthyStash +// +//@available(*, deprecated, message: "Use internal builder.") +//struct CompositeCredentialsQueryBuilder: ModelQueryBuilder { +// static func updates( +// from previousItem: CompositeCredentials, +// to newItem: CompositeCredentials +// ) -> [StealthyPropertyUpdate] { +// let newPasswordData = newItem.password.flatMap { +// $0.data(using: .utf8) +// }.map { +// InternetPasswordItem(account: newItem.userName, data: $0) +// } +// +// let oldPasswordData = previousItem.password.flatMap { +// $0.data(using: .utf8) +// }.map { +// InternetPasswordItem(account: previousItem.userName, data: $0) +// } +// +// let previousTokenData = previousItem.token.flatMap { +// $0.data(using: .utf8) +// }.map { +// GenericPasswordItem(account: previousItem.userName, data: $0) +// } +// +// let newTokenData = newItem.token.flatMap { +// $0.data(using: .utf8) +// }.map { +// GenericPasswordItem(account: newItem.userName, data: $0) +// } +// +// let passwordUpdate = StealthyPropertyUpdate( +// previousProperty: oldPasswordData, +// newProperty: newPasswordData +// ) +// let tokenUpdate = StealthyPropertyUpdate( +// previousProperty: previousTokenData, +// newProperty: newTokenData +// ) +// return [passwordUpdate, tokenUpdate] +// } +// +// static func properties( +// from model: CompositeCredentials, +// for _: ModelOperation +// ) -> [AnyStealthyProperty] { +// let passwordData = model.password.flatMap { +// $0.data(using: .utf8) +// } +// +// let passwordProperty: AnyStealthyProperty = .init( +// property: InternetPasswordItem( +// account: model.userName, +// data: passwordData ?? .init() +// ) +// ) +// +// let tokenData = model.token.flatMap { +// $0.data(using: .utf8) +// } +// +// let tokenProperty: AnyStealthyProperty = .init( +// property: GenericPasswordItem( +// account: model.userName, +// data: tokenData ?? .init() +// ) +// ) +// +// return [passwordProperty, tokenProperty] +// } +// +// static func queries(from _: Void) -> [String: Query] { +// [ +// "password": TypeQuery(type: .internet), +// "token": TypeQuery(type: .generic) +// ] +// } +// +// static func model( +// from properties: [String: [AnyStealthyProperty]] +// ) throws -> CompositeCredentials? { +// for internet in properties["password"] ?? [] { +// for generic in properties["token"] ?? [] { +// if internet.account == generic.account { +// return .init( +// userName: internet.account, +// password: internet.dataString, +// token: generic.dataString +// ) +// } +// } +// } +// let properties = properties.values.flatMap { $0 }.enumerated().sorted { lhs, rhs in +// if lhs.element.propertyType == rhs.element.propertyType { +// return lhs.offset < rhs.offset +// } else { +// return lhs.element.propertyType == .internet +// } +// }.map(\.element) +// +// guard let username = properties.map(\.account).first else { +// return nil +// } +// let password = properties +// .first { $0.propertyType == .internet }? +// .data +// let token = properties.first { +// $0.propertyType == .generic && $0.account == username +// }?.data +// +// return CompositeCredentials( +// userName: username, +// password: password?.string(), +// token: token?.string() +// ) +// } +// +// typealias QueryType = Void +// +// typealias StealthyModelType = CompositeCredentials +//} diff --git a/Samples/Demo/App/Reactive/CompositeStealthyObject.swift b/Samples/Demo/App/Reactive/CompositeStealthyObject.swift index b28ab97..66419dc 100644 --- a/Samples/Demo/App/Reactive/CompositeStealthyObject.swift +++ b/Samples/Demo/App/Reactive/CompositeStealthyObject.swift @@ -20,6 +20,7 @@ class CompositeStealthyObject: ObservableObject { .receive(on: DispatchQueue.main) .assign(to: &$secret) + //print(CompositeCredentials.QueryBuilder.QueryType) let loadResult = Publishers.Merge(loadPassthrough, triggerSet.receiveUpdatePublisher) .map { Future { completed in diff --git a/Sources/StealthyStash/Macros.swift b/Sources/StealthyStash/Macros.swift index 5a7fdfe..e137052 100644 --- a/Sources/StealthyStash/Macros.swift +++ b/Sources/StealthyStash/Macros.swift @@ -9,4 +9,6 @@ import Foundation -@attached(extension, conformances: StealthyModel) public macro StealthyModelMacro() = #externalMacro(module: "StealthyStashMacros", type: "StealthyModelMacro") +@attached(member, names: named(QueryBuilder)) +@attached(extension, conformances: StealthyModel) +public macro StealthyModel() = #externalMacro(module: "StealthyStashMacros", type: "StealthyModelMacro") diff --git a/Sources/StealthyStash/StealthyRepository+StealthyModel.swift b/Sources/StealthyStash/StealthyRepository+StealthyModel.swift index abb0d07..d1b812f 100644 --- a/Sources/StealthyStash/StealthyRepository+StealthyModel.swift +++ b/Sources/StealthyStash/StealthyRepository+StealthyModel.swift @@ -4,6 +4,7 @@ extension StealthyRepository { public func create( _ model: StealthyModelType ) throws { + let properties = StealthyModelType.QueryBuilder.properties(from: model, for: .adding) for property in properties { try create(property) diff --git a/Sources/StealthyStashMacros/CompositeModelMacro.swift b/Sources/StealthyStashMacros/CompositeModelMacro.swift index 5b2c397..4489361 100644 --- a/Sources/StealthyStashMacros/CompositeModelMacro.swift +++ b/Sources/StealthyStashMacros/CompositeModelMacro.swift @@ -6,7 +6,6 @@ // import SwiftSyntaxMacros -import StealthyStash import SwiftSyntax import SwiftCompilerPlugin @@ -17,11 +16,169 @@ struct MacrosPlugin: CompilerPlugin { ] } -public struct StealthyModelMacro : ExtensionMacro { +enum CustomError: Error { case message(String) } +public struct StealthyModelMacro : MemberMacro, ExtensionMacro { public static func expansion(of node: SwiftSyntax.AttributeSyntax, attachedTo declaration: some SwiftSyntax.DeclGroupSyntax, providingExtensionsOf type: some SwiftSyntax.TypeSyntaxProtocol, conformingTo protocols: [SwiftSyntax.TypeSyntax], in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.ExtensionDeclSyntax] { - return try [ .init("test")] + + + [try ExtensionDeclSyntax("extension \(type): StealthyModel {}")] } +// public static func expansion(of node: SwiftSyntax.AttributeSyntax, attachedTo declaration: some SwiftSyntax.DeclGroupSyntax, providingExtensionsOf type: some SwiftSyntax.TypeSyntaxProtocol, conformingTo protocols: [SwiftSyntax.TypeSyntax], in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.ExtensionDeclSyntax] { +// return try [] +// } + + public static func expansion(of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] { + guard let structDecl = declaration.as(StructDeclSyntax.self) else { + throw CustomError.message("Type must be struct.") + } + + let typeName = structDecl.name.trimmed + let anotherDecl : DeclSyntax + #if false + let inheritedType = InheritedTypeSyntax(type: IdentifierTypeSyntax(name: "QueryBuilder")) + + + let newStruct = StructDeclSyntax(name: "NewQueryBuilder" , inheritanceClause: .init(inheritedTypes: .init(arrayLiteral: inheritedType))) { + FunctionDeclSyntax(modifiers: .init(itemsBuilder: { + DeclModifierSyntax(name: .keyword(.static)) + }), name: "updates", signature: FunctionSignatureSyntax.init( + + parameterClause: .init(parameters: .init(itemsBuilder: { + [ + FunctionParameterSyntax(firstName: "from", secondName: "previousItem", type: TypeSyntax( "Int")) + + ] + })), returnClause: .init(type: TypeSyntax("[Int]")))) + } + anotherDec = .init(newStruct) + #else + anotherDecl = """ + struct QueryBuilder: ModelQueryBuilder { + static func updates( + from previousItem: \(typeName), + to newItem: \(typeName) + ) -> [StealthyPropertyUpdate] { + let newPasswordData = newItem.password.flatMap { + $0.data(using: .utf8) + }.map { + InternetPasswordItem(account: newItem.userName, data: $0) + } + + let oldPasswordData = previousItem.password.flatMap { + $0.data(using: .utf8) + }.map { + InternetPasswordItem(account: previousItem.userName, data: $0) + } + + let previousTokenData = previousItem.token.flatMap { + $0.data(using: .utf8) + }.map { + GenericPasswordItem(account: previousItem.userName, data: $0) + } + + let newTokenData = newItem.token.flatMap { + $0.data(using: .utf8) + }.map { + GenericPasswordItem(account: newItem.userName, data: $0) + } + + let passwordUpdate = StealthyPropertyUpdate( + previousProperty: oldPasswordData, + newProperty: newPasswordData + ) + let tokenUpdate = StealthyPropertyUpdate( + previousProperty: previousTokenData, + newProperty: newTokenData + ) + return [passwordUpdate, tokenUpdate] + } + + static func properties( + from model: \(typeName), + for _: ModelOperation + ) -> [AnyStealthyProperty] { + let passwordData = model.password.flatMap { + $0.data(using: .utf8) + } + + let passwordProperty: AnyStealthyProperty = .init( + property: InternetPasswordItem( + account: model.userName, + data: passwordData ?? .init() + ) + ) + + let tokenData = model.token.flatMap { + $0.data(using: .utf8) + } + + let tokenProperty: AnyStealthyProperty = .init( + property: GenericPasswordItem( + account: model.userName, + data: tokenData ?? .init() + ) + ) + + return [passwordProperty, tokenProperty] + } + + static func queries(from _: Void) -> [String: Query] { + [ + "password": TypeQuery(type: .internet), + "token": TypeQuery(type: .generic) + ] + } + + static func model( + from properties: [String: [AnyStealthyProperty]] + ) throws -> \(typeName)? { + for internet in properties["password"] ?? [] { + for generic in properties["token"] ?? [] { + if internet.account == generic.account { + return .init( + userName: internet.account, + password: internet.dataString, + token: generic.dataString + ) + } + } + } + let properties = properties.values.flatMap { $0 }.enumerated().sorted { lhs, rhs in + if lhs.element.propertyType == rhs.element.propertyType { + return lhs.offset < rhs.offset + } else { + return lhs.element.propertyType == .internet + } + }.map(\\.element) + + guard let username = properties.map(\\.account).first else { + return nil + } + let password = properties + .first { $0.propertyType == .internet }? + .data + let token = properties.first { + $0.propertyType == .generic && $0.account == username + }?.data + + return \(typeName)( + userName: username, + password: password?.string(), + token: token?.string() + ) + } + + internal typealias QueryType = Void + + internal typealias StealthyModelType = \(typeName) + } +""" + #endif + return [ + anotherDecl + ] + }