MarcoEidinger / npsapi-swift

@@ -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

No yaml found.

Create your codecov.yml to customize your Codecov experience

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.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading