From 6cb3ae80796e56c18aa5c565e984cf0228a5d4d4 Mon Sep 17 00:00:00 2001 From: retoor Date: Mon, 23 Dec 2024 19:56:25 +0100 Subject: [PATCH] Progress. --- .reviews/Package.swift.md | 31 ++++++++++------ .../DevRant/DevRantApiError.swift.md | 32 +++++++++++------ .../DevRant/DevRantJSONCoder.swift.md | 30 ++++++++++------ .../SwiftDevRant/DevRantRequest.swift.md | 35 +++++++++++++------ 4 files changed, 84 insertions(+), 44 deletions(-) diff --git a/.reviews/Package.swift.md b/.reviews/Package.swift.md index 1c2bd4b..5d635c8 100644 --- a/.reviews/Package.swift.md +++ b/.reviews/Package.swift.md @@ -1,11 +1,20 @@ -{ - "extension": ".swift", - "source": "// swift-tools-version: 6.0\n// The swift-tools-version declares the minimum version of Swift required to build this package.\n\nimport PackageDescription\n\nlet package = Package(\n name: \"SwiftDevRant\",\n platforms: [.iOS(.v13), .macOS(.v10_15), .tvOS(.v13), .watchOS(.v6), .driverKit(.v19), .macCatalyst(.v13), .visionOS(.v1)],\n products: [\n // Products define the executables and libraries a package produces, making them visible to other packages.\n .library(\n name: \"SwiftDevRant\",\n targets: [\"SwiftDevRant\"]\n ),\n ],\n dependencies: [\n .package(url: \"https://github.com/WilhelmOks/KreeRequest\", .upToNextMajor(from: \"1.0.3\")),\n ],\n targets: [\n // Targets are the basic building blocks of a package, defining a module or a test suite.\n // Targets can depend on other targets in this package and products from dependencies.\n .target(\n name: \"SwiftDevRant\",\n dependencies: [\"KreeRequest\"]\n ),\n .testTarget(\n name: \"SwiftDevRantTests\",\n dependencies: [\"SwiftDevRant\"]\n ),\n ]\n)\n", - "review": "**Grade: 7**\n\n# Bugs\n- No explicit bugs were found in the code. However, compatibility with \"swift-tools-version: 6.0\" might need future validation as this version wasn't officially recognized up to October 2023.\n\n# Optimizations\n- Consider updating `swift-tools-version` to a stable and widely recognized version (e.g., 5.7) to ensure compatibility with existing Swift toolchains.\n- Adding more comments within the code to describe functionality and dependencies could enhance readability and maintainability.\n\n# Good points\n- Proper use of Swift Package Manager manifest file structure.\n- Comprehensive platform support including `iOS`, `macOS`, `tvOS`, `watchOS`, `driverKit`, `macCatalyst`, and `visionOS`.\n- The package dependency is clearly defined with a specified source and versioning.\n\n# Summary\nOverall, this `Package.swift` file adheres well to the standard Swift Package Manager format with a clear definition of products, dependencies, and targets. Compatibility considerations with tool versions could improve wider adoption and toolchain support.\n\n# Open source alternatives\n- **Alamofire**: A widely-used Swift library for HTTP networking, which may serve as an alternative to `KreeRequest` if HTTP functionalities are being utilized.\n- **Moya**: A Swift network abstraction layer that can be considered for handling networking tasks.", - "filename": "Package.swift", - "path": "Package.swift", - "directory": "", - "grade": 7, - "size": 1130, - "line_count": 32 -} \ No newline at end of file +**Grade: 7** + +# Bugs +- No explicit bugs were found in the code. However, compatibility with "swift-tools-version: 6.0" might need future validation as this version wasn't officially recognized up to October 2023. + +# Optimizations +- Consider updating `swift-tools-version` to a stable and widely recognized version (e.g., 5.7) to ensure compatibility with existing Swift toolchains. +- Adding more comments within the code to describe functionality and dependencies could enhance readability and maintainability. + +# Good points +- Proper use of Swift Package Manager manifest file structure. +- Comprehensive platform support including `iOS`, `macOS`, `tvOS`, `watchOS`, `driverKit`, `macCatalyst`, and `visionOS`. +- The package dependency is clearly defined with a specified source and versioning. + +# Summary +Overall, this `Package.swift` file adheres well to the standard Swift Package Manager format with a clear definition of products, dependencies, and targets. Compatibility considerations with tool versions could improve wider adoption and toolchain support. + +# Open source alternatives +- **Alamofire**: A widely-used Swift library for HTTP networking, which may serve as an alternative to `KreeRequest` if HTTP functionalities are being utilized. +- **Moya**: A Swift network abstraction layer that can be considered for handling networking tasks. \ No newline at end of file diff --git a/.reviews/Sources/SwiftDevRant/DevRant/DevRantApiError.swift.md b/.reviews/Sources/SwiftDevRant/DevRant/DevRantApiError.swift.md index df542e8..509365e 100644 --- a/.reviews/Sources/SwiftDevRant/DevRant/DevRantApiError.swift.md +++ b/.reviews/Sources/SwiftDevRant/DevRant/DevRantApiError.swift.md @@ -1,11 +1,21 @@ -{ - "extension": ".swift", - "source": "/// Represents an error coming directly from the devrant API.\npublic struct DevRantApiError: Swift.Error {\n public let message: String\n}\n\npublic extension DevRantApiError {\n struct CodingData: Decodable, Swift.Error {\n let error: String\n }\n}\n\npublic extension DevRantApiError.CodingData {\n var decoded: DevRantApiError {\n .init(message: error)\n }\n}\n", - "review": "7\n\n## Bugs\n- No immediate bugs are evident from the code provided.\n\n## Optimizations\n- Consider making `CodingData` struct also `public`, since it\u2019s declared inside a `public` struct but doesn't have default access level modifiers for its properties.\n- Include documentation comments for properties and methods to improve code readability and maintainability.\n- The `error` property in `CodingData` could be `public` if it needs to be accessed outside of the containing module.\n \n## Good points\n- Provides a clear and concise representation of an error by abstracting it into a struct.\n- Proper use of Swift\u2019s error protocol, adhering to Swift's error handling mechanisms.\n- The `decoded` computed property is a useful bridge to convert between different error representations.\n\n## Summary\nThe code is well-structured and achieves its goal of representing and decoding errors from the DevRant API effectively. However, it could benefit from a few access level adjustments and additional documentation for greater clarity and usability.\n\n## Open source alternatives\n- [Alamofire](https://github.com/Alamofire/Alamofire): While primarily a networking library, it includes error handling capabilities.\n- [Moya](https://github.com/Moya/Moya): A powerful networking abstraction layer that can be used with customizable error handling features.", - "filename": "DevRantApiError.swift", - "path": "Sources/SwiftDevRant/DevRant/DevRantApiError.swift", - "directory": "DevRant", - "grade": 7, - "size": 378, - "line_count": 17 -} \ No newline at end of file +7 + +## Bugs +- No immediate bugs are evident from the code provided. + +## Optimizations +- Consider making `CodingData` struct also `public`, since it’s declared inside a `public` struct but doesn't have default access level modifiers for its properties. +- Include documentation comments for properties and methods to improve code readability and maintainability. +- The `error` property in `CodingData` could be `public` if it needs to be accessed outside of the containing module. + +## Good points +- Provides a clear and concise representation of an error by abstracting it into a struct. +- Proper use of Swift’s error protocol, adhering to Swift's error handling mechanisms. +- The `decoded` computed property is a useful bridge to convert between different error representations. + +## Summary +The code is well-structured and achieves its goal of representing and decoding errors from the DevRant API effectively. However, it could benefit from a few access level adjustments and additional documentation for greater clarity and usability. + +## Open source alternatives +- [Alamofire](https://github.com/Alamofire/Alamofire): While primarily a networking library, it includes error handling capabilities. +- [Moya](https://github.com/Moya/Moya): A powerful networking abstraction layer that can be used with customizable error handling features. \ No newline at end of file diff --git a/.reviews/Sources/SwiftDevRant/DevRant/DevRantJSONCoder.swift.md b/.reviews/Sources/SwiftDevRant/DevRant/DevRantJSONCoder.swift.md index 31f0a01..a150ff8 100644 --- a/.reviews/Sources/SwiftDevRant/DevRant/DevRantJSONCoder.swift.md +++ b/.reviews/Sources/SwiftDevRant/DevRant/DevRantJSONCoder.swift.md @@ -1,11 +1,19 @@ -{ - "extension": ".swift", - "source": "import Foundation\nimport KreeRequest\n\nextension JSONEncoder {\n static let devRant: JSONEncoder = {\n let encoder = JSONEncoder()\n encoder.dateEncodingStrategy = .iso8601\n return encoder\n }()\n}\n\nextension JSONDecoder {\n static let devRant: JSONDecoder = {\n let decoder = JSONDecoder()\n decoder.dateDecodingStrategy = .iso8601WithOptionalFractionalSeconds\n return decoder\n }()\n}\n", - "review": "**8**\n\n## Bugs\n- The `JSONDecoder`'s `dateDecodingStrategy` set to `.iso8601WithOptionalFractionalSeconds` does not exist; it should be `.iso8601`. If a custom strategy for optional fractional seconds is needed, this needs to be implemented separately.\n\n## Optimizations\n- Consider adding error handling or logging to catch issues during JSON encoding/decoding.\n- If `dateDecodingStrategy` with optional fractional seconds is truly required, implement a custom decoding strategy.\n\n## Good points\n- Encapsulation of `JSONEncoder` and `JSONDecoder` configuration within static properties improves code reusability and centralizes configuration.\n- Use of the `.iso8601` date encoding/decoding strategy aligns with a standardized format for date/time.\n\n## Summary\nThis piece of code provides a well-structured approach through extensions to configure `JSONEncoder` and `JSONDecoder` with ISO8601 date strategies, which are commonly used for APIs. The method for encoding is correctly standardized, but there's an issue with using a non-existent decoding strategy for handling optional fractional seconds. The structure itself is clean, maintains good practices for code reusability, and is set up for potentially easy maintenance. Adding error handling could catch configuration issues during actual encoding/decoding operations.\n\n## Open source alternatives\n- SwiftyJSON: A popular Swift library to deal with JSON data with additional features and error handling.\n- CodableAlamofire: Provides extensions for Alamofire to handle JSON encoding/decoding in a type-safe manner with Codable.", - "filename": "DevRantJSONCoder.swift", - "path": "Sources/SwiftDevRant/DevRant/DevRantJSONCoder.swift", - "directory": "DevRant", - "grade": 8, - "size": 430, - "line_count": 19 -} \ No newline at end of file +**8** + +## Bugs +- The `JSONDecoder`'s `dateDecodingStrategy` set to `.iso8601WithOptionalFractionalSeconds` does not exist; it should be `.iso8601`. If a custom strategy for optional fractional seconds is needed, this needs to be implemented separately. + +## Optimizations +- Consider adding error handling or logging to catch issues during JSON encoding/decoding. +- If `dateDecodingStrategy` with optional fractional seconds is truly required, implement a custom decoding strategy. + +## Good points +- Encapsulation of `JSONEncoder` and `JSONDecoder` configuration within static properties improves code reusability and centralizes configuration. +- Use of the `.iso8601` date encoding/decoding strategy aligns with a standardized format for date/time. + +## Summary +This piece of code provides a well-structured approach through extensions to configure `JSONEncoder` and `JSONDecoder` with ISO8601 date strategies, which are commonly used for APIs. The method for encoding is correctly standardized, but there's an issue with using a non-existent decoding strategy for handling optional fractional seconds. The structure itself is clean, maintains good practices for code reusability, and is set up for potentially easy maintenance. Adding error handling could catch configuration issues during actual encoding/decoding operations. + +## Open source alternatives +- SwiftyJSON: A popular Swift library to deal with JSON data with additional features and error handling. +- CodableAlamofire: Provides extensions for Alamofire to handle JSON encoding/decoding in a type-safe manner with Codable. \ No newline at end of file diff --git a/.reviews/Sources/SwiftDevRant/DevRantRequest.swift.md b/.reviews/Sources/SwiftDevRant/DevRantRequest.swift.md index 3b4743c..d83ece6 100644 --- a/.reviews/Sources/SwiftDevRant/DevRantRequest.swift.md +++ b/.reviews/Sources/SwiftDevRant/DevRantRequest.swift.md @@ -1,11 +1,24 @@ -{ - "extension": ".swift", - "source": "import Foundation\nimport KreeRequest\n\npublic struct DevRantRequest {\n let request: KreeRequest\n let backend = DevRantBackend()\n \n public init(requestLogger: Logger) {\n self.request = KreeRequest(encoder: .devRant, decoder: .devRant, logger: requestLogger)\n }\n \n private func makeConfig(_ method: KreeRequest.Method, path: String, urlParameters: [String: String] = [:], headers: [String: String] = [:], token: AuthToken? = nil) -> KreeRequest.Config {\n var urlParameters = urlParameters\n urlParameters[\"app\"] = \"3\"\n \n if let token {\n urlParameters[\"token_id\"] = String(token.id)\n urlParameters[\"token_key\"] = token.key\n urlParameters[\"user_id\"] = String(token.userId)\n }\n \n var headers = headers\n headers[\"Content-Type\"] = \"application/x-www-form-urlencoded\"\n \n return .init(method: method, backend: backend, path: path, urlParameters: urlParameters, headers: headers)\n }\n \n private func makeMultipartConfig(_ method: KreeRequest.Method, path: String, parameters: [String: String] = [:], boundary: String, headers: [String: String] = [:], token: AuthToken? = nil) -> KreeRequest.Config {\n var parameters = parameters\n parameters[\"app\"] = \"3\"\n \n if let token {\n parameters[\"token_id\"] = String(token.id)\n parameters[\"token_key\"] = token.key\n parameters[\"user_id\"] = String(token.userId)\n }\n \n var headers = headers\n headers[\"Content-Type\"] = \"multipart/form-data; boundary=\\(boundary)\"\n \n return .init(method: method, backend: backend, path: path, urlParameters: parameters, headers: headers)\n }\n \n private func multipartBody(parameters: [String: String], boundary: String, imageData: Data?) -> Data {\n var body = Data()\n \n let boundaryPrefix = \"--\\(boundary)\\r\\n\"\n \n for (key, value) in parameters {\n body.appendString(boundaryPrefix)\n body.appendString(\"Content-Disposition: form-data; name=\\\"\\(key)\\\"\\r\\n\\r\\n\")\n body.appendString(\"\\(value)\\r\\n\")\n }\n \n if let imageData {\n //TODO: the image is not always jpeg. not sure if it matters here.\n body.appendString(boundaryPrefix)\n body.appendString(\"Content-Disposition: form-data; name=\\\"image\\\"; filename=\\\"image.jpeg\\\"\\r\\n\")\n body.appendString(\"Content-Type: image/jpeg\\r\\n\\r\\n\")\n body.append(imageData)\n body.appendString(\"\\r\\n\")\n }\n \n body.appendString(\"--\".appending(boundary.appending(\"--\")))\n \n return body\n }\n \n /// For endpoints with the POST method in the devRant API the url parameters need to be passed as a string in the http body rather than in the URL.\n /// The url encoding works but it might also work with other encodings like json or multipart form data.\n private func stringBody(fromUrlParameters urlParameters: [String: String]) -> String {\n String(KreeRequest.urlEncodedQueryString(from: urlParameters).dropFirst()) // dropping the first character \"?\"\n }\n}\n\npublic extension DevRantRequest {\n func logIn(username: String, password: String) async throws -> AuthToken {\n var parameters: [String: String] = [:]\n parameters[\"app\"] = \"3\"\n parameters[\"username\"] = username\n parameters[\"password\"] = password\n \n let config = makeConfig(.post, path: \"users/auth-token\")\n \n let body = stringBody(fromUrlParameters: parameters)\n \n let response: AuthToken.CodingData.Container = try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self)\n \n return response.auth_token.decoded\n }\n \n /// Gets a personalized feed of rants.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - limit: The number of rants to get for pagination.\n /// - skip: How many rants to skip for pagination.\n /// - sessionHash: Pass the session hash value from the last rant feed response or `nil` if calling for the first time.\n func getRantFeed(token: AuthToken, sort: RantFeed.Sort = .algorithm, limit: Int = 20, skip: Int, sessionHash: String?) async throws -> RantFeed {\n var parameters: [String: String] = [:]\n \n parameters[\"sort\"] = switch sort {\n case .algorithm: \"algo\"\n case .recent: \"recent\"\n case .top: \"top\"\n }\n \n switch sort {\n case .top(range: let range):\n parameters[\"range\"] = switch range {\n case .day: \"day\"\n case .week: \"week\"\n case .month: \"month\"\n case .all: \"all\"\n }\n default:\n break\n }\n \n parameters[\"limit\"] = String(limit)\n parameters[\"skip\"] = String(skip)\n parameters[\"prev_set\"] = sessionHash\n \n parameters[\"plat\"] = \"1\" // I don't know wtf that is.\n parameters[\"nari\"] = \"1\" // I don't know wtf that is.\n \n let config = makeConfig(.get, path: \"devrant/rants\", urlParameters: parameters, token: token)\n \n let response: RantFeed.CodingData = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)\n \n return response.decoded\n }\n \n /// Gets all weeklies as a list.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n func getWeeklies(token: AuthToken) async throws -> [Weekly] {\n let config = makeConfig(.get, path: \"devrant/weekly-list\", token: token)\n \n let response: Weekly.CodingData.List = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)\n \n return response.weeks.map(\\.decoded)\n }\n \n /// Gets a specific week's weekly rants.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - week: The number of the week. Pass `nil` to get the latest week's rants.\n /// - limit: The number of rants for pagination.\n /// - skip: How many rants to skip for pagination.\n func getWeeklyRants(token: AuthToken, week: Int?, limit: Int = 20, skip: Int) async throws -> RantFeed {\n var parameters: [String: String] = [:]\n \n parameters[\"week\"] = week.flatMap { String($0) }\n parameters[\"limit\"] = String(limit)\n parameters[\"skip\"] = String(skip)\n \n let config = makeConfig(.get, path: \"devrant/weekly-rants\", urlParameters: parameters, token: token)\n \n let response: RantFeed.CodingData = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)\n \n return response.decoded\n }\n \n /// Gets the list of notifications and numbers for each notification type.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - lastChecked: Pass the value from the last response or `nil`.\n func getNotificationFeed(token: AuthToken, lastChecked: Date?, category: NotificationFeed.Category) async throws -> NotificationFeed {\n var parameters: [String: String] = [:]\n \n parameters[\"last_time\"] = lastChecked.flatMap { String(Int($0.timeIntervalSince1970)) } ?? \"0\"\n parameters[\"ext_prof\"] = \"1\" // I don't know wtf that is.\n \n let config = makeConfig(.get, path: \"users/me/notif-feed\\(category.rawValue)\", urlParameters: parameters, token: token)\n \n let response: NotificationFeed.CodingData.Container = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)\n \n return response.data.decoded\n }\n \n /// Gets a single rant and its comments.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - rantId: The id of the rant.\n /// - lastCommentId: Only fetch the comments which were posted after the one corresponding to this id. Pass `nil` to get all comments.\n func getRant(token: AuthToken, rantId: Int, lastCommentId: Int? = nil) async throws -> (rant: Rant, comments: [Comment]) {\n var parameters: [String: String] = [:]\n\n parameters[\"last_comment_id\"] = lastCommentId.flatMap { String($0) }\n \n let config = makeConfig(.get, path: \"devrant/rants/\\(rantId)\", urlParameters: parameters, token: token)\n \n struct Response: Codable {\n let rant: Rant.CodingData\n let comments: [Comment.CodingData]?\n }\n \n let response: Response = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)\n \n return (rant: response.rant.decoded, comments: response.comments?.map(\\.decoded) ?? [])\n }\n \n /// Gets a single comment.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - commentId: The id of the comment.\n func getComment(token: AuthToken, commentId: Int) async throws -> Comment {\n let config = makeConfig(.get, path: \"comments/\\(commentId)\", token: token)\n \n let response: Comment.CodingData = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)\n \n return response.decoded\n }\n \n /// Gets the id of a user.\n ///\n /// - Parameters:\n /// - username: The username of the user.\n func getUserId(username: String) async throws -> Int {\n var parameters: [String: String] = [:]\n\n parameters[\"username\"] = username\n \n let config = makeConfig(.get, path: \"get-user-id\", urlParameters: parameters)\n \n struct Response: Decodable {\n let user_id: Int\n }\n \n let response: Response = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)\n \n return response.user_id\n }\n \n /// Gets a user's profile data.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - userId: The id of the user.\n /// - contentType: The type of content created by the user to be fetched.\n /// - skip: The number of content items to skip for pagination.\n func getProfile(token: AuthToken, userId: Int, contentType: Profile.ContentType, skip: Int) async throws -> Profile {\n var parameters: [String: String] = [:]\n\n parameters[\"skip\"] = String(skip)\n parameters[\"content\"] = contentType.rawValue\n \n let config = makeConfig(.get, path: \"users/\\(userId)\", urlParameters: parameters, token: token)\n \n let response: Profile.CodingData.Container = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)\n \n return response.decoded\n }\n \n /// Votes on a rant.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - rantId: The id of the rant.\n /// - vote: The vote for this rant.\n func voteOnRant(token: AuthToken, rantId: Int, vote: VoteState, downvoteReason: DownvoteReason = .notForMe) async throws -> Rant {\n let config = makeConfig(.post, path: \"devrant/rants/\\(rantId)/vote\", token: token)\n \n var parameters = config.urlParameters\n\n parameters[\"vote\"] = String(vote.rawValue)\n \n if vote == .downvoted {\n parameters[\"reason\"] = String(downvoteReason.rawValue)\n }\n \n let body = stringBody(fromUrlParameters: parameters)\n \n struct Response: Codable {\n let rant: Rant.CodingData\n }\n \n let response: Response = try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self)\n \n return response.rant.decoded\n }\n \n /// Votes on a comment.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - commentId: The id of the comment.\n /// - vote: The vote for this comment.\n func voteOnComment(token: AuthToken, commentId: Int, vote: VoteState, downvoteReason: DownvoteReason = .notForMe) async throws -> Comment {\n let config = makeConfig(.post, path: \"comments/\\(commentId)/vote\", token: token)\n \n var parameters = config.urlParameters\n\n parameters[\"vote\"] = String(vote.rawValue)\n \n if vote == .downvoted {\n parameters[\"reason\"] = String(downvoteReason.rawValue)\n }\n \n let body = stringBody(fromUrlParameters: parameters)\n \n struct Response: Decodable {\n let comment: Comment.CodingData\n }\n \n let response: Response = try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self)\n \n return response.comment.decoded\n }\n \n /// Updates the user profile.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - about: The user's about text.\n /// - skills: The user's list of skills.\n /// - github: The user's GitHub link.\n /// - location: The user's geographic location.\n /// - website: The user's personal website.\n func editUserProfile(token: AuthToken, about: String, skills: String, github: String, location: String, website: String) async throws {\n let config = makeConfig(.post, path: \"users/me/edit-profile\", token: token)\n \n var parameters = config.urlParameters\n\n parameters[\"profile_about\"] = about\n parameters[\"profile_skills\"] = skills\n parameters[\"profile_github\"] = github\n parameters[\"profile_location\"] = location\n parameters[\"profile_website\"] = website\n \n let body = stringBody(fromUrlParameters: parameters)\n \n try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self)\n }\n \n /// Creates and posts a rant.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - kind: The type of rant.\n /// - text: The text content of the rant.\n /// - tags: The rant's associated tags.\n /// - image: An image to attach to the rant.\n /// - imageConversion: The image conversion methods for unsupported image formats.\n /// - Returns:\n /// The id of the posted rant.\n func postRant(token: AuthToken, kind: Rant.Kind, text: String, tags: String, image: Data?, imageConversion: [ImageDataConverter] = [.unsupportedToJpeg]) async throws -> Int {\n let boundary = UUID().uuidString\n \n let config = makeMultipartConfig(.post, path: \"devrant/rants\", boundary: boundary, token: token)\n \n var parameters = config.urlParameters\n\n parameters[\"rant\"] = text\n parameters[\"tags\"] = tags\n parameters[\"type\"] = String(kind.rawValue)\n \n let convertedImage = image.flatMap { imageConversion.convert($0) }\n \n let bodyData = multipartBody(parameters: parameters, boundary: boundary, imageData: convertedImage)\n \n struct Response: Decodable {\n let rant_id: Int\n }\n \n let response: Response = try await request.requestJson(config: config, data: bodyData, apiError: DevRantApiError.CodingData.self)\n \n return response.rant_id\n }\n \n /// Deletes a rant.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - rantId: The id of the rant.\n func deleteRant(token: AuthToken, rantId: Int) async throws {\n let config = makeConfig(.delete, path: \"devrant/rants/\\(rantId)\", token: token)\n \n try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)\n }\n \n /// Sets or unsets a rant as a favorite.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - rantId: The id of the rant.\n /// - favorite: `true` sets the rant as favorite and `false` sets it as not favorite.\n func favoriteRant(token: AuthToken, rantId: Int, favorite: Bool) async throws {\n let favoritePath = favorite ? \"favorite\" : \"unfavorite\"\n \n let config = makeConfig(.post, path: \"devrant/rants/\\(rantId)/\\(favoritePath)\", token: token)\n \n let parameters = config.urlParameters\n \n let body = stringBody(fromUrlParameters: parameters)\n \n try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self)\n }\n \n /// Edits a posted rant.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - rantId: The id of the rant.\n /// - text: The text content of the rant.\n /// - tags: The rants's associated tags.\n /// - image: An image to attach to the rant.\n func editRant(token: AuthToken, rantId: Int, text: String, tags: String, image: Data?, imageConversion: [ImageDataConverter] = [.unsupportedToJpeg]) async throws {\n let boundary = UUID().uuidString\n \n let config = makeMultipartConfig(.post, path: \"devrant/rants/\\(rantId)\", boundary: boundary, token: token)\n \n var parameters = config.urlParameters\n\n parameters[\"rant\"] = text\n parameters[\"tags\"] = tags\n \n let convertedImage = image.flatMap { imageConversion.convert($0) }\n \n let bodyData = multipartBody(parameters: parameters, boundary: boundary, imageData: convertedImage)\n \n try await request.requestJson(config: config, data: bodyData, apiError: DevRantApiError.CodingData.self)\n }\n \n /// Creates and posts a comment for a specific rant.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - rantId: The id of the rant that this comment should be posted for.\n /// - text: The text content of the comment.\n /// - image: An image to attach to the comment.\n func postComment(token: AuthToken, rantId: Int, text: String, image: Data?, imageConversion: [ImageDataConverter] = [.unsupportedToJpeg]) async throws {\n let boundary = UUID().uuidString\n \n let config = makeMultipartConfig(.post, path: \"devrant/rants/\\(rantId)/comments\", boundary: boundary, token: token)\n \n var parameters = config.urlParameters\n\n parameters[\"comment\"] = text\n \n let convertedImage = image.flatMap { imageConversion.convert($0) }\n \n let bodyData = multipartBody(parameters: parameters, boundary: boundary, imageData: convertedImage)\n \n try await request.requestJson(config: config, data: bodyData, apiError: DevRantApiError.CodingData.self)\n }\n \n /// Edits a posted comment.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - commentId: The id of the comment.\n /// - text: The text content of the comment.\n /// - image: An image to attach to the comment.\n func editComment(token: AuthToken, commentId: Int, text: String, image: Data?, imageConversion: [ImageDataConverter] = [.unsupportedToJpeg]) async throws {\n let boundary = UUID().uuidString\n \n let config = makeMultipartConfig(.post, path: \"comments/\\(commentId)\", boundary: boundary, token: token)\n \n var parameters = config.urlParameters\n\n parameters[\"comment\"] = text\n \n let convertedImage = image.flatMap { imageConversion.convert($0) }\n \n let bodyData = multipartBody(parameters: parameters, boundary: boundary, imageData: convertedImage)\n \n try await request.requestJson(config: config, data: bodyData, apiError: DevRantApiError.CodingData.self)\n }\n \n /// Deletes a comment.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - commentId: The id of the comment.\n func deleteComment(token: AuthToken, commentId: Int) async throws {\n let config = makeConfig(.delete, path: \"comments/\\(commentId)\", token: token)\n \n try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)\n }\n \n /// Marks all notifications as read.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n func markAllNotificationsAsRead(token: AuthToken) async throws {\n let config = makeConfig(.delete, path: \"users/me/notif-feed\", token: token)\n \n try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)\n }\n \n /// Subscribes to a user.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - userId: The id of the user to subscribe to.\n func subscribeToUser(token: AuthToken, userId: Int) async throws {\n let config = makeConfig(.post, path: \"users/\\(userId)/subscribe\", token: token)\n \n let parameters = config.urlParameters\n \n let body = stringBody(fromUrlParameters: parameters)\n \n try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self)\n }\n \n /// Unsubscribes from a user.\n ///\n /// - Parameters:\n /// - token: The token from the `logIn` call response.\n /// - userId: The id of the user to unsubscribe from.\n func unsubscribeFromUser(token: AuthToken, userId: Int) async throws {\n let config = makeConfig(.delete, path: \"users/\\(userId)/subscribe\", token: token)\n \n try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)\n }\n}\n\nprivate extension Data {\n mutating func appendString(_ string: String) {\n let data = string.data(using: .utf8, allowLossyConversion: false)\n append(data!)\n }\n}\n", - "review": "**8**\n\n# Bugs\n- **Image Format Assumption:** In the `multipartBody` function, the image is assumed to always be a JPEG, which might not be the case leading to incorrect content types.\n- **Unnecessary \"plat\" and \"nari\" Parameters:** The `getRantFeed` function contains parameters whose purpose is unknown, which may affect the API call results.\n- **Unsafe Force Unwrapping:** In `appendString`, force unwrapping data from string conversion can lead to runtime crashes if `nil`.\n\n# Optimizations\n- **Error Handling:** Implement robust error handling instead of force unwrapping in `appendString`.\n- **String Valuation Improvements:** Improve the boundary string concatenation for better readability.\n- **Use Codable Protocols:** Consider utilizing Swift's `Codable` protocol more extensively to simplify JSON parsing and mapping.\n\n# Good Points\n- **Asynchronous Functionality:** Makes good use of Swift\u2019s async/await features for handling asynchronous operations.\n- **Clean Structuring:** The code is neatly structured with clear sections for different API calls.\n- **Encapsulation:** The logic for request configuration is encapsulated in private methods reducing code duplication.\n\n# Summary\nThe code is well-structured, with a good use of Swift's modern async/await pattern facilitating asynchronous network requests. The functionality is well-encapsulated in functions, maintaining a clean code structure. However, some assumptions, especially regarding image data handing and forced unwrapping, might lead to runtime issues. Adding comprehensive error checking and handling could be beneficial. Overall, the code serves its purpose well but could improve in terms of robustness.\n\n# Open source alternatives\n- **Alamofire**: A robust library for network requests in Swift, providing many utilities out of the box.\n- **Moya**: Built on top of Alamofire, Moya provides an abstraction over network requests, simplifying API interaction.\n- **HTTPNetworking in SwiftNIO**: For more control and performance in building HTTP clients, especially for asynchronous I/O operations.", - "filename": "DevRantRequest.swift", - "path": "Sources/SwiftDevRant/DevRantRequest.swift", - "directory": "SwiftDevRant", - "grade": 8, - "size": 22105, - "line_count": 531 -} \ No newline at end of file +**8** + +# Bugs +- **Image Format Assumption:** In the `multipartBody` function, the image is assumed to always be a JPEG, which might not be the case leading to incorrect content types. +- **Unnecessary "plat" and "nari" Parameters:** The `getRantFeed` function contains parameters whose purpose is unknown, which may affect the API call results. +- **Unsafe Force Unwrapping:** In `appendString`, force unwrapping data from string conversion can lead to runtime crashes if `nil`. + +# Optimizations +- **Error Handling:** Implement robust error handling instead of force unwrapping in `appendString`. +- **String Valuation Improvements:** Improve the boundary string concatenation for better readability. +- **Use Codable Protocols:** Consider utilizing Swift's `Codable` protocol more extensively to simplify JSON parsing and mapping. + +# Good Points +- **Asynchronous Functionality:** Makes good use of Swift’s async/await features for handling asynchronous operations. +- **Clean Structuring:** The code is neatly structured with clear sections for different API calls. +- **Encapsulation:** The logic for request configuration is encapsulated in private methods reducing code duplication. + +# Summary +The code is well-structured, with a good use of Swift's modern async/await pattern facilitating asynchronous network requests. The functionality is well-encapsulated in functions, maintaining a clean code structure. However, some assumptions, especially regarding image data handing and forced unwrapping, might lead to runtime issues. Adding comprehensive error checking and handling could be beneficial. Overall, the code serves its purpose well but could improve in terms of robustness. + +# Open source alternatives +- **Alamofire**: A robust library for network requests in Swift, providing many utilities out of the box. +- **Moya**: Built on top of Alamofire, Moya provides an abstraction over network requests, simplifying API interaction. +- **HTTPNetworking in SwiftNIO**: For more control and performance in building HTTP clients, especially for asynchronous I/O operations. \ No newline at end of file