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

Change folder composition in config directory #79

Merged
merged 7 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Demo/BuildConfigSwiftDemo/Sources/Data/BaseEntities.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// BaseEntities.swift
// BuildConfigSwiftDemo
//
// Created by 417.72KI on 2023/10/05.
// Copyright © 2023 417.72KI. All rights reserved.
//

import Foundation

protocol Request: Encodable, Hashable {
}

protocol Response: Decodable, Hashable {
}
14 changes: 14 additions & 0 deletions Demo/BuildConfigSwiftDemo/Sources/Data/LoginRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// LoginRequest.swift
// BuildConfigSwiftDemo
//
// Created by 417.72KI on 2023/10/05.
// Copyright © 2023 417.72KI. All rights reserved.
//

import Foundation

struct LoginRequest: Request {
var id: String
var password: String
}
14 changes: 14 additions & 0 deletions Demo/BuildConfigSwiftDemo/Sources/Data/LoginResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// LoginResponse.swift
// BuildConfigSwiftDemo
//
// Created by 417.72KI on 2023/10/05.
// Copyright © 2023 417.72KI. All rights reserved.
//

import Foundation

struct LoginResponse: Response {
var accessToken: String
var refreshToken: String
}
19 changes: 19 additions & 0 deletions Demo/BuildConfigSwiftDemo/Sources/Data/SearchResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// SearchResponse.swift
// BuildConfigSwiftDemo
//
// Created by 417.72KI on 2023/10/05.
// Copyright © 2023 417.72KI. All rights reserved.
//

import Foundation

struct SearchResponse: Response {
var items: [Item]
}

extension SearchResponse {
struct Item: Response {
var name: String
}
}
61 changes: 61 additions & 0 deletions Demo/BuildConfigSwiftDemo/Sources/Repository/APIClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// APIClient.swift
// BuildConfigSwiftDemo
//
// Created by 417.72KI on 2023/10/05.
// Copyright © 2023 417.72KI. All rights reserved.
//

import Foundation

struct APIClient {
var config: BuildConfig.Api
var session: URLSession
}

extension APIClient {
var host: URL { URL(string: "https://\(config.host)")! }

var decoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}

var encoder: JSONEncoder {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
return encoder
}
}

extension APIClient {
func endpoint<E>(_ keyPath: KeyPath<BuildConfig.Api.Endpoint, E>) -> E {
config.endpoint[keyPath: keyPath]
}
}

extension APIClient {
func login(id: String, password: String) async throws -> LoginResponse {
let endpoint = endpoint(\.login)
let url = host.appendingPathComponent(endpoint.path)
var request = URLRequest(url: url)
request.httpMethod = endpoint.method
request.httpBody = try encoder.encode(LoginRequest(id: id, password: password))
let (data, _) = try await session.data(for: request)
return try decoder.decode(LoginResponse.self, from: data)
}

@available(iOS 16.0, *)
func search(_ text: String) async throws -> SearchResponse {
let endpoint = endpoint(\.search)
let url = host.appendingPathComponent(endpoint.path)
.appending(queryItems: [
URLQueryItem(name: "text", value: text)
])
var request = URLRequest(url: url)
request.httpMethod = endpoint.method
let (data, _) = try await session.data(for: request)
return try decoder.decode(SearchResponse.self, from: data)
}
}
4 changes: 2 additions & 2 deletions Demo/BuildConfigSwiftDemo/Sources/UI/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
import SwiftUI

struct ContentView: View {
var config: BuildConfig = .default
@Environment(\.buildConfig) var config: BuildConfig

var body: some View {
VStack {
Text("isDebug: \(String(config.isDebug))")
Text("Environment: \(config.environment)")
Text("API version: \(config.apiVersion, format: .number)")
Text("API version: \(config.api.version, format: .number)")
Text("PI: \(config.pi, format: .number)")
}
}
Expand Down
13 changes: 3 additions & 10 deletions Demo/BuildConfigSwiftDemo/Sources/UI/DemoApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,13 @@

import SwiftUI

@main
struct DemoApp: App {
static var buildConfig: BuildConfig = .default

var body: some Scene {
WindowGroup {
ContentView()
.environment(\.buildConfig, Self.buildConfig)
}
}
}

// MARK: - For Debug
#if DEBUG
private extension DemoApp {
var isTesting: Bool {
ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// BuildConfig+Environment.swift
// BuildConfigSwiftDemo
//
// Created by 417.72KI on 2023/10/09.
// Copyright © 2023 417.72KI. All rights reserved.
//

import SwiftUI

struct BuildConfigKey: EnvironmentKey {
static let defaultValue = BuildConfig.default
}

extension EnvironmentValues {
var buildConfig: BuildConfig {
get { self[BuildConfigKey.self] }
set { self[BuildConfigKey.self] = newValue }
}
}
15 changes: 15 additions & 0 deletions Demo/BuildConfigSwiftDemo/Sources/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// main.swift
// BuildConfigSwiftDemo
//
// Created by 417.72KI on 2023/10/09.
// Copyright © 2023 417.72KI. All rights reserved.
//

import SwiftUI

if let clazz = NSClassFromString("FakeApp") as? any App.Type {
clazz.main()
} else {
DemoApp.main()
}
64 changes: 64 additions & 0 deletions Demo/BuildConfigSwiftDemoTests/APIClientTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// APIClientTests.swift
// BuildConfigSwiftDemoTests
//
// Created by 417.72KI on 2023/10/05.
// Copyright © 2023 417.72KI. All rights reserved.
//

import XCTest
import StubNetworkKit

@testable import BuildConfigSwiftDemo

final class APIClientTests: XCTestCase {
var config = BuildConfig.fake.api

var apiClient: APIClient!

override func setUpWithError() throws {
apiClient = APIClient(config: config,
session: defaultStubSession)
}

override func tearDownWithError() throws {
clearStubs()
}

func testLogin() async throws {
stub {
Scheme.is("https")
Host.is("localhost")
Path.is("/login")
Method.isPost()
Body.isJson(["id": "john_doe", "password": "password"])
}.responseJson(["access_token": "foo", "refresh_token": "bar"])

let response = try await apiClient.login(id: "john_doe",
password: "password")
XCTAssertEqual("foo", response.accessToken)
XCTAssertEqual("bar", response.refreshToken)
}

@available(iOS 16.0, *)
func testSearch() async throws {
stub {
Scheme.is("https")
Host.is("localhost")
Path.is("/search")
Method.isGet()
QueryParams.contains(["text": "寿限無"])
}.responseJson([
"items": [
["name": "foo"],
["name": "bar"],
["name": "baz"],
]
])

let response = try await apiClient.search("寿限無")
XCTAssertEqual(3, response.items.count)
XCTAssertEqual("foo", response.items.first?.name)
XCTAssertEqual("baz", response.items.last?.name)
}
}
25 changes: 9 additions & 16 deletions Demo/BuildConfigSwiftDemoTests/BuildConfigSwiftDemoTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,19 @@ import XCTest
final class BuildConfigTests: XCTestCase {
func testDefault() {
let buildConfig = BuildConfig.default
XCTAssertEqual(1, buildConfig.apiVersion)
XCTAssertEqual("develop", buildConfig.environment)
XCTAssertFalse(buildConfig.isDebug)
XCTAssertEqual(1, buildConfig.api.version)
XCTAssertEqual("api-dev.example.com", buildConfig.api.host)
XCTAssertEqual("debug", buildConfig.environment)
XCTAssertTrue(buildConfig.isDebug)
XCTAssertEqual(3.14, buildConfig.pi, accuracy: 0.01)
}

func testLoad() {
let buildConfig = BuildConfig.load(
from: #"""
{
"api_version": 100,
"environment": "staging",
"is_debug": true,
"pi": 3.14
}
"""#.data(using: .utf8)!
)
XCTAssertEqual(100, buildConfig.apiVersion)
func testLoad() throws {
let buildConfig = BuildConfig.fake
XCTAssertEqual(100, buildConfig.api.version)
XCTAssertEqual("localhost", buildConfig.api.host)
XCTAssertEqual("staging", buildConfig.environment)
XCTAssertTrue(buildConfig.isDebug)
XCTAssertFalse(buildConfig.isDebug)
XCTAssertEqual(3.14, buildConfig.pi, accuracy: 0.01)
}
}
25 changes: 25 additions & 0 deletions Demo/BuildConfigSwiftDemoTests/FakeApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// FakeApp.swift
// BuildConfigSwiftDemoTests
//
// Created by 417.72KI on 2023/10/09.
// Copyright © 2023 417.72KI. All rights reserved.
//

import SwiftUI

@objc(FakeApp)
final class FakeApp: NSObject, App {
override init() { super.init() }

var body: some Scene {
WindowGroup {
Text("This is a fake app.")
.foregroundStyle(Color.white)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background {
Color.black
}
}
}
}
30 changes: 30 additions & 0 deletions Demo/BuildConfigSwiftDemoTests/TestHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// TestHelper.swift
// BuildConfigSwiftDemoTests
//
// Created by 417.72KI on 2023/10/05.
// Copyright © 2023 417.72KI. All rights reserved.
//

import Foundation
@testable import BuildConfigSwiftDemo

final class TestHelper {
private init() {}
}

extension TestHelper {
static var bundle: Bundle { Bundle(for: self.self) }
}

extension TestHelper {
static func path(forResource name: String, ofType ext: String) -> String? {
bundle.path(forResource: name, ofType: ext)
}
}

extension BuildConfig {
static var fake: Self {
.load(from: TestHelper.path(forResource: "test_config", ofType: "json")!)
}
}
23 changes: 23 additions & 0 deletions Demo/BuildConfigSwiftDemoTests/test_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"api": {
"version": 100,
"host": "localhost",
"endpoint": {
"login": {
"path": "/login",
"method": "POST"
},
"profile": {
"path": "/profile",
"method": "GET"
},
"search": {
"path": "/search",
"method": "GET"
}
}
},
"environment": "staging",
"is_debug": false,
"pi": 3.14
}
2 changes: 0 additions & 2 deletions Demo/Resources/BuildConfig/.env/debug.yml

This file was deleted.

Loading