diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index c7d755e7..375a884d 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -14,26 +14,25 @@ jobs: strategy: fail-fast: false matrix: - swift-version: ['5.7', '5.7.1', '5.7.2'] - mongodb-version: ['4.2', '4.4', '5.0', '6.0'] + swift-version: ["5.7", "5.8"] + mongodb-version: ["4.2", "4.4", "5.0", "6.0", "7.0"] steps: - - name: Check out - uses: actions/checkout@v3 - - - name: Install Swift - uses: swift-actions/setup-swift@v1 - with: - swift-version: ${{ matrix.swift-version }} - - - name: Start MongoDB - uses: supercharge/mongodb-github-action@1.8.0 - with: - mongodb-version: ${{ matrix.mongodb-version }} - mongodb-replica-set: mk-rs - - - name: Run tests - run: swift test - + - name: Check out + uses: actions/checkout@v3 + + - name: Install Swift + uses: swift-actions/setup-swift@v1 + with: + swift-version: ${{ matrix.swift-version }} + + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.8.0 + with: + mongodb-version: ${{ matrix.mongodb-version }} + mongodb-replica-set: mk-rs + + - name: Run tests + run: swift test # macos: # runs-on: macos-12 # steps: diff --git a/Sources/Meow/KeyPathModel/KeyPathModels.swift b/Sources/Meow/KeyPathModel/KeyPathModels.swift index 83252267..c29a8208 100644 --- a/Sources/Meow/KeyPathModel/KeyPathModels.swift +++ b/Sources/Meow/KeyPathModel/KeyPathModels.swift @@ -64,7 +64,11 @@ extension QuerySubject where T: Sequence { } } -public protocol KeyPathQueryable: Codable {} +public protocol KeyPathQueryable: Codable { + static func resolveFieldPath(_ field: KeyPath>) -> [String] + static func resolveFieldPath(_ field: KeyPath>) -> [String] +} + public protocol KeyPathQueryableModel: ReadableModel, KeyPathQueryable {} extension CodingUserInfoKey { @@ -272,6 +276,17 @@ public struct QueryableField { internal var isInvalid: Bool { key == nil } + public subscript(dynamicMember keyPath: KeyPath>) -> QueryableField where Value: KeyPathQueryable { + var subKeys = Value.resolveFieldPath(keyPath) + let lastKey = subKeys.isEmpty ? nil : subKeys.removeLast() + + return QueryableField( + parents: parents + subKeys, + key: lastKey, + value: nil + ) + } + public subscript(dynamicMember keyPath: KeyPath>) -> QueryableField { if let key = key, let value = value { let subField = value[keyPath: keyPath] @@ -291,6 +306,24 @@ public struct QueryableField { } } +extension QueryableField where Value: Sequence { + public subscript(dynamicMember keyPath: KeyPath>) -> QueryableField where Value.Element: KeyPathQueryable { + var subKeys = Value.Element.resolveFieldPath(keyPath) + let lastKey = subKeys.isEmpty ? nil : subKeys.removeLast() + var parents = parents + if let key = self.key { + parents.append(key) + parents.append(contentsOf: subKeys) + } + parents.append("$") + return QueryableField( + parents: parents, + key: lastKey, + value: nil + ) + } +} + extension QueryableField: _QueryableFieldRepresentable where Value: Codable { public var wrapper: _QueryableFieldWrapper { .init(wrapped: .queryableField(self)) diff --git a/Sources/Meow/KeyPathModel/MeowCollection+KeyPath.swift b/Sources/Meow/KeyPathModel/MeowCollection+KeyPath.swift index cd85022f..578b62b0 100644 --- a/Sources/Meow/KeyPathModel/MeowCollection+KeyPath.swift +++ b/Sources/Meow/KeyPathModel/MeowCollection+KeyPath.swift @@ -283,6 +283,24 @@ public struct ModelUpdateQuery { unsetField(at: keyPath) } } + + /// Adds an atomic `$set` to the update query that updates the field corresponding to `keyPath` to the `newValue` + public mutating func setField(at keyPath: KeyPath>, to newValue: PE) throws { + let path = M.resolveFieldPath(keyPath).joined(separator: ".") + let newValue = try newValue.encodePrimitive() + set[path] = newValue + } + + /// Sets the `Result`'s `keyPath` to a constant `newValue` + public mutating func setField(at keyPath: KeyPath>, to newValue: PE?) throws { + let path = M.resolveFieldPath(keyPath).joined(separator: ".") + if let newValue = newValue { + let newValue = try newValue.encodePrimitive() + set[path] = newValue + } else { + unset[path] = "" + } + } /// Adds an atomic `$unset` to the update query that updates the field corresponding to `keyPath` to be removed public mutating func unsetField(at keyPath: WritableKeyPath>) { diff --git a/Sources/Meow/KeyPathModel/TestDecoder.swift b/Sources/Meow/KeyPathModel/TestDecoder.swift index 81e53537..73207440 100644 --- a/Sources/Meow/KeyPathModel/TestDecoder.swift +++ b/Sources/Meow/KeyPathModel/TestDecoder.swift @@ -66,6 +66,15 @@ struct KeyedTestDecodingContainer: KeyedDecodingContainerProtoco } } +struct ArrayElementCodingKey: CodingKey { + var stringValue: String { "$" } + var intValue: Int? { nil } + + init() {} + init?(intValue: Int) { nil } + init?(stringValue: String) { nil } +} + struct UnkeyedTestDecodingContainer: UnkeyedDecodingContainer { let decoder: TestDecoder var codingPath: [CodingKey] { decoder.codingPath } @@ -73,12 +82,17 @@ struct UnkeyedTestDecodingContainer: UnkeyedDecodingContainer { var currentIndex: Int { 0 } var isAtEnd: Bool { true } - func decode(_ type: T.Type) throws -> T { - throw CannotActuallyDecode() + func decode(_ type: T.Type) throws -> T { + var decoder = self.decoder + decoder.codingPath.append(ArrayElementCodingKey()) + + return try T(from: decoder) } func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { - throw CannotActuallyDecode() + var decoder = self.decoder + decoder.codingPath.append(ArrayElementCodingKey()) + return UnkeyedTestDecodingContainer(decoder: decoder) } func decodeNil() throws -> Bool { @@ -90,7 +104,12 @@ struct UnkeyedTestDecodingContainer: UnkeyedDecodingContainer { } func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw CannotActuallyDecode() + var decoder = self.decoder + decoder.codingPath.append(ArrayElementCodingKey()) + + return KeyedDecodingContainer( + KeyedTestDecodingContainer(decoder: decoder) + ) } }