RustToMetal commented on code in PR #4190:
URL: https://github.com/apache/iggy/pull/4190#discussion_r4067712596


##########
.github/actions/swift/pre-merge/action.yml:
##########
@@ -0,0 +1,66 @@
+# 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: swift-pre-merge
+description: Swift pre-merge testing github iggy actions
+
+inputs:
+  task:
+    description: "Task to run (lint, test, build)"
+    required: true
+
+runs:
+  using: "composite"
+  steps:
+    - name: Setup Swift
+      uses: ./.github/actions/utils/setup-swift
+      with:
+        cache-key: ${{ inputs.task }}
+
+    # swift-format ships with the toolchain; the configuration lives in
+    # foreign/swift/.swift-format and the examples and BDD packages reuse it.
+    - name: Format Check
+      shell: bash
+      if: inputs.task == 'lint'
+      run: |
+        cd foreign/swift
+        swift format lint --strict --recursive Sources Tests Package.swift
+
+    # A warning-free build is the lint gate the Swift compiler provides: the
+    # SDK builds in Swift 6 language mode with strict concurrency checking.
+    - name: Build with warnings as errors
+      shell: bash
+      if: inputs.task == 'lint'
+      run: |
+        cd foreign/swift
+        swift build -Xswiftc -warnings-as-errors
+
+    - name: Test
+      shell: bash
+      if: inputs.task == 'test'
+      run: |
+        cd foreign/swift
+        # The end-to-end suite skips itself while IGGY_TCP_ADDRESS is unset,

Review Comment:
   Done, the comment moves to the PR that adds the end to end suite.



##########
.github/actions/swift/pre-merge/action.yml:
##########
@@ -0,0 +1,66 @@
+# 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: swift-pre-merge
+description: Swift pre-merge testing github iggy actions
+
+inputs:
+  task:
+    description: "Task to run (lint, test, build)"
+    required: true
+
+runs:
+  using: "composite"
+  steps:
+    - name: Setup Swift
+      uses: ./.github/actions/utils/setup-swift
+      with:
+        cache-key: ${{ inputs.task }}
+
+    # swift-format ships with the toolchain; the configuration lives in

Review Comment:
   Done, it only says where the formatter file lives now.



##########
foreign/swift/README.md:
##########
@@ -0,0 +1,28 @@
+# Swift SDK for Iggy

Review Comment:
   Done, added the logo block plus install and usage sections.



##########
foreign/swift/Package.swift:
##########
@@ -0,0 +1,55 @@
+// swift-tools-version: 6.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.
+
+import PackageDescription
+
+let package = Package(
+    name: "iggy",

Review Comment:
   Done, renamed to apache-iggy.



##########
foreign/swift/Sources/Iggy/Wire/ByteCodec.swift:
##########
@@ -0,0 +1,268 @@
+// 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.
+
+import Foundation
+
+/// Failure while encoding or decoding wire bytes. Internal to the codec; the
+/// client maps it onto an ``IggyError`` at the API boundary.
+enum WireError: Error, Equatable {
+    case truncated(offset: Int, need: Int, have: Int)
+    case invalidUTF8(offset: Int)
+    case invalidDiscriminant(type: String, value: UInt8)
+    case validation(String)
+    case invalidBatchChecksum(stored: UInt64, computed: UInt64)
+    case invalidMessageChecksum(stored: UInt64, computed: UInt64, offset: 
UInt64)
+    case invalidMessageTimestampDelta(UInt64)
+
+    var iggyError: IggyError {

Review Comment:
   Good catch, thanks. It maps to invalidCommand now, in the protocol PR where 
the mapping first gets a caller.



##########
foreign/swift/Sources/Iggy/Wire/ByteCodec.swift:
##########
@@ -0,0 +1,268 @@
+// 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.
+
+import Foundation
+
+/// Failure while encoding or decoding wire bytes. Internal to the codec; the
+/// client maps it onto an ``IggyError`` at the API boundary.
+enum WireError: Error, Equatable {
+    case truncated(offset: Int, need: Int, have: Int)
+    case invalidUTF8(offset: Int)
+    case invalidDiscriminant(type: String, value: UInt8)
+    case validation(String)
+    case invalidBatchChecksum(stored: UInt64, computed: UInt64)
+    case invalidMessageChecksum(stored: UInt64, computed: UInt64, offset: 
UInt64)
+    case invalidMessageTimestampDelta(UInt64)
+
+    var iggyError: IggyError {
+        switch self {
+        case .truncated(let offset, let need, let have):
+            IggyError(.invalidFormat, context: "unexpected end of buffer at 
offset \(offset): need \(need) bytes, have \(have)")
+        case .invalidUTF8(let offset):
+            IggyError(.invalidUtf8, context: "invalid utf-8 at offset 
\(offset)")
+        case .invalidDiscriminant(let type, let value):
+            IggyError(.invalidFormat, context: "unknown discriminant \(value) 
for \(type)")
+        case .validation(let message):
+            IggyError(.invalidFormat, context: message)
+        case .invalidBatchChecksum(let stored, let computed):
+            IggyError(.invalidBatchChecksum, context: "stored \(stored), 
computed \(computed)")
+        case .invalidMessageChecksum(let stored, let computed, let offset):
+            IggyError(.invalidMessageChecksum, context: "stored \(stored), 
computed \(computed), offset \(offset)")
+        case .invalidMessageTimestampDelta(let delta):
+            IggyError(.invalidMessageTimestampDelta, context: "delta \(delta) 
microseconds")
+        }
+    }
+}
+
+/// Little-endian append-only encoder over a byte array.
+struct ByteWriter {
+    private(set) var bytes: [UInt8]
+
+    init(capacity: Int = 0) {
+        bytes = []
+        bytes.reserveCapacity(capacity)
+    }
+
+    var count: Int { bytes.count }
+
+    mutating func write(_ value: UInt8) {
+        bytes.append(value)
+    }
+
+    mutating func write(_ value: Bool) {
+        bytes.append(value ? 1 : 0)
+    }
+
+    mutating func write(_ value: UInt16) {
+        withUnsafeBytes(of: value.littleEndian) { bytes.append(contentsOf: $0) 
}
+    }
+
+    mutating func write(_ value: UInt32) {
+        withUnsafeBytes(of: value.littleEndian) { bytes.append(contentsOf: $0) 
}
+    }
+
+    mutating func write(_ value: UInt64) {
+        withUnsafeBytes(of: value.littleEndian) { bytes.append(contentsOf: $0) 
}
+    }
+
+    mutating func write(_ value: Float) {
+        write(value.bitPattern)
+    }
+
+    mutating func write(_ value: UInt128Value) {
+        write(value.low)
+        write(value.high)
+    }
+
+    mutating func write(_ value: [UInt8]) {
+        bytes.append(contentsOf: value)
+    }
+
+    mutating func write(_ value: ArraySlice<UInt8>) {
+        bytes.append(contentsOf: value)
+    }
+
+    mutating func write(_ value: String) {
+        bytes.append(contentsOf: value.utf8)
+    }
+
+    /// `[len: u8][utf8]`, the layout of every wire name. The caller guarantees
+    /// the length fits.
+    mutating func writeName(_ value: String) {
+        let utf8 = Array(value.utf8)
+        bytes.append(UInt8(utf8.count))
+        bytes.append(contentsOf: utf8)
+    }
+
+    /// `[len: u32][utf8]`, the layout of longer free-form strings.
+    mutating func writeLongString(_ value: String) {
+        let utf8 = Array(value.utf8)
+        write(UInt32(utf8.count))
+        bytes.append(contentsOf: utf8)
+    }
+
+    mutating func writeZeros(_ count: Int) {
+        bytes.append(contentsOf: repeatElement(0, count: count))
+    }
+
+    /// Overwrites `count` bytes at `offset` with `value`; `value` must have
+    /// exactly `count` bytes and the range must already exist.
+    mutating func overwrite(at offset: Int, with value: [UInt8]) {
+        bytes.replaceSubrange(offset..<offset + value.count, with: value)
+    }
+}
+
+/// Little-endian cursor over a byte slice. Every read is bounds-checked and
+/// fails with ``WireError/truncated(offset:need:have:)`` instead of trapping,
+/// because the bytes come from the network.
+struct ByteReader {
+    let bytes: ArraySlice<UInt8>
+    private(set) var offset: Int
+
+    init(_ bytes: ArraySlice<UInt8>) {
+        self.bytes = bytes
+        self.offset = bytes.startIndex
+    }
+
+    init(_ bytes: [UInt8]) {
+        self.init(bytes[...])
+    }
+
+    var isAtEnd: Bool { offset >= bytes.endIndex }
+    var remaining: Int { bytes.endIndex - offset }
+    /// Position relative to the start of the buffer handed to the reader.
+    var position: Int { offset - bytes.startIndex }
+
+    private func require(_ count: Int) throws {
+        if remaining < count {
+            throw WireError.truncated(offset: position, need: count, have: 
remaining)
+        }
+    }
+
+    mutating func readUInt8() throws -> UInt8 {
+        try require(1)
+        let value = bytes[offset]
+        offset += 1
+        return value
+    }
+
+    mutating func readBool() throws -> Bool {
+        try readUInt8() != 0
+    }
+
+    mutating func readUInt16() throws -> UInt16 {

Review Comment:
   Done, the readers delegate to one init(littleEndianBytes:) per width and the 
unused getters are gone.



##########
foreign/swift/Sources/Iggy/Errors/IggyError.swift:
##########
@@ -0,0 +1,103 @@
+// 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.
+
+/// Error raised by every SDK operation.
+///
+/// The `code` is what the server answered with, or what the SDK decided
+/// locally (a dropped connection, a rejected argument). Match on it:
+///
+/// ```swift
+/// do {
+///     _ = try await client.getStream("orders")
+/// } catch let error as IggyError where error.code == .streamNameNotFound {
+///     // create it
+/// }
+/// ```
+///
+/// `context` carries whatever extra detail was available where the error was
+/// raised, such as the transport failure message or the protocol window the
+/// server accepts. It never carries credentials.
+public struct IggyError: Error, Sendable, Hashable, CustomStringConvertible {
+    /// The classified code.
+    public let code: IggyErrorCode
+    /// The exact code that arrived on the wire. Equal to `code.rawValue` 
unless
+    /// the server answered with a code this SDK does not know, in which case
+    /// `code` is ``IggyErrorCode/error`` and this keeps the original value.
+    public let rawCode: UInt32
+    /// Extra detail about the failure, when any was available.
+    public let context: String?
+
+    public init(_ code: IggyErrorCode, context: String? = nil) {
+        self.code = code
+        self.rawCode = code.rawValue
+        self.context = context
+    }
+
+    /// Maps a code received from the server, keeping unknown codes visible.
+    public init(wireCode: UInt32) {
+        if let code = IggyErrorCode(rawValue: wireCode) {
+            self.code = code
+            self.context = nil
+        } else {
+            self.code = .error
+            self.context = "unknown error code \(wireCode)"
+        }
+        self.rawCode = wireCode
+    }
+
+    public var description: String {
+        if let context {
+            return "\(code.name) (\(rawCode)): \(context)"
+        }
+        return "\(code.name) (\(rawCode))"
+    }
+}
+
+extension IggyError {

Review Comment:
   Done, removed here, the TCP client PR adds them back with isReconnectable 
written as isConnectionLoss || code == .unauthenticated.



##########
foreign/swift/Sources/Iggy/Wire/ByteCodec.swift:
##########
@@ -0,0 +1,268 @@
+// 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.
+
+import Foundation
+
+/// Failure while encoding or decoding wire bytes. Internal to the codec; the
+/// client maps it onto an ``IggyError`` at the API boundary.
+enum WireError: Error, Equatable {
+    case truncated(offset: Int, need: Int, have: Int)
+    case invalidUTF8(offset: Int)
+    case invalidDiscriminant(type: String, value: UInt8)
+    case validation(String)
+    case invalidBatchChecksum(stored: UInt64, computed: UInt64)
+    case invalidMessageChecksum(stored: UInt64, computed: UInt64, offset: 
UInt64)
+    case invalidMessageTimestampDelta(UInt64)
+
+    var iggyError: IggyError {
+        switch self {
+        case .truncated(let offset, let need, let have):
+            IggyError(.invalidFormat, context: "unexpected end of buffer at 
offset \(offset): need \(need) bytes, have \(have)")
+        case .invalidUTF8(let offset):
+            IggyError(.invalidUtf8, context: "invalid utf-8 at offset 
\(offset)")
+        case .invalidDiscriminant(let type, let value):
+            IggyError(.invalidFormat, context: "unknown discriminant \(value) 
for \(type)")
+        case .validation(let message):
+            IggyError(.invalidFormat, context: message)
+        case .invalidBatchChecksum(let stored, let computed):
+            IggyError(.invalidBatchChecksum, context: "stored \(stored), 
computed \(computed)")
+        case .invalidMessageChecksum(let stored, let computed, let offset):
+            IggyError(.invalidMessageChecksum, context: "stored \(stored), 
computed \(computed), offset \(offset)")
+        case .invalidMessageTimestampDelta(let delta):
+            IggyError(.invalidMessageTimestampDelta, context: "delta \(delta) 
microseconds")
+        }
+    }
+}
+
+/// Little-endian append-only encoder over a byte array.
+struct ByteWriter {
+    private(set) var bytes: [UInt8]
+
+    init(capacity: Int = 0) {
+        bytes = []
+        bytes.reserveCapacity(capacity)
+    }
+
+    var count: Int { bytes.count }
+
+    mutating func write(_ value: UInt8) {
+        bytes.append(value)
+    }
+
+    mutating func write(_ value: Bool) {
+        bytes.append(value ? 1 : 0)
+    }
+
+    mutating func write(_ value: UInt16) {
+        withUnsafeBytes(of: value.littleEndian) { bytes.append(contentsOf: $0) 
}
+    }
+
+    mutating func write(_ value: UInt32) {
+        withUnsafeBytes(of: value.littleEndian) { bytes.append(contentsOf: $0) 
}
+    }
+
+    mutating func write(_ value: UInt64) {
+        withUnsafeBytes(of: value.littleEndian) { bytes.append(contentsOf: $0) 
}
+    }
+
+    mutating func write(_ value: Float) {
+        write(value.bitPattern)
+    }
+
+    mutating func write(_ value: UInt128Value) {
+        write(value.low)
+        write(value.high)
+    }
+
+    mutating func write(_ value: [UInt8]) {
+        bytes.append(contentsOf: value)
+    }
+
+    mutating func write(_ value: ArraySlice<UInt8>) {
+        bytes.append(contentsOf: value)
+    }
+
+    mutating func write(_ value: String) {
+        bytes.append(contentsOf: value.utf8)
+    }
+
+    /// `[len: u8][utf8]`, the layout of every wire name. The caller guarantees
+    /// the length fits.
+    mutating func writeName(_ value: String) {
+        let utf8 = Array(value.utf8)
+        bytes.append(UInt8(utf8.count))
+        bytes.append(contentsOf: utf8)
+    }
+
+    /// `[len: u32][utf8]`, the layout of longer free-form strings.
+    mutating func writeLongString(_ value: String) {
+        let utf8 = Array(value.utf8)
+        write(UInt32(utf8.count))
+        bytes.append(contentsOf: utf8)
+    }
+
+    mutating func writeZeros(_ count: Int) {

Review Comment:
   Done, all five move to the protocol PR that first needs them.



##########
foreign/swift/Sources/Iggy/Wire/ByteCodec.swift:
##########
@@ -0,0 +1,268 @@
+// 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.
+
+import Foundation
+
+/// Failure while encoding or decoding wire bytes. Internal to the codec; the
+/// client maps it onto an ``IggyError`` at the API boundary.
+enum WireError: Error, Equatable {

Review Comment:
   Done, only the thrown cases stay and the mapping moves to the protocol PR.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to