From 14c7dc38d347557c1fe337400cf798e2e898b3d6 Mon Sep 17 00:00:00 2001 From: Daniel Saidi Date: Fri, 4 Oct 2024 16:34:33 +0200 Subject: [PATCH] Add API model to simplify conforming to Codable and Sendable --- RELEASE_NOTES.md | 6 +++ Sources/ApiKit/ApiClient.swift | 2 +- Sources/ApiKit/ApiEnvironment.swift | 4 +- Sources/ApiKit/ApiError.swift | 2 +- Sources/ApiKit/ApiModel.swift | 14 +++++++ Sources/ApiKit/ApiRequestData.swift | 2 +- Sources/ApiKit/ApiResult.swift | 2 +- Sources/ApiKit/ApiRoute.swift | 4 +- .../ApiKit/Extensions/String+UrlEncode.swift | 2 +- Sources/ApiKit/Http/HttpMethod.swift | 2 +- .../TheMovieDb/TheMovieDb+Environment.swift | 2 +- .../TheMovieDb/TheMovieDb+Models.swift | 14 +++---- .../TheMovieDb/TheMovieDb+Route.swift | 2 +- .../Integrations/TheMovieDb/TheMovieDb.swift | 2 +- .../Integrations/Yelp/Yelp+Environment.swift | 2 +- .../Integrations/Yelp/Yelp+Models.swift | 42 +++++++++---------- .../ApiKit/Integrations/Yelp/Yelp+Route.swift | 2 +- Sources/ApiKit/Integrations/Yelp/Yelp.swift | 2 +- Tests/ApiKitTests/ApiClientTests.swift | 2 +- Tests/ApiKitTests/ApiEnvironmentTests.swift | 2 +- Tests/ApiKitTests/ApiRequestDataTests.swift | 2 +- Tests/ApiKitTests/ApiRouteTests.swift | 2 +- Tests/ApiKitTests/HttpMethodTests.swift | 2 +- Tests/ApiKitTests/TestTypes.swift | 2 +- 24 files changed, 70 insertions(+), 50 deletions(-) create mode 100644 Sources/ApiKit/ApiModel.swift diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index bc133f9..7c4ed52 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,6 +6,12 @@ Until then, breaking changes can happen in any version, and deprecated features +## 0.9.2 + +This version adds an `ApiModel` protocol that simplifies conforming to `Codable` and `Sendable`. + + + ## 0.9.1 This version adjusts HTTP status code terminology. diff --git a/Sources/ApiKit/ApiClient.swift b/Sources/ApiKit/ApiClient.swift index e10d5ba..f7aa367 100644 --- a/Sources/ApiKit/ApiClient.swift +++ b/Sources/ApiKit/ApiClient.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-25. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation diff --git a/Sources/ApiKit/ApiEnvironment.swift b/Sources/ApiKit/ApiEnvironment.swift index 2bf9acb..a5d4086 100644 --- a/Sources/ApiKit/ApiEnvironment.swift +++ b/Sources/ApiKit/ApiEnvironment.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-24. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation @@ -21,7 +21,7 @@ import Foundation /// headers and query parameters they need. Environments can /// define global headers and query parameters, while routes /// can define route-specific ones. -public protocol ApiEnvironment: ApiRequestData { +public protocol ApiEnvironment: ApiRequestData, Sendable { /// The base URL of the environment. var url: String { get } diff --git a/Sources/ApiKit/ApiError.swift b/Sources/ApiKit/ApiError.swift index ded0de0..d81f3d1 100644 --- a/Sources/ApiKit/ApiError.swift +++ b/Sources/ApiKit/ApiError.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-25. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation diff --git a/Sources/ApiKit/ApiModel.swift b/Sources/ApiKit/ApiModel.swift new file mode 100644 index 0000000..b22d204 --- /dev/null +++ b/Sources/ApiKit/ApiModel.swift @@ -0,0 +1,14 @@ +// +// ApiModel.swift +// ApiKit +// +// Created by Daniel Saidi on 2024-10-04. +// Copyright © 2024 Daniel Saidi. All rights reserved. +// + +/// This protocol can be implemented by API-specific models. +/// +/// This protocol makes a type conform to both `Codable` and +/// `Sendable`, which simplifies conforming to both when you +/// create your API models. +public protocol ApiModel: Codable, Sendable {} diff --git a/Sources/ApiKit/ApiRequestData.swift b/Sources/ApiKit/ApiRequestData.swift index a1b517b..5fd947b 100644 --- a/Sources/ApiKit/ApiRequestData.swift +++ b/Sources/ApiKit/ApiRequestData.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-28. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation diff --git a/Sources/ApiKit/ApiResult.swift b/Sources/ApiKit/ApiResult.swift index ed5469c..a1fa725 100644 --- a/Sources/ApiKit/ApiResult.swift +++ b/Sources/ApiKit/ApiResult.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-25. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation diff --git a/Sources/ApiKit/ApiRoute.swift b/Sources/ApiKit/ApiRoute.swift index 97cc748..d1d3a39 100644 --- a/Sources/ApiKit/ApiRoute.swift +++ b/Sources/ApiKit/ApiRoute.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-24. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation @@ -27,7 +27,7 @@ import Foundation /// headers and query parameters they need. Environments can /// define global headers and query parameters, while routes /// can define route-specific ones. -public protocol ApiRoute: ApiRequestData { +public protocol ApiRoute: Sendable, ApiRequestData { /// The HTTP method to use for the route. var httpMethod: HttpMethod { get } diff --git a/Sources/ApiKit/Extensions/String+UrlEncode.swift b/Sources/ApiKit/Extensions/String+UrlEncode.swift index fa469cc..79de18f 100644 --- a/Sources/ApiKit/Extensions/String+UrlEncode.swift +++ b/Sources/ApiKit/Extensions/String+UrlEncode.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-24. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // // https://danielsaidi.com/blog/2020/06/04/string-urlencode // diff --git a/Sources/ApiKit/Http/HttpMethod.swift b/Sources/ApiKit/Http/HttpMethod.swift index dcbc7f8..265d170 100644 --- a/Sources/ApiKit/Http/HttpMethod.swift +++ b/Sources/ApiKit/Http/HttpMethod.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-24. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation diff --git a/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb+Environment.swift b/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb+Environment.swift index a7dd116..4d13c9c 100644 --- a/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb+Environment.swift +++ b/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb+Environment.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-08-17. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation diff --git a/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb+Models.swift b/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb+Models.swift index 3af62e4..064fcb9 100644 --- a/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb+Models.swift +++ b/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb+Models.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-08-17. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation @@ -11,8 +11,8 @@ import Foundation public extension TheMovieDb { /// This type represents a TheMovieDb movie. - struct Movie: Codable, Identifiable { - + struct Movie: ApiModel, Identifiable { + public let id: Int public let imdbId: String? public let title: String @@ -75,15 +75,15 @@ public extension TheMovieDb { } /// This type represents a TheMovieDb movie genre. - struct MovieGenre: Codable, Identifiable { - + struct MovieGenre: ApiModel, Identifiable { + public let id: Int public let name: String } /// This type represents a TheMovieDb pagination result. - struct MoviesPaginationResult: Codable { - + struct MoviesPaginationResult: ApiModel { + public let page: Int public let results: [Movie] public let totalPages: Int diff --git a/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb+Route.swift b/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb+Route.swift index 96b5f06..9f19e6f 100644 --- a/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb+Route.swift +++ b/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb+Route.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-08-17. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation diff --git a/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb.swift b/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb.swift index 59686c4..1921fd7 100644 --- a/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb.swift +++ b/Sources/ApiKit/Integrations/TheMovieDb/TheMovieDb.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-28. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation diff --git a/Sources/ApiKit/Integrations/Yelp/Yelp+Environment.swift b/Sources/ApiKit/Integrations/Yelp/Yelp+Environment.swift index 5a9bae7..3ba37dd 100644 --- a/Sources/ApiKit/Integrations/Yelp/Yelp+Environment.swift +++ b/Sources/ApiKit/Integrations/Yelp/Yelp+Environment.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-08-17. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation diff --git a/Sources/ApiKit/Integrations/Yelp/Yelp+Models.swift b/Sources/ApiKit/Integrations/Yelp/Yelp+Models.swift index 24dc354..2be7024 100644 --- a/Sources/ApiKit/Integrations/Yelp/Yelp+Models.swift +++ b/Sources/ApiKit/Integrations/Yelp/Yelp+Models.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-08-17. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation @@ -11,8 +11,8 @@ import Foundation public extension Yelp { /// This type represents a Yelp restaurant (business). - struct Restaurant: Codable { - + struct Restaurant: ApiModel { + public let id: String public let alias: String? public let name: String? @@ -54,21 +54,21 @@ public extension Yelp { /// This type represents a Yelp restaurant category. - struct RestaurantCategory: Codable { - + struct RestaurantCategory: ApiModel { + public let title: String } /// This type represents Yelp restaurant coordinates. - struct RestaurantCoordinates: Codable { - + struct RestaurantCoordinates: ApiModel { + public let latitude: Double? public let longitude: Double? } /// This type represents a Yelp restaurant opening hours. - struct RestaurantHour: Codable { - + struct RestaurantHour: ApiModel { + public let isOvernight: Bool public let start: String public let end: String @@ -83,8 +83,8 @@ public extension Yelp { } /// This type represents a Yelp restaurant opening hour. - struct RestaurantHours: Codable { - + struct RestaurantHours: ApiModel { + public let type: String public let isOpenNow: Bool public let open: [RestaurantHour] @@ -97,8 +97,8 @@ public extension Yelp { } /// This type represents a Yelp restaurant location. - struct RestaurantLocation: Codable { - + struct RestaurantLocation: ApiModel { + public let displayAddress: [String] enum CodingKeys: String, CodingKey { @@ -107,8 +107,8 @@ public extension Yelp { } /// This type represents a Yelp restaurant review. - struct RestaurantReview: Codable { - + struct RestaurantReview: ApiModel { + public let id: String public let url: String? public let text: String? @@ -117,8 +117,8 @@ public extension Yelp { } /// This type represents a Yelp restaurant review user. - struct RestaurantReviewUser: Codable { - + struct RestaurantReviewUser: ApiModel { + public let id: String public let name: String? public let imageUrl: String? @@ -137,8 +137,8 @@ public extension Yelp { } /// This type represents Yelp search parameters. - struct SearchParams { - + struct SearchParams: Sendable { + public init( skip: Int, take: Int, @@ -155,14 +155,14 @@ public extension Yelp { self.openingHours = openingHours } - public enum BudgetLevel: String { + public enum BudgetLevel: String, Sendable { case level1 = "1" case level2 = "2" case level3 = "3" case level4 = "4" } - public enum OpeningHours: String { + public enum OpeningHours: String, Sendable { case openNow case showAll } diff --git a/Sources/ApiKit/Integrations/Yelp/Yelp+Route.swift b/Sources/ApiKit/Integrations/Yelp/Yelp+Route.swift index 76aa5ae..7900c21 100644 --- a/Sources/ApiKit/Integrations/Yelp/Yelp+Route.swift +++ b/Sources/ApiKit/Integrations/Yelp/Yelp+Route.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-08-17. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation diff --git a/Sources/ApiKit/Integrations/Yelp/Yelp.swift b/Sources/ApiKit/Integrations/Yelp/Yelp.swift index 90c3a03..b079ff1 100644 --- a/Sources/ApiKit/Integrations/Yelp/Yelp.swift +++ b/Sources/ApiKit/Integrations/Yelp/Yelp.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-28. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import Foundation diff --git a/Tests/ApiKitTests/ApiClientTests.swift b/Tests/ApiKitTests/ApiClientTests.swift index 6977431..b69c324 100644 --- a/Tests/ApiKitTests/ApiClientTests.swift +++ b/Tests/ApiKitTests/ApiClientTests.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-24. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import ApiKit diff --git a/Tests/ApiKitTests/ApiEnvironmentTests.swift b/Tests/ApiKitTests/ApiEnvironmentTests.swift index 4fae4b6..f7dcdbf 100644 --- a/Tests/ApiKitTests/ApiEnvironmentTests.swift +++ b/Tests/ApiKitTests/ApiEnvironmentTests.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-25. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import XCTest diff --git a/Tests/ApiKitTests/ApiRequestDataTests.swift b/Tests/ApiKitTests/ApiRequestDataTests.swift index 321b70f..edc1cca 100644 --- a/Tests/ApiKitTests/ApiRequestDataTests.swift +++ b/Tests/ApiKitTests/ApiRequestDataTests.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-28. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import ApiKit diff --git a/Tests/ApiKitTests/ApiRouteTests.swift b/Tests/ApiKitTests/ApiRouteTests.swift index d341a7d..aa88e42 100644 --- a/Tests/ApiKitTests/ApiRouteTests.swift +++ b/Tests/ApiKitTests/ApiRouteTests.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-24. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import XCTest diff --git a/Tests/ApiKitTests/HttpMethodTests.swift b/Tests/ApiKitTests/HttpMethodTests.swift index 8c74a7b..0843a8b 100644 --- a/Tests/ApiKitTests/HttpMethodTests.swift +++ b/Tests/ApiKitTests/HttpMethodTests.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-24. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import ApiKit diff --git a/Tests/ApiKitTests/TestTypes.swift b/Tests/ApiKitTests/TestTypes.swift index f0b566a..352746f 100644 --- a/Tests/ApiKitTests/TestTypes.swift +++ b/Tests/ApiKitTests/TestTypes.swift @@ -3,7 +3,7 @@ // ApiKit // // Created by Daniel Saidi on 2023-03-28. -// Copyright © 2023 Daniel Saidi. All rights reserved. +// Copyright © 2023-2024 Daniel Saidi. All rights reserved. // import ApiKit