to avoid duplicate code
Showing 2 of 7 files from the diff.
Other files ignored by Codecov
docs/Classes.html
has changed.
docs/docsets/.tgz
has changed.
docs/Classes/DataService.html
has changed.
@@ -8,29 +8,23 @@
Loading
8 | 8 | import Foundation |
|
9 | 9 | import CoreLocation |
|
10 | 10 | ||
11 | - | struct Parks: Decodable { |
|
12 | - | let total: String |
|
13 | - | let data: [Park] |
|
14 | - | } |
|
15 | - | ||
16 | - | struct Alerts: Decodable { |
|
17 | - | let total: String |
|
18 | - | let data: [Alert] |
|
11 | + | enum DataServiceEndpoint: String { |
|
12 | + | case parks = "/parks" |
|
13 | + | case alerts = "/alerts" |
|
14 | + | case newsRelease = "/newsreleases" |
|
15 | + | case visitorCenters = "/visitorcenters" |
|
16 | + | case assets = "/places" |
|
19 | 17 | } |
|
20 | 18 | ||
21 | - | struct NewsReleases: Decodable { |
|
22 | - | let total: String |
|
23 | - | let data: [NewsRelease] |
|
24 | - | } |
|
25 | - | ||
26 | - | struct VisitorCenters: Decodable { |
|
27 | - | let total: String |
|
28 | - | let data: [VisitorCenter] |
|
19 | + | protocol IResource: Decodable { |
|
20 | + | associatedtype NatParkServiceEntity |
|
21 | + | var total: String { get } |
|
22 | + | var data: [NatParkServiceEntity] { get } |
|
29 | 23 | } |
|
30 | 24 | ||
31 | - | struct Assets: Decodable { |
|
25 | + | struct Resources<NatParkServiceEntity: Decodable>: IResource { |
|
32 | 26 | let total: String |
|
33 | - | let data: [Asset] |
|
27 | + | let data: [NatParkServiceEntity] |
|
34 | 28 | } |
|
35 | 29 | ||
36 | 30 | extension String { |
@@ -1,13 +1,7 @@
Loading
1 | 1 | import Foundation |
|
2 | 2 | import Combine |
|
3 | 3 | ||
4 | - | private enum DataServiceEndpoint: String { |
|
5 | - | case parks = "/parks" |
|
6 | - | case alerts = "/alerts" |
|
7 | - | case newsRelease = "/newsreleases" |
|
8 | - | case visitorCenters = "/visitorcenters" |
|
9 | - | case assets = "/places" |
|
10 | - | } |
|
4 | + | ||
11 | 5 | ||
12 | 6 | /// Main API class to interact with the National Park Service API |
|
13 | 7 | public class DataService { |
@@ -63,16 +57,7 @@
Loading
63 | 57 | */ |
|
64 | 58 | public func fetchParks(by parkCodes: [String]? = [], in states: [StateInUSA]? = [], _ requestOptions: RequestOptions<RequestableParkField>? = nil) -> AnyPublisher<(data: [Park], total: Int), DataServiceError> { |
|
65 | 59 | ||
66 | - | guard let validUrl = self.url(.parks, by: parkCodes, in: states, requestOptions) else { |
|
67 | - | return Fail(error: DataServiceError.badURL).eraseToAnyPublisher() |
|
68 | - | } |
|
69 | - | ||
70 | - | return URLSession.shared.dataTaskPublisher(for: validUrl) |
|
71 | - | .tryMap(responseTransformer) |
|
72 | - | .decode(type: Parks.self, decoder: JSONDecoder()) |
|
73 | - | .map { ($0.data, Int($0.total) ?? 0) } |
|
74 | - | .mapError(self.errorTransformer) |
|
75 | - | .eraseToAnyPublisher() |
|
60 | + | return self.fetch(Resources<Park>.self, by: parkCodes, in: states, honoring: requestOptions) |
|
76 | 61 | } |
|
77 | 62 | ||
78 | 63 | /** |
@@ -85,16 +70,7 @@
Loading
85 | 70 | */ |
|
86 | 71 | public func fetchAlerts(by parkCodes: [String]? = [], in states: [StateInUSA]? = [], _ requestOptions: RequestOptions<RequestableAlertField>? = nil) -> AnyPublisher<(data: [Alert], total: Int), DataServiceError> { |
|
87 | 72 | ||
88 | - | guard let validUrl = self.url(.alerts, by: parkCodes, in: states, requestOptions) else { |
|
89 | - | return Fail(error: DataServiceError.badURL).eraseToAnyPublisher() |
|
90 | - | } |
|
91 | - | ||
92 | - | return URLSession.shared.dataTaskPublisher(for: validUrl) |
|
93 | - | .tryMap(responseTransformer) |
|
94 | - | .decode(type: Alerts.self, decoder: JSONDecoder()) |
|
95 | - | .map { ($0.data, Int($0.total) ?? 0) } |
|
96 | - | .mapError(self.errorTransformer) |
|
97 | - | .eraseToAnyPublisher() |
|
73 | + | return self.fetch(Resources<Alert>.self, by: parkCodes, in: states, honoring: requestOptions) |
|
98 | 74 | } |
|
99 | 75 | ||
100 | 76 | /** |
@@ -107,16 +83,7 @@
Loading
107 | 83 | */ |
|
108 | 84 | public func fetchNewsReleases(by parkCodes: [String]? = [], in states: [StateInUSA]? = [], _ requestOptions: RequestOptions<RequestableNewsReleaseField>? = nil) -> AnyPublisher<(data: [NewsRelease], total: Int), DataServiceError> { |
|
109 | 85 | ||
110 | - | guard let validUrl = self.url(.newsRelease, by: parkCodes, in: states, requestOptions) else { |
|
111 | - | return Fail(error: DataServiceError.badURL).eraseToAnyPublisher() |
|
112 | - | } |
|
113 | - | ||
114 | - | return URLSession.shared.dataTaskPublisher(for: validUrl) |
|
115 | - | .tryMap(responseTransformer) |
|
116 | - | .decode(type: NewsReleases.self, decoder: JSONDecoder()) |
|
117 | - | .map { ($0.data, Int($0.total) ?? 0) } |
|
118 | - | .mapError(self.errorTransformer) |
|
119 | - | .eraseToAnyPublisher() |
|
86 | + | return self.fetch(Resources<NewsRelease>.self, by: parkCodes, in: states, honoring: requestOptions) |
|
120 | 87 | } |
|
121 | 88 | ||
122 | 89 | /** |
@@ -129,16 +96,7 @@
Loading
129 | 96 | */ |
|
130 | 97 | public func fetchVisitorCenters(by parkCodes: [String]? = [], in states: [StateInUSA]? = [], _ requestOptions: RequestOptions<RequestableVisitorCenterField>? = nil) -> AnyPublisher<(data: [VisitorCenter], total: Int), DataServiceError> { |
|
131 | 98 | ||
132 | - | guard let validUrl = self.url(.visitorCenters, by: parkCodes, in: states, requestOptions) else { |
|
133 | - | return Fail(error: DataServiceError.badURL).eraseToAnyPublisher() |
|
134 | - | } |
|
135 | - | ||
136 | - | return URLSession.shared.dataTaskPublisher(for: validUrl) |
|
137 | - | .tryMap(responseTransformer) |
|
138 | - | .decode(type: VisitorCenters.self, decoder: JSONDecoder()) |
|
139 | - | .map { ($0.data, Int($0.total) ?? 0) } |
|
140 | - | .mapError(self.errorTransformer) |
|
141 | - | .eraseToAnyPublisher() |
|
99 | + | return self.fetch(Resources<VisitorCenter>.self, by: parkCodes, in: states, honoring: requestOptions) |
|
142 | 100 | } |
|
143 | 101 | ||
144 | 102 | /** |
@@ -151,16 +109,7 @@
Loading
151 | 109 | */ |
|
152 | 110 | public func fetchAssets(by parkCodes: [String]? = [], in states: [StateInUSA]? = [], _ requestOptions: RequestOptions<RequestableAssetField>? = nil) -> AnyPublisher<(data: [Asset], total: Int), DataServiceError> { |
|
153 | 111 | ||
154 | - | guard let validUrl = self.url(.assets, by: parkCodes, in: states, requestOptions) else { |
|
155 | - | return Fail(error: DataServiceError.badURL).eraseToAnyPublisher() |
|
156 | - | } |
|
157 | - | ||
158 | - | return URLSession.shared.dataTaskPublisher(for: validUrl) |
|
159 | - | .tryMap(responseTransformer) |
|
160 | - | .decode(type: Assets.self, decoder: JSONDecoder()) |
|
161 | - | .map { ($0.data, Int($0.total) ?? 0) } |
|
162 | - | .mapError(self.errorTransformer) |
|
163 | - | .eraseToAnyPublisher() |
|
112 | + | return self.fetch(Resources<Asset>.self, by: parkCodes, in: states, honoring: requestOptions) |
|
164 | 113 | } |
|
165 | 114 | ||
166 | 115 | // MARK: private functions |
@@ -182,4 +131,43 @@
Loading
182 | 131 | ||
183 | 132 | return urlComponents.url |
|
184 | 133 | } |
|
134 | + | ||
135 | + | private func endpoint(of target: Decodable.Type) -> DataServiceEndpoint? { |
|
136 | + | switch target { |
|
137 | + | case is Park.Type: |
|
138 | + | return .parks |
|
139 | + | case is Alert.Type: |
|
140 | + | return .alerts |
|
141 | + | case is NewsRelease.Type: |
|
142 | + | return .newsRelease |
|
143 | + | case is VisitorCenter.Type: |
|
144 | + | return .visitorCenters |
|
145 | + | case is Asset.Type: |
|
146 | + | return .assets |
|
147 | + | default: |
|
148 | + | return nil |
|
149 | + | } |
|
150 | + | } |
|
151 | + | ||
152 | + | private func fetch<T: IResource, U: RequestableField>(_ value: T.Type, by parkCodes: [String]? = [], in states: [StateInUSA]? = [], honoring requestOptions: RequestOptions<U>? = nil) -> AnyPublisher<(data: [T.NatParkServiceEntity], total: Int), DataServiceError> { |
|
153 | + | ||
154 | + | guard let decodedAssociatedType = value.NatParkServiceEntity.self as? Decodable.Type else { |
|
155 | + | return Fail(error: DataServiceError.badURL).eraseToAnyPublisher() |
|
156 | + | } |
|
157 | + | ||
158 | + | guard let endpoint = self.endpoint(of: decodedAssociatedType) else { |
|
159 | + | return Fail(error: DataServiceError.badURL).eraseToAnyPublisher() |
|
160 | + | } |
|
161 | + | ||
162 | + | guard let validUrl = self.url(endpoint, by: parkCodes, in: states, requestOptions) else { |
|
163 | + | return Fail(error: DataServiceError.badURL).eraseToAnyPublisher() |
|
164 | + | } |
|
165 | + | ||
166 | + | return URLSession.shared.dataTaskPublisher(for: validUrl) |
|
167 | + | .tryMap(responseTransformer) |
|
168 | + | .decode(type: T.self, decoder: JSONDecoder()) |
|
169 | + | .map { ($0.data, Int($0.total) ?? 0) } |
|
170 | + | .mapError(self.errorTransformer) |
|
171 | + | .eraseToAnyPublisher() |
|
172 | + | } |
|
185 | 173 | } |
Files | Coverage |
---|---|
Sources/NatParkSwiftKit | 96.46% |
Project Totals (12 files) | 96.46% |
81.1
default= TRAVIS_OS_NAME=osx
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file.
The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files.
The size and color of each slice is representing the number of statements and the coverage, respectively.