Skip to content

Commit 0568570

Browse files
authored
🔀 ProductInfo에 필요한 네트워크 관련 작업 (#44)
* 📦️ ProductInfoAPI Package 디렉토리 추가 * ✨ ProductInfoEndPoint 추가 및 구현 * 🌱 ProductInfoPriceResponse DTO 구현 * 🌱 Mock 데이터 추가 및 패키지 설정 * 🌱 ProductInfoURLProtocol 구현 * 🌱 ProductPrice Entity 추가 * ✨ ProductInfoService 구현 * 🎨 SwiftFormat 형식으로 코드 컨벤션 정리 * 🌱 Product.init(dto:) private 해제 및 파일 이동 * 🌱 요청 사항 수정 - ProductDetailResponse DTO 추가 - 같은 모듈내에서 참조하는 것을 방지 * 🌱 요청 사항 수정 및 날짜 변환 코드 추가 - init(decoder:) 제거 - ProductPrice init(dto:) 날짜 변환 코드 추가 * 🌱 요청 사항 수정 - ProductPrice, yearMonth으로 변수 네이밍 변경 - 불필요한 접근 제어 제거 - 불필요한 코드 간략화 - ProductDetail, Entity 추가 및 적용 - ResponseDTO 날짜 값 iso8601으로 변경 * 🌱 요청 사항 수정 - 이니셜라이저에 불필요한 코드 제거 * 🌱 요청 사항 수정 - 불필요한 접근제어 삭제
1 parent 5bc066d commit 0568570

File tree

11 files changed

+296
-1
lines changed

11 files changed

+296
-1
lines changed

APIService/Package.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ let package = Package(
1414
name: "HomeAPISupport",
1515
targets: ["HomeAPISupport"]
1616
),
17+
.library(
18+
name: "ProductInfoAPI",
19+
targets: ["ProductInfoAPI"]
20+
),
21+
.library(
22+
name: "ProductInfoAPISupport",
23+
targets: ["ProductInfoAPISupport"]
24+
),
1725
],
1826
dependencies: [
1927
.package(path: "../Core"),
@@ -34,5 +42,19 @@ let package = Package(
3442
],
3543
resources: [.process("Mocks")]
3644
),
45+
.target(
46+
name: "ProductInfoAPI",
47+
dependencies: [
48+
.product(name: "Network", package: "Core"),
49+
.product(name: "Entity", package: "Entity"),
50+
]
51+
),
52+
.target(
53+
name: "ProductInfoAPISupport",
54+
dependencies: [
55+
"ProductInfoAPI",
56+
],
57+
resources: [.process("Mocks")]
58+
),
3759
]
3860
)

APIService/Sources/HomeAPI/Responses/ProductResponse.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import Entity
99
import Foundation
1010

11+
// MARK: - ProductResponse
12+
1113
struct ProductResponse: Decodable {
1214
let id: Int
1315
let imageURL: URL
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// ProductInfoEndPoint.swift
3+
//
4+
//
5+
// Created by 김응철 on 2/5/24.
6+
//
7+
8+
import Foundation
9+
import Network
10+
11+
// MARK: - ProductInfoEndPoint
12+
13+
public enum ProductInfoEndPoint {
14+
case fetchProduct(Int)
15+
case fetchPrices(Int)
16+
}
17+
18+
// MARK: EndPoint
19+
20+
extension ProductInfoEndPoint: EndPoint {
21+
public var method: HTTPMethod {
22+
.get
23+
}
24+
25+
public var path: String {
26+
switch self {
27+
case let .fetchProduct(productID):
28+
"/v2/products/\(productID)"
29+
case let .fetchPrices(productID):
30+
"/v2/products/\(productID)/price-history"
31+
}
32+
}
33+
34+
public var parameters: HTTPParameter {
35+
.plain
36+
}
37+
38+
public var headers: [String: String] {
39+
["Content-Type": "application/json"]
40+
}
41+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//
2+
// ProductInfoService.swift
3+
//
4+
//
5+
// Created by 김응철 on 2/5/24.
6+
//
7+
8+
import Entity
9+
import Foundation
10+
import Network
11+
12+
// MARK: - ProductInfoServiceRepresentable
13+
14+
public protocol ProductInfoServiceRepresentable {
15+
func fetchProduct(productID: Int) async throws -> ProductDetail
16+
func fetchProductPrice(productID: Int) async throws -> [ProductPrice]
17+
}
18+
19+
// MARK: - ProductInfoService
20+
21+
public struct ProductInfoService {
22+
private let network: Networking
23+
24+
public init(network: Networking) {
25+
self.network = network
26+
}
27+
}
28+
29+
// MARK: ProductInfoServiceRepresentable
30+
31+
extension ProductInfoService: ProductInfoServiceRepresentable {
32+
public func fetchProduct(productID _: Int) async throws -> ProductDetail {
33+
let productResponse: ProductDetailResponse = try await network.request(with: ProductInfoEndPoint.fetchProduct(0))
34+
return ProductDetail(dto: productResponse)
35+
}
36+
37+
public func fetchProductPrice(productID _: Int) async throws -> [ProductPrice] {
38+
let productPrice: [ProductPriceResponse] = try await network.request(
39+
with: ProductInfoEndPoint.fetchPrices(0)
40+
)
41+
return productPrice.map(ProductPrice.init)
42+
}
43+
}
44+
45+
private extension ProductPrice {
46+
init(dto: ProductPriceResponse) {
47+
self.init(date: dto.date, price: dto.price)
48+
}
49+
}
50+
51+
private extension ProductDetail {
52+
init(dto: ProductDetailResponse) {
53+
self.init(
54+
id: dto.id,
55+
imageURL: dto.imageURL,
56+
price: dto.price,
57+
name: dto.name,
58+
promotion: dto.promotion,
59+
convenienceStore: dto.convenienceStore
60+
)
61+
}
62+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// ProductDetailResponse.swift
3+
//
4+
//
5+
// Created by 김응철 on 2/5/24.
6+
//
7+
8+
import Entity
9+
import Foundation
10+
11+
struct ProductDetailResponse: Decodable {
12+
let id: Int
13+
let imageURL: URL
14+
let price: Int
15+
let name: String
16+
let promotion: Promotion
17+
let convenienceStore: ConvenienceStore
18+
19+
enum CodingKeys: String, CodingKey {
20+
case id
21+
case imageURL = "image_url"
22+
case price
23+
case name
24+
case promotion
25+
case convenienceStore
26+
}
27+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// ProductPriceResponse.swift
3+
//
4+
//
5+
// Created by 김응철 on 2/5/24.
6+
//
7+
8+
import Foundation
9+
10+
struct ProductPriceResponse: Decodable {
11+
let date: Date
12+
let price: Int
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"date": "2023-12-06T00:42:21Z"
4+
"price": 1100
5+
},
6+
{
7+
"date": "2024-01-06T00:42:21Z"
8+
"price": 1200
9+
},
10+
{
11+
"date": "2024-02-06T00:42:21Z"
12+
"price": 1250
13+
}
14+
]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// ProductInfoURLProtocol.swift
3+
//
4+
//
5+
// Created by 김응철 on 2/5/24.
6+
//
7+
8+
import Foundation
9+
import Network
10+
import ProductInfoAPI
11+
12+
public final class ProductInfoURLProtocol: URLProtocol {
13+
private lazy var mockData: [String: Data?] = [
14+
ProductInfoEndPoint.fetchProduct(0).path: loadMockData(fileName: "HomeProductResponse"),
15+
ProductInfoEndPoint.fetchPrices(0).path: loadMockData(fileName: "ProductPriceResponse"),
16+
]
17+
18+
override public class func canInit(with _: URLRequest) -> Bool {
19+
true
20+
}
21+
22+
override public class func canonicalRequest(for request: URLRequest) -> URLRequest {
23+
request
24+
}
25+
26+
override public func startLoading() {
27+
defer { client?.urlProtocolDidFinishLoading(self) }
28+
if let url = request.url,
29+
let mockData = mockData[url.path(percentEncoded: true)],
30+
let data = mockData,
31+
let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil) {
32+
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
33+
client?.urlProtocol(self, didLoad: data)
34+
} else {
35+
client?.urlProtocol(self, didFailWithError: NetworkError.urlError)
36+
}
37+
}
38+
39+
private func loadMockData(fileName: String) -> Data? {
40+
guard let url = Bundle.module.url(forResource: fileName, withExtension: "json")
41+
else {
42+
return nil
43+
}
44+
return try? Data(contentsOf: url)
45+
}
46+
}

Entity/Sources/Entity/Product.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import Foundation
99

10-
/// 편의점 행사 제품에 대한 Entity Model입니다.
10+
/// 편의점 행사 제품 리스트에 대한 Entity Model입니다.
1111
public struct Product: Identifiable {
1212
/// 제품 고유 Identifier
1313
public let id: Int
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// ProductDetail.swift
3+
//
4+
//
5+
// Created by 김응철 on 2/6/24.
6+
//
7+
8+
import Foundation
9+
10+
/// 편의점 행사 제품에 대한 Entity Model입니다.
11+
public struct ProductDetail: Identifiable {
12+
/// 제품 고유 Identifier
13+
public let id: Int
14+
15+
/// 이미지 URL
16+
public let imageURL: URL
17+
18+
/// 제품 가격
19+
public let price: Int
20+
21+
/// 제품명
22+
public let name: String
23+
24+
/// 행사 정보
25+
public let promotion: Promotion
26+
27+
/// 편의점
28+
public let convenienceStore: ConvenienceStore
29+
30+
// TODO: 카테고리 상수 추가하기
31+
32+
public init(
33+
id: Int,
34+
imageURL: URL,
35+
price: Int,
36+
name: String,
37+
promotion: Promotion,
38+
convenienceStore: ConvenienceStore
39+
) {
40+
self.id = id
41+
self.imageURL = imageURL
42+
self.price = price
43+
self.name = name
44+
self.promotion = promotion
45+
self.convenienceStore = convenienceStore
46+
}
47+
}

0 commit comments

Comments
 (0)