Skip to content
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
8 changes: 0 additions & 8 deletions NativeAppTemplate.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
0106414029A9F2EC00B46FED /* AccountPasswordService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0106413F29A9F2EC00B46FED /* AccountPasswordService.swift */; };
0106414229A9F51700B46FED /* AccountPasswordRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0106414129A9F51700B46FED /* AccountPasswordRepository.swift */; };
0106414429AA061100B46FED /* PasswordEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0106414329AA061100B46FED /* PasswordEditView.swift */; };
010A30A528A4A285001D6BD1 /* DataCacheUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010A30A428A4A285001D6BD1 /* DataCacheUpdate.swift */; };
010F86AE2621A2A900B6C62A /* ShopDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010F86AD2621A2A900B6C62A /* ShopDetailView.swift */; };
010F86BE2622F9C900B6C62A /* ShopListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010F86BD2622F9C900B6C62A /* ShopListView.swift */; };
0110A15F2AC816F5003EDCBA /* SendConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0110A15E2AC816F5003EDCBA /* SendConfirmation.swift */; };
Expand All @@ -36,7 +35,6 @@
0158BA0125C174E0008EC9D5 /* ShopsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0158BA0025C174E0008EC9D5 /* ShopsRequest.swift */; };
015C78052B72DA2C00B6523C /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 015C78042B72DA2C00B6523C /* PrivacyInfo.xcprivacy */; };
0172033725A9642E008FD63B /* JSONAPIResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172030825A9642D008FD63B /* JSONAPIResource.swift */; };
0172033925A9642E008FD63B /* JSONAPIRelationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172030A25A9642E008FD63B /* JSONAPIRelationship.swift */; };
0172033A25A9642E008FD63B /* JSONAPIDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172030B25A9642E008FD63B /* JSONAPIDocument.swift */; };
0172033C25A9642E008FD63B /* NativeAppTemplateAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172030E25A9642E008FD63B /* NativeAppTemplateAPI.swift */; };
0172033D25A9642E008FD63B /* NativeAppTemplateEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172030F25A9642E008FD63B /* NativeAppTemplateEnvironment.swift */; };
Expand Down Expand Up @@ -167,7 +165,6 @@
0106413F29A9F2EC00B46FED /* AccountPasswordService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPasswordService.swift; sourceTree = "<group>"; };
0106414129A9F51700B46FED /* AccountPasswordRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPasswordRepository.swift; sourceTree = "<group>"; };
0106414329AA061100B46FED /* PasswordEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordEditView.swift; sourceTree = "<group>"; };
010A30A428A4A285001D6BD1 /* DataCacheUpdate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataCacheUpdate.swift; sourceTree = "<group>"; };
010F86AD2621A2A900B6C62A /* ShopDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopDetailView.swift; sourceTree = "<group>"; };
010F86BD2622F9C900B6C62A /* ShopListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopListView.swift; sourceTree = "<group>"; };
0110A15E2AC816F5003EDCBA /* SendConfirmation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendConfirmation.swift; sourceTree = "<group>"; };
Expand All @@ -194,7 +191,6 @@
0158BA0025C174E0008EC9D5 /* ShopsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopsRequest.swift; sourceTree = "<group>"; };
015C78042B72DA2C00B6523C /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
0172030825A9642D008FD63B /* JSONAPIResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONAPIResource.swift; sourceTree = "<group>"; };
0172030A25A9642E008FD63B /* JSONAPIRelationship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONAPIRelationship.swift; sourceTree = "<group>"; };
0172030B25A9642E008FD63B /* JSONAPIDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONAPIDocument.swift; sourceTree = "<group>"; };
0172030E25A9642E008FD63B /* NativeAppTemplateAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeAppTemplateAPI.swift; sourceTree = "<group>"; };
0172030F25A9642E008FD63B /* NativeAppTemplateEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeAppTemplateEnvironment.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -465,7 +461,6 @@
isa = PBXGroup;
children = (
0172030B25A9642E008FD63B /* JSONAPIDocument.swift */,
0172030A25A9642E008FD63B /* JSONAPIRelationship.swift */,
0172030825A9642D008FD63B /* JSONAPIResource.swift */,
);
path = JSONAPI;
Expand Down Expand Up @@ -497,7 +492,6 @@
0172031F25A9642E008FD63B /* Adapters */ = {
isa = PBXGroup;
children = (
010A30A428A4A285001D6BD1 /* DataCacheUpdate.swift */,
0172032025A9642E008FD63B /* EntityAdapter.swift */,
0172032225A9642E008FD63B /* EntityAdapters */,
);
Expand Down Expand Up @@ -900,9 +894,7 @@
01B9E45228A5070D00CAC681 /* ShopkeeperSignInAdapter.swift in Sources */,
0172040025AA6775008FD63B /* LoginRepository.swift in Sources */,
0172034B25A9642E008FD63B /* EntityAdapter.swift in Sources */,
010A30A528A4A285001D6BD1 /* DataCacheUpdate.swift in Sources */,
01E0A60C25BD440300298D35 /* SignInEmailAndPasswordView.swift in Sources */,
0172033925A9642E008FD63B /* JSONAPIRelationship.swift in Sources */,
01B526562AF4E82A00655131 /* ScrollToTopID.swift in Sources */,
7249A60C06FE44338E16BC50 /* APISessionDelegate.swift in Sources */,
0172033C25A9642E008FD63B /* NativeAppTemplateAPI.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
try await accountPasswordService.updatePassword(updatePassword: updatePassword)
} catch {
Failure
.destroy(from: Self.self, reason: error.codedDescription)
.update(from: Self.self, reason: error.codedDescription)
.log()
throw error
}
Expand Down
4 changes: 2 additions & 2 deletions NativeAppTemplate/Login/LoginRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import os
_currentShopkeeper = shopkeeper
} catch {
Failure
.fetch(from: Self.self, reason: error.codedDescription)
.login(from: Self.self, reason: error.codedDescription)
.log()
throw error
}
Expand All @@ -54,7 +54,7 @@ import os
_currentShopkeeper = .none
} catch {
Failure
.fetch(from: Self.self, reason: error.codedDescription)
.logout(from: Self.self, reason: error.codedDescription)
.log()
removeShopkeeper()
_currentShopkeeper = .none
Expand Down
62 changes: 15 additions & 47 deletions NativeAppTemplate/Login/RootPathService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import Foundation

/// A service for devise_token_auth's routes (sign-in, sign-up, passwords), which sit at
/// the API root, without the account and /api/v1 prefix that `Service` adds.
/// Signed out, the client has no token, so no session headers are sent.
/// Sending and error handling are shared with `Service` (see `URLSession.apiResponse`);
/// it differs in three ways:
/// - Signed out, the client has no token, so only the `source` header is sent.
/// - A request's query items are not sent. No devise_token_auth request has any.
/// - devise_token_auth's 401 is not announced as an expired session (`.apiSessionDidExpire`).
protocol RootPathService {
var networkClient: NativeAppTemplateAPI { get }
var session: URLSession { get }
Expand All @@ -17,59 +21,23 @@ extension RootPathService {
func prepare(request: some NativeAppTemplate.Request) throws -> URLRequest {
let pathURL = networkClient.environment.baseURL.appendingPathComponent(request.path)

guard let components = URLComponents(
url: pathURL,
resolvingAgainstBaseURL: false
) else {
throw URLError(.badURL)
var urlRequest = try URLRequest(url: pathURL, queryItems: [], for: request)
if networkClient.authToken.isEmpty {
// No session to send, only the platform (which sessionHeaders also carry)
urlRequest.addHeaders([NativeAppTemplateAPI.sourceHeader])
} else {
urlRequest.addHeaders(networkClient.sessionHeaders)
}

guard let url = components.url
else { throw URLError(.badURL) }

var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = request.method.rawValue

// body *needs* to be the last property that we set, because of this bug:
// https://bugs.swift.org/browse/SR-6687
urlRequest.httpBody = request.body

if !networkClient.authToken.isEmpty {
networkClient.sessionHeaders.forEach { urlRequest.addValue($0.value, forHTTPHeaderField: $0.key) }
}

urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("ios", forHTTPHeaderField: "source")

return urlRequest
}

@MainActor func makeRequest<Request: NativeAppTemplate.Request>(
request: Request
) async throws -> Request.Response {
let (data, response) = try await session.data(
for: prepare(request: request)
try await session.apiResponse(
to: request,
sending: prepare(request: request),
announcingRejectionOf: nil
)

let statusCode = (response as? HTTPURLResponse)?.statusCode
guard statusCode.map((200 ..< 300).contains) == true
else {
var errorMessage: String?

do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let theErrorMessage = json["error_message"] as? String {
errorMessage = theErrorMessage
}
} catch {
// Not JSON (e.g. a proxy's HTML error page): no message to show,
// so the error falls back to its generic description
throw NativeAppTemplateAPIError.requestFailed(nil, statusCode ?? 0, nil)
}

throw NativeAppTemplateAPIError.requestFailed(nil, statusCode ?? 0, errorMessage)
}

return try request.handle(response: data)
}
}
4 changes: 2 additions & 2 deletions NativeAppTemplate/Login/SignUpRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import os
shopkeeper = try await signUpsService.makeShopkeeper(signUp: signUp)
} catch {
Failure
.fetch(from: Self.self, reason: error.codedDescription)
.create(from: Self.self, reason: error.codedDescription)
.log()
throw error
}
Expand Down Expand Up @@ -50,7 +50,7 @@ import os
removeShopkeeper()
} catch {
Failure
.fetch(from: Self.self, reason: error.codedDescription)
.destroy(from: Self.self, reason: error.codedDescription)
.log()
removeShopkeeper()

Expand Down
81 changes: 0 additions & 81 deletions NativeAppTemplate/Networking/Adapters/DataCacheUpdate.swift

This file was deleted.

24 changes: 1 addition & 23 deletions NativeAppTemplate/Networking/Adapters/EntityAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,7 @@
// NativeAppTemplate
//

import Foundation

protocol EntityAdapter {
associatedtype Response

static func process(
resource: JSONAPIResource,
relationships: [EntityRelationship],
cacheUpdate: DataCacheUpdate
) throws -> Response
}

/// The API's resource types (JSON:API `type`) that an adapter turns into a model
enum EntityType {
case shop
case shopkeeper
Expand All @@ -36,14 +25,3 @@ enum EntityType {
}
}
}

struct EntityIdentity: Identifiable {
let id: String
let type: EntityType
}

struct EntityRelationship {
let name: String
let from: EntityIdentity
let to: EntityIdentity
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@

import struct Foundation.URL

struct ItemTagAdapter: EntityAdapter {
static func process(
resource: JSONAPIResource,
relationships: [EntityRelationship] = [],
cacheUpdate: DataCacheUpdate = DataCacheUpdate()
) throws -> ItemTag {
enum ItemTagAdapter {
static func process(resource: JSONAPIResource) throws -> ItemTag {
guard resource.entityType == .itemTag else { throw EntityAdapterError.invalidResourceTypeForAdapter }

guard let shopId = resource.attributes["shop_id"] as? String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@

import struct Foundation.URL

struct ShopAdapter: EntityAdapter {
static func process(
resource: JSONAPIResource,
relationships: [EntityRelationship] = [],
cacheUpdate: DataCacheUpdate = DataCacheUpdate()
) throws -> Shop {
enum ShopAdapter {
static func process(resource: JSONAPIResource) throws -> Shop {
guard resource.entityType == .shop else { throw EntityAdapterError.invalidResourceTypeForAdapter }

guard let name = resource.attributes["name"] as? String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@

import Foundation

struct ShopkeeperAdapter: EntityAdapter {
static func process(
resource: JSONAPIResource,
relationships: [EntityRelationship] = [],
cacheUpdate: DataCacheUpdate = DataCacheUpdate()
) throws -> Shopkeeper {
enum ShopkeeperAdapter {
static func process(resource: JSONAPIResource) throws -> Shopkeeper {
guard resource.entityType == .shopkeeper else {
throw EntityAdapterError.invalidResourceTypeForAdapter
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@

import Foundation

struct ShopkeeperSignInAdapter: EntityAdapter {
static func process(
resource: JSONAPIResource,
relationships: [EntityRelationship] = [],
cacheUpdate: DataCacheUpdate = DataCacheUpdate()
) throws -> Shopkeeper {
enum ShopkeeperSignInAdapter {
static func process(resource: JSONAPIResource) throws -> Shopkeeper {
guard resource.entityType == .shopkeeperSignIn else {
throw EntityAdapterError.invalidResourceTypeForAdapter
}
Expand Down
Loading
Loading