Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ProductInfo에 필요한 네트워크 관련 작업 #44

Merged
merged 14 commits into from
Feb 6, 2024
22 changes: 22 additions & 0 deletions APIService/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ let package = Package(
name: "HomeAPISupport",
targets: ["HomeAPISupport"]
),
.library(
name: "ProductInfoAPI",
targets: ["ProductInfoAPI"]
),
.library(
name: "ProductInfoAPISupport",
targets: ["ProductInfoAPISupport"]
),
],
dependencies: [
.package(path: "../Core"),
Expand All @@ -34,5 +42,19 @@ let package = Package(
],
resources: [.process("Mocks")]
),
.target(
name: "ProductInfoAPI",
dependencies: [
.product(name: "Network", package: "Core"),
.product(name: "Entity", package: "Entity"),
]
),
.target(
name: "ProductInfoAPISupport",
dependencies: [
"ProductInfoAPI",
],
resources: [.process("Mocks")]
),
]
)
2 changes: 2 additions & 0 deletions APIService/Sources/HomeAPI/Responses/ProductResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import Entity
import Foundation

// MARK: - ProductResponse

struct ProductResponse: Decodable {
let id: Int
let imageURL: URL
Expand Down
41 changes: 41 additions & 0 deletions APIService/Sources/ProductInfoAPI/ProductInfoEndPoint.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// ProductInfoEndPoint.swift
//
//
// Created by 김응철 on 2/5/24.
//

import Foundation
import Network

// MARK: - ProductInfoEndPoint

public enum ProductInfoEndPoint {
case fetchProduct(Int)
case fetchPrices(Int)
}

// MARK: EndPoint

extension ProductInfoEndPoint: EndPoint {
public var method: HTTPMethod {
.get
}

public var path: String {
switch self {
case let .fetchProduct(productID):
"/v2/products/\(productID)"
case let .fetchPrices(productID):
"/v2/products/\(productID)/price-history"
}
}

public var parameters: HTTPParameter {
.plain
}

public var headers: [String: String] {
["Content-Type": "application/json"]
}
}
62 changes: 62 additions & 0 deletions APIService/Sources/ProductInfoAPI/ProductInfoService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// ProductInfoService.swift
//
//
// Created by 김응철 on 2/5/24.
//

import Entity
import Foundation
import Network

// MARK: - ProductInfoServiceRepresentable

public protocol ProductInfoServiceRepresentable {
func fetchProduct(productID: Int) async throws -> ProductDetail
func fetchProductPrice(productID: Int) async throws -> [ProductPrice]
}

// MARK: - ProductInfoService

public struct ProductInfoService {
private let network: Networking

public init(network: Networking) {
self.network = network
}
}

// MARK: ProductInfoServiceRepresentable

extension ProductInfoService: ProductInfoServiceRepresentable {
public func fetchProduct(productID _: Int) async throws -> ProductDetail {
let productResponse: ProductDetailResponse = try await network.request(with: ProductInfoEndPoint.fetchProduct(0))
return ProductDetail(dto: productResponse)
}

public func fetchProductPrice(productID _: Int) async throws -> [ProductPrice] {
let productPrice: [ProductPriceResponse] = try await network.request(
with: ProductInfoEndPoint.fetchPrices(0)
)
return productPrice.map(ProductPrice.init)
}
}

private extension ProductPrice {
init(dto: ProductPriceResponse) {
self.init(date: dto.date, price: dto.price)
}
}

private extension ProductDetail {
init(dto: ProductDetailResponse) {
self.init(
id: dto.id,
imageURL: dto.imageURL,
price: dto.price,
name: dto.name,
promotion: dto.promotion,
convenienceStore: dto.convenienceStore
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// ProductDetailResponse.swift
//
//
// Created by 김응철 on 2/5/24.
//

import Entity
import Foundation

struct ProductDetailResponse: Decodable {
let id: Int
let imageURL: URL
let price: Int
let name: String
let promotion: Promotion
let convenienceStore: ConvenienceStore

enum CodingKeys: String, CodingKey {
case id
case imageURL = "image_url"
case price
case name
case promotion
case convenienceStore
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// ProductPriceResponse.swift
//
//
// Created by 김응철 on 2/5/24.
//

import Foundation

struct ProductPriceResponse: Decodable {
let date: Date
let price: Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"date": "2023-12-06T00:42:21Z"
"price": 1100
},
{
"date": "2024-01-06T00:42:21Z"
"price": 1200
},
{
"date": "2024-02-06T00:42:21Z"
"price": 1250
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// ProductInfoURLProtocol.swift
//
//
// Created by 김응철 on 2/5/24.
//

import Foundation
import Network
import ProductInfoAPI

public final class ProductInfoURLProtocol: URLProtocol {
private lazy var mockData: [String: Data?] = [
ProductInfoEndPoint.fetchProduct(0).path: loadMockData(fileName: "HomeProductResponse"),
ProductInfoEndPoint.fetchPrices(0).path: loadMockData(fileName: "ProductPriceResponse"),
]

override public class func canInit(with _: URLRequest) -> Bool {
true
}

override public class func canonicalRequest(for request: URLRequest) -> URLRequest {
request
}

override public func startLoading() {
defer { client?.urlProtocolDidFinishLoading(self) }
if let url = request.url,
let mockData = mockData[url.path(percentEncoded: true)],
let data = mockData,
let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil) {
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
client?.urlProtocol(self, didLoad: data)
} else {
client?.urlProtocol(self, didFailWithError: NetworkError.urlError)
}
}

private func loadMockData(fileName: String) -> Data? {
guard let url = Bundle.module.url(forResource: fileName, withExtension: "json")
else {
return nil
}
return try? Data(contentsOf: url)
}
}
2 changes: 1 addition & 1 deletion Entity/Sources/Entity/Product.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

/// 편의점 행사 제품에 대한 Entity Model입니다.
/// 편의점 행사 제품 리스트에 대한 Entity Model입니다.
public struct Product: Identifiable {
/// 제품 고유 Identifier
public let id: Int
Expand Down
47 changes: 47 additions & 0 deletions Entity/Sources/Entity/ProductDetail.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// ProductDetail.swift
//
//
// Created by 김응철 on 2/6/24.
//

import Foundation

/// 편의점 행사 제품에 대한 Entity Model입니다.
public struct ProductDetail: Identifiable {
/// 제품 고유 Identifier
public let id: Int

/// 이미지 URL
public let imageURL: URL

/// 제품 가격
public let price: Int

/// 제품명
public let name: String

/// 행사 정보
public let promotion: Promotion

/// 편의점
public let convenienceStore: ConvenienceStore

// TODO: 카테고리 상수 추가하기

public init(
id: Int,
imageURL: URL,
price: Int,
name: String,
promotion: Promotion,
convenienceStore: ConvenienceStore
) {
self.id = id
self.imageURL = imageURL
self.price = price
self.name = name
self.promotion = promotion
self.convenienceStore = convenienceStore
}
}
21 changes: 21 additions & 0 deletions Entity/Sources/Entity/ProductPrice.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// ProductPrice.swift
//
//
// Created by 김응철 on 2/5/24.
//

import Foundation

public struct ProductPrice {
public let date: Date
public let price: Int

public init(
date: Date,
price: Int
) {
self.date = date
self.price = price
}
}