Skip to content

Commit

Permalink
feat: Query rule custom data widget (#132)
Browse files Browse the repository at this point in the history
* feat: Implement Query rule custom data widget
  • Loading branch information
VladislavFitz authored Oct 15, 2020
1 parent f9d948f commit 6261916
Show file tree
Hide file tree
Showing 9 changed files with 571 additions and 0 deletions.
17 changes: 17 additions & 0 deletions Sources/InstantSearchCore/Helper/Decodable+JSON.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Decodable+JSON.swift
//
//
// Created by Vladislav Fitc on 12/10/2020.
//

import Foundation

extension Decodable {

init(json: JSON) throws {
let data = try JSONEncoder().encode(json)
self = try JSONDecoder().decode(Self.self, from: data)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// QueryRuleCustomDataConnector+Controller.swift
//
//
// Created by Vladislav Fitc on 10/10/2020.
//

import Foundation

public extension QueryRuleCustomDataConnector {

/**
- Parameters:
- searcher: Searcher that handles your searches
- interactor: External custom data interactor
- controller: Controller interfacing with a concrete custom data view
- presenter: Presenter defining how a model appears in the controller
*/
convenience init<Controller: ItemController, Output>(searcher: SingleIndexSearcher,
interactor: Interactor = .init(),
controller: Controller,
presenter: @escaping (Model?) -> Output) where Controller.Item == Output {

self.init(searcher: searcher, interactor: interactor)
let controllerConnection = interactor.connectController(controller, presenter: presenter)
controllerConnections.append(controllerConnection)
}

/**
- Parameters:
- searcher: Searcher that handles your searches.
- interactor: External custom data interactor
- controller: Controller interfacing with a concrete custom data view
*/
convenience init<Controller: ItemController>(searcher: SingleIndexSearcher,
interactor: Interactor = .init(),
controller: Controller) where Controller.Item == Model? {
self.init(searcher: searcher, interactor: interactor)
let controllerConnection = interactor.connectController(controller, presenter: { $0 })
controllerConnections.append(controllerConnection)
}

}

public extension QueryRuleCustomDataConnector {

/**
- Parameters:
- searcher: Searcher that handles your searches.
- queryIndex: Index of query from response of which the user data will be extracted
- interactor: External custom data interactor
- controller: Controller interfacing with a concrete custom data view
- presenter: Presenter defining how a model appears in the controller
*/
convenience init<Controller: ItemController, Output>(searcher: MultiIndexSearcher,
queryIndex: Int,
interactor: Interactor = .init(),
controller: Controller,
presenter: @escaping (Model?) -> Output) where Controller.Item == Output {

self.init(searcher: searcher, queryIndex: queryIndex, interactor: interactor)
let controllerConnection = interactor.connectController(controller, presenter: presenter)
controllerConnections = [controllerConnection]
}

/**
- Parameters:
- searcher: Searcher that handles your searches.
- queryIndex: Index of query from response of which the user data will be extracted
- interactor: External custom data interactor
- controller: Controller interfacing with a concrete custom data view
*/
convenience init<Controller: ItemController>(searcher: MultiIndexSearcher,
queryIndex: Int,
interactor: Interactor = .init(),
controller: Controller) where Controller.Item == Model? {
self.init(searcher: searcher, queryIndex: queryIndex, interactor: interactor)
let controllerConnection = interactor.connectController(controller, presenter: { $0 })
controllerConnections.append(controllerConnection)
}

}

public extension QueryRuleCustomDataConnector {

/**
Establishes a connection with the controller
- Parameters:
- controller: Controller interfacing with a concrete custom data view
- presenter: Presenter defining how a model appears in the controller
- Returns: Established connection
*/
@discardableResult func connectController<Controller: ItemController, Output>(_ controller: Controller,
presenter: @escaping (Model?) -> Output) -> QueryRuleCustomDataInteractor<Model>.ControllerConnection<Controller, Output> {
let connection = interactor.connectController(controller, presenter: presenter)
controllerConnections.append(connection)
return connection
}

/**
Establishes a connection with the controller
- Parameters:
- controller: Controller interfacing with a concrete custom data view
- Returns: Established connection
*/
@discardableResult func connectController<Controller: ItemController>(_ controller: Controller) -> QueryRuleCustomDataInteractor<Model>.ControllerConnection<Controller, Model?> {
let connection = interactor.connectController(controller, presenter: { $0 })
controllerConnections.append(connection)
return connection
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// QueryRuleCustomDataConnector.swift
//
//
// Created by Vladislav Fitc on 09/10/2020.
//

import Foundation

/// Component that displays custom data from rules.
///
/// [Documentation](https://www.algolia.com/doc/api-reference/widgets/query-rule-custom-data/ios/)
public class QueryRuleCustomDataConnector<Model: Decodable> {

public typealias Interactor = QueryRuleCustomDataInteractor<Model>

/// Logic applied to the custom model
public let interactor: Interactor

/// Connection between hits interactor and searcher
public let searcherConnection: Connection

/// Connections between interactor and controllers
public var controllerConnections: [Connection]

internal init(interactor: Interactor,
connectSearcher: (Interactor) -> Connection) {
self.interactor = interactor
searcherConnection = connectSearcher(interactor)
controllerConnections = []
searcherConnection.connect()
}

}

public extension QueryRuleCustomDataConnector {

/**
- Parameters:
- searcher: Searcher that handles your searches
- interactor: External custom data interactor
*/
convenience init(searcher: SingleIndexSearcher,
interactor: Interactor = .init()) {
self.init(interactor: interactor) {
QueryRuleCustomDataInteractor<Model>.SingleIndexSearcherConnection(interactor: $0, searcher: searcher)
}
}

/**
- Parameters:
- searcher: Searcher that handles your searches
- queryIndex: Index of query from response of which the user data will be extracted
- interactor: External custom data interactor
*/
convenience init(searcher: MultiIndexSearcher,
queryIndex: Int,
interactor: Interactor = .init()) {
self.init(interactor: interactor) {
QueryRuleCustomDataInteractor<Model>.MultiIndexSearcherConnection(interactor: $0, searcher: searcher, queryIndex: queryIndex)
}
}

}

extension QueryRuleCustomDataConnector: Connection {

public func connect() {
searcherConnection.connect()
controllerConnections.forEach { $0.connect() }
}

public func disconnect() {
searcherConnection.disconnect()
controllerConnections.forEach { $0.disconnect() }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// QueryRuleCustomDataInteractor+MultiIndexSearcher.swift
//
//
// Created by Vladislav Fitc on 10/10/2020.
//

import Foundation

extension QueryRuleCustomDataInteractor {

/// Connection between a rule custom data logic and a multi-index searcher
public struct MultiIndexSearcherConnection: Connection {

/// Logic applied to the custom model
public let interactor: QueryRuleCustomDataInteractor

/// Searcher that handles your searches
public let searcher: MultiIndexSearcher

/// Index of query from response of which the user data will be extracted
public let queryIndex: Int

/**
- Parameters:
- interactor: Interactor to connect
- searcher: Searcher to connect
- queryIndex: Index of query from response of which the user data will be extracted
*/
public init(interactor: QueryRuleCustomDataInteractor,
searcher: MultiIndexSearcher,
queryIndex: Int) {
self.searcher = searcher
self.queryIndex = queryIndex
self.interactor = interactor
}

public func connect() {
searcher.onResults.subscribe(with: interactor) { (interactor, searchResponse) in
interactor.extractModel(from: searchResponse.results[queryIndex])
}
}

public func disconnect() {
searcher.onResults.cancelSubscription(for: interactor)
}

}

}

public extension QueryRuleCustomDataInteractor {

/**
- Parameters:
- searcher: Searcher to connect
- queryIndex: Index of query from response of which the user data will be extracted
*/
@discardableResult func connectSearcher(_ searcher: MultiIndexSearcher,
toQueryAtIndex queryIndex: Int) -> MultiIndexSearcherConnection {
let connection = MultiIndexSearcherConnection(interactor: self, searcher: searcher, queryIndex: queryIndex)
connection.connect()
return connection
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// QueryRuleCustomDataInteractor+SingleIndexSearcher.swift
//
//
// Created by Vladislav Fitc on 10/10/2020.
//

import Foundation

extension QueryRuleCustomDataInteractor {

/// Connection between a rule custom data logic and a single index searcher
public struct SingleIndexSearcherConnection: Connection {

/// Logic applied to the custom model
public let interactor: QueryRuleCustomDataInteractor

/// Searcher that handles your searches
public let searcher: SingleIndexSearcher

/**
- Parameters:
- interactor: Interactor to connect
- searcher: Searcher to connect
*/
public init(interactor: QueryRuleCustomDataInteractor,
searcher: SingleIndexSearcher) {
self.searcher = searcher
self.interactor = interactor
}

public func connect() {
searcher.onResults.subscribe(with: interactor) { (interactor, searchResponse) in
interactor.extractModel(from: searchResponse)
}
}

public func disconnect() {
searcher.onResults.cancelSubscription(for: interactor)
}

}

}

public extension QueryRuleCustomDataInteractor {

/**
- Parameters:
- searcher: Searcher to connect
*/
@discardableResult func connectSearcher(_ searcher: SingleIndexSearcher) -> SingleIndexSearcherConnection {
let connection = SingleIndexSearcherConnection(interactor: self, searcher: searcher)
connection.connect()
return connection
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// QueryRuleCustomDataInteractor.swift
//
//
// Created by Vladislav Fitc on 10/10/2020.
//

import Foundation

/// Component encapsulating the logic applied to the custom model
public class QueryRuleCustomDataInteractor<Model: Decodable>: ItemInteractor<Model?> {

public override init(item: Model? = nil) {
super.init(item: item)
}

}

extension QueryRuleCustomDataInteractor {

func extractModel(from searchResponse: SearchResponse) {
if let userData = searchResponse.userData,
let model = userData.compactMap({ try? Model(json: $0) }).first {
item = model
} else {
item = nil
}
}

}

public extension QueryRuleCustomDataInteractor {

/**
Establishes a connection with the controller
- Parameters:
- controller: Controller interfacing with a concrete custom data view
- Returns: Established connection
*/
@discardableResult func connectController<Controller: ItemController>(_ controller: Controller) -> ItemInteractor<Model?>.ControllerConnection<Controller, Model?> {
super.connectController(controller, presenter: { $0 })
}

}
Loading

0 comments on commit 6261916

Please sign in to comment.