Skip to content

Commit

Permalink
SPT-1998 append missed files
Browse files Browse the repository at this point in the history
  • Loading branch information
mrandrewsmith committed May 2, 2024
1 parent e6102fc commit 023e441
Show file tree
Hide file tree
Showing 25 changed files with 1,793 additions and 136 deletions.
272 changes: 136 additions & 136 deletions NodeKit/NodeKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions NodeKit/NodeKit/CacheNode/ETag/URLETagReaderNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// EtagReaderNode.swift
// CoreNetKit
//
// Created by Александр Кравченков on 05/03/2019.
// Copyright © 2019 Кравченков Александр. All rights reserved.
//

import Foundation

/// Этот узел читает eTag-токен из хранилища и добавляет его к запросу.
open class URLETagReaderNode: AsyncNode {

// Следующий узел для обработки.
public var next: any TransportLayerNode

/// Ключ, по которому необходимо получить eTag-токен из хедеров.
/// По-молчанию имеет значение `eTagRequestHeaderKey`
public var etagHeaderKey: String

/// Инициаллизирует узел.
///
/// - Parameters:
/// - next: Следующий узел для обработки.
/// - eTagHeaderKey: Ключ, по которому необходимо добавить eTag-токен к запросу.
public init(next: some TransportLayerNode,
etagHeaderKey: String = ETagConstants.eTagRequestHeaderKey) {
self.next = next
self.etagHeaderKey = etagHeaderKey
}

/// Пытается прочесть eTag-токен из хранилища и добавить его к запросу.
/// В случае, если прочесть токен не удалось, то управление просто передается дальше.
open func process(
_ data: TransportURLRequest,
logContext: LoggingContextProtocol
) async -> NodeResult<Json> {
guard
let key = data.url.withOrderedQuery(),
let tag = UserDefaults.etagStorage?.value(forKey: key) as? String
else {
return await next.process(data, logContext: logContext)
}

var headers = data.headers
headers[self.etagHeaderKey] = tag

let params = TransportURLParameters(method: data.method,
url: data.url,
headers: headers)

let newData = TransportURLRequest(with: params, raw: data.raw)

return await next.process(newData, logContext: logContext)
}

}
96 changes: 96 additions & 0 deletions NodeKit/NodeKit/CacheNode/ETag/URLETagSaverNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//
// eTagSaverNode.swift
// CoreNetKit
//
// Created by Александр Кравченков on 04/03/2019.
// Copyright © 2019 Кравченков Александр. All rights reserved.
//

import Foundation

// MARK: - UserDefaults eTag storage

/// Содержит указатель на UserDefaults-хранилище для eTag токенов.
extension UserDefaults {
/// Хранилище для eTag-токенов
static var etagStorage = UserDefaults(suiteName: "\(String(describing: self.self))")
}

/// Этот узел сохраняет пришедшие eTag-токены.
/// В качестве ключа используется абсолютный URL до endpoint-a.
open class URLETagSaverNode: AsyncNode {

/// Следующий узел для обработки.
public var next: (any ResponsePostprocessorLayerNode)?

/// Ключ, по которому необходимо получить eTag-токен из хедеров.
/// По-молчанию имеет значение `ETagConstants.eTagResponseHeaderKey`
public var eTagHeaderKey: String

/// Инициаллизирует узел.
///
/// - Parameters:
/// - next: Следующий узел для обработки.
/// - eTagHeaderKey: Ключ, по которому необходимо получить eTag-токен из хедеров.
public init(next: (any ResponsePostprocessorLayerNode)?, eTagHeaderKey: String = ETagConstants.eTagResponseHeaderKey) {
self.next = next
self.eTagHeaderKey = eTagHeaderKey
}

/// Пытается получить eTag-токен по ключу.
/// В любом случае передает управление дальше.
open func process(
_ data: URLProcessedResponse,
logContext: LoggingContextProtocol
) async -> NodeResult<Void> {
guard let tag = data.response.allHeaderFields[self.eTagHeaderKey] as? String,
let url = data.request.url,
let urlAsKey = url.withOrderedQuery()
else {
return await next?.process(data, logContext: logContext) ?? .success(())
}

UserDefaults.etagStorage?.set(tag, forKey: urlAsKey)

return await next?.process(data, logContext: logContext) ?? .success(())
}
}

public extension URL {

/// Берет исходный URL
/// Получает словарь параметров query
/// Если параметров нет - возвращает `self.absoluteString`
/// Если параметры есть - сортирует их соединяет в одну строку
/// Удаляет query параметры из исходного URL
/// Склеивает строковое представление URL без парамтеров со сторокой параметров
///
/// **ВАЖНО**
///
/// Полученная строка может быть невалидным URL - т.к. задача этого метода - получить уникальный идентификатор из URL
/// Причем так, чтобы порядок перечисления query парамтеров был не важен.
func withOrderedQuery() -> String? {
guard var comp = URLComponents(string: self.absoluteString) else {
return nil
}

// ели нет query параметров, то просто возвращаем этот url т.к. ничего сортировать не надо
if comp.queryItems == nil || comp.queryItems?.isEmpty == true {
return self.absoluteString
}

let ordereedQueryString = comp.queryItems!
.map { $0.description }
.sorted()
.reduce("", { $1 + $0 })

// если в компонентах сбросить query в nil, то в итоговом URL не будет query
comp.query = nil

guard let url = comp.url else {
return nil
}

return url.absoluteString + ordereedQueryString
}
}
64 changes: 64 additions & 0 deletions NodeKit/NodeKit/CacheNode/URLCacheReaderNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// URLCacheReaderNode.swift
// CoreNetKitWithExample
//
// Created by Александр Кравченков on 28/11/2018.
// Copyright © 2018 Александр Кравченков. All rights reserved.
//

import Foundation

/// Ошибки для узла `URLCacheReaderNode`
///
/// - cantLoadDataFromCache: Случается, если запрос в кэш завершился с ошибкой
/// - cantSerializeJson: Случается, если запрос в кэш завершился успешно, но не удалось сериализовать ответ в JSON
/// - cantCastToJson: Случается, если сериализовать ответ удалось, но каст к `Json` или к `[Json]` завершился с ошибкой
public enum BaseURLCacheReaderError: Error {
case cantLoadDataFromCache
case cantSerializeJson
case cantCastToJson
}

/// Этот узел отвечает за чтение данных из URL кэша.
/// Сам по себе узел является листом и не может быть встроен в сквозную цепочку.
open class URLCacheReaderNode: AsyncNode {

public var needsToThrowError: Bool

public init(needsToThrowError: Bool) {
self.needsToThrowError = needsToThrowError
}

/// Посылает запрос в кэш и пытается сериализовать данные в JSON.
open func process(
_ data: URLNetworkRequest,
logContext: LoggingContextProtocol
) async -> NodeResult<Json> {
guard let cachedResponse = extractCachedURLResponse(data.urlRequest) else {
return .failure(BaseURLCacheReaderError.cantLoadDataFromCache)
}

guard let jsonObjsect = try? JSONSerialization.jsonObject(
with: cachedResponse.data,
options: .allowFragments
) else {
return .failure(BaseURLCacheReaderError.cantSerializeJson)
}

guard let json = jsonObjsect as? Json else {
guard let json = jsonObjsect as? [Json] else {
return .failure(BaseURLCacheReaderError.cantCastToJson)
}
return .success([MappingUtils.arrayJsonKey: json])
}

return .success(json)
}

private func extractCachedURLResponse(_ request: URLRequest) -> CachedURLResponse? {
if let response = URLCache.shared.cachedResponse(for: request) {
return response
}
return nil
}
}
31 changes: 31 additions & 0 deletions NodeKit/NodeKit/CacheNode/URLCacheWriterNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// URLCacheWriterNode.swift
// CoreNetKitWithExample
//
// Created by Александр Кравченков on 28/11/2018.
// Copyright © 2018 Александр Кравченков. All rights reserved.
//

import Foundation

/// Этот узел занимается записью данных в URL кэш.
/// - Important: это "глупая" реализация,
/// в которой не учитываются server-side политики и прочее.
/// Подразумечается, что этот узел не входит в цепочку, а является листом одного из узлов.
open class URLCacheWriterNode: AsyncNode {

/// Формирует `CachedURLResponse` с политикой `.allowed`, сохраняет его в кэш,
/// а затем возвращает сообщение об успешной операции.
open func process(
_ data: URLProcessedResponse,
logContext: LoggingContextProtocol
) async -> NodeResult<Void> {
let cached = CachedURLResponse(
response: data.response,
data: data.data,
storagePolicy: .allowed
)
URLCache.shared.storeCachedResponse(cached, for: data.request)
return .success(())
}
}
74 changes: 74 additions & 0 deletions NodeKit/NodeKit/CacheNode/URLNotModifiedTriggerNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// ETagRederNode.swift
// CoreNetKit
//
// Created by Александр Кравченков on 04/03/2019.
// Copyright © 2019 Кравченков Александр. All rights reserved.
//

import Foundation

/// Этот узел проверяет код ответа от сервера и в случае, если код равен 304 (NotModified)
/// Узел посылает запрос в URL кэш.
open class URLNotModifiedTriggerNode: AsyncNode {

// MARK: - Properties

/// Следующий узел для обратки.
public var next: any ResponseProcessingLayerNode

/// Узел для чтения данных из кэша.
public var cacheReader: any AsyncNode<URLNetworkRequest, Json>

// MARK: - Init and deinit

/// Инициаллизирует узел.
///
/// - Parameters:
/// - next: Следующий узел для обратки.
/// - cacheReader: Узел для чтения данных из кэша.
public init(next: some ResponseProcessingLayerNode,
cacheReader: some AsyncNode<URLNetworkRequest, Json>) {
self.next = next
self.cacheReader = cacheReader
}

// MARK: - Node

/// Проверяет http status-code. Если код соовуетствует NotModified, то возвращает запрос из кэша.
/// В протвином случае передает управление дальше.
open func process(
_ data: URLDataResponse,
logContext: LoggingContextProtocol
) async -> NodeResult<Json> {
guard data.response.statusCode == 304 else {
await logContext.add(makeErrorLog(code: data.response.statusCode))
return await next.process(data, logContext: logContext)
}

await logContext.add(makeSuccessLog())

return await cacheReader.process(
URLNetworkRequest(urlRequest: data.request),
logContext: logContext
)
}

// MARK: - Private Methods

private func makeErrorLog(code: Int) -> Log {
let msg = "Response status code = \(code) != 304 -> skip cache reading"
return Log(
logViewObjectName + msg,
id: objectName
)
}

private func makeSuccessLog() -> Log {
let msg = "Response status code == 304 -> read cache"
return Log(
logViewObjectName + msg,
id: objectName
)
}
}
36 changes: 36 additions & 0 deletions NodeKit/NodeKit/Chains/URLChainConfigModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation

/// Модель данных для конфигурироания цепочки преобразований для запроса в сеть.
public struct URLChainConfigModel {

/// HTTP метод, который будет использован цепочкой
public let method: Method

/// Маршрут до удаленного метода (в частном случае - URL endpoint'a)
public let route: URLRouteProvider

/// В случае классического HTTP это Header'ы запроса.
/// По-умолчанию пустой.
public let metadata: [String: String]

/// Кодировка данных для запроса.
/// По умолчанию`.json`
public let encoding: ParametersEncoding

/// Инициаллизирует объект.
///
/// - Parameters:
/// - method: HTTP метод, который будет использован цепочкой
/// - route: Маршрут до удаленного метод
/// - metadata: В случае классического HTTP это Header'ы запроса. По-умолчанию пустой.
/// - encoding: Кодировка данных для запроса. По-умолчанию `.json`
public init(method: Method,
route: URLRouteProvider,
metadata: [String: String] = [:],
encoding: ParametersEncoding = .json) {
self.method = method
self.route = route
self.metadata = metadata
self.encoding = encoding
}
}
Loading

0 comments on commit 023e441

Please sign in to comment.