Skip to content

Commit de625ca

Browse files
feat: Algolia Answers (#701)
* Implement Answers structures, endpoint and tests * Add Find Answers snippet
1 parent eefc439 commit de625ca

29 files changed

+1936
-966
lines changed

Dockerfile

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,3 @@ COPY ./Package.* ./
99
RUN swift package resolve
1010

1111
COPY . .
12-
13-
RUN swift build \
14-
--enable-test-discovery \
15-
-c release \
16-
-Xswiftc -g \
17-
-j 4
18-
19-
# RUN swift test --enable-test-discovery
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Command+Answers.swift
3+
//
4+
//
5+
// Created by Vladislav Fitc on 19/11/2020.
6+
//
7+
8+
import Foundation
9+
#if canImport(FoundationNetworking)
10+
import FoundationNetworking
11+
#endif
12+
13+
extension Command {
14+
15+
enum Answers {
16+
17+
struct Find: AlgoliaCommand {
18+
19+
let callType: CallType = .read
20+
let urlRequest: URLRequest
21+
let requestOptions: RequestOptions?
22+
23+
init(indexName: IndexName,
24+
query: AnswersQuery,
25+
requestOptions: RequestOptions?) {
26+
self.requestOptions = requestOptions
27+
let path: IndexCompletion = .answers >>> .index(indexName) >>> .prediction
28+
urlRequest = .init(method: .post, path: path, body: query.httpBody, requestOptions: self.requestOptions)
29+
}
30+
31+
}
32+
33+
}
34+
35+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// AssertionTestHelper.swift
3+
//
4+
//
5+
// Created by Vladislav Fitc on 20/11/2020.
6+
//
7+
8+
import Foundation
9+
10+
/// Our custom drop-in replacement `assertionFailure`.
11+
///
12+
/// This will call Swift's `assertionFailure` by default (and terminate the program).
13+
/// But it can be changed at runtime to be tested instead of terminating.
14+
func assertionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) {
15+
assertionClosure(message(), file, line)
16+
}
17+
18+
/// The actual function called by our custom `precondition`.
19+
var assertionClosure: (String, StaticString, UInt) -> Void = defaultAssertionClosure
20+
let defaultAssertionClosure = { Swift.assertionFailure($0, file: $1, line: $2) }
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// Index+Answers.swift
3+
//
4+
//
5+
// Created by Vladislav Fitc on 20/11/2020.
6+
//
7+
8+
import Foundation
9+
10+
public extension Index {
11+
12+
/**
13+
Returns answers that match the query.
14+
15+
- Parameter query: The AnswersQuery used to search.
16+
- Parameter requestOptions: Configure request locally with RequestOptions.
17+
- Parameter completion: Result completion
18+
- Returns: Launched asynchronous operation
19+
*/
20+
@discardableResult func findAnswers(for query: AnswersQuery,
21+
requestOptions: RequestOptions? = nil,
22+
completion: @escaping ResultCallback<SearchResponse>) -> Operation & TransportTask {
23+
let command = Command.Answers.Find(indexName: name, query: query, requestOptions: requestOptions)
24+
return execute(command, completion: completion)
25+
}
26+
27+
/**
28+
Returns answers that match the query.
29+
30+
- Parameter query: The AnswersQuery used to search.
31+
- Parameter requestOptions: Configure request locally with RequestOptions.
32+
- Returns: SearchResponse object
33+
*/
34+
@discardableResult func findAnswers(for query: AnswersQuery,
35+
requestOptions: RequestOptions? = nil) throws -> SearchResponse {
36+
let command = Command.Answers.Find(indexName: name, query: query, requestOptions: requestOptions)
37+
return try execute(command)
38+
}
39+
40+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// AnswersQuery+Language.swift
3+
//
4+
//
5+
// Created by Vladislav Fitc on 20/11/2020.
6+
//
7+
8+
import Foundation
9+
10+
extension AnswersQuery {
11+
12+
public enum Language: RawRepresentable, Codable {
13+
14+
case english
15+
case custom(String)
16+
17+
public var rawValue: AlgoliaSearchClient.Language {
18+
switch self {
19+
case .english:
20+
return .english
21+
case .custom(let rawValue):
22+
return .init(rawValue: rawValue)
23+
}
24+
}
25+
26+
public init(rawValue: AlgoliaSearchClient.Language) {
27+
switch rawValue {
28+
case .english:
29+
self = .english
30+
default:
31+
self = .custom(rawValue.rawValue)
32+
}
33+
}
34+
35+
public init(from decoder: Decoder) throws {
36+
let rawLanguage = try AlgoliaSearchClient.Language(from: decoder)
37+
self.init(rawValue: rawLanguage)
38+
}
39+
40+
public func encode(to encoder: Encoder) throws {
41+
try rawValue.encode(to: encoder)
42+
}
43+
44+
}
45+
46+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//
2+
// AnswersQuery.swift
3+
//
4+
//
5+
// Created by Vladislav Fitc on 19/11/2020.
6+
//
7+
8+
import Foundation
9+
10+
public struct AnswersQuery: SearchParameters, Codable {
11+
12+
/// The query for which to retrieve results.
13+
public var query: String
14+
15+
/// The languages in the query.
16+
///
17+
/// Default value: [.english]
18+
public var queryLanguages: [Language]
19+
20+
/// Attributes to use for predictions.
21+
///
22+
/// Default value: ["*"] (all searchable attributes)
23+
/// - Warning: All your attributesForPrediction must be part of your searchableAttributes.
24+
public var attributesForPrediction: [Attribute]?
25+
26+
/// Maximum number of answers to retrieve from the Answers Engine.
27+
///
28+
/// Default value: 10
29+
///
30+
/// Cannot be greater than 1000.
31+
public var nbHits: Int?
32+
33+
/// Threshold for the answers’ confidence score: only answers with extracts that score above this threshold are returned.
34+
///
35+
/// Default value: 0.0
36+
public var threshold: Double?
37+
38+
/// Not supported by Answers
39+
public var attributesToSnippet: [Snippet]? {
40+
get {
41+
return nil
42+
}
43+
// swiftlint:disable:next unused_setter_value
44+
set {
45+
assertionFailure("attributesToSnippet is not supported by answers")
46+
}
47+
}
48+
49+
/// Not supported by Answers
50+
public var hitsPerPage: Int? {
51+
get {
52+
return nil
53+
}
54+
// swiftlint:disable:next unused_setter_value
55+
set {
56+
assertionFailure("hitsPerPage is not supported by answers")
57+
}
58+
}
59+
60+
/// Not supported by Answers
61+
public var restrictSearchableAttributes: [Attribute]? {
62+
get {
63+
return nil
64+
}
65+
// swiftlint:disable:next unused_setter_value
66+
set {
67+
assertionFailure("restrictSearchableAttributes is not supported by answers")
68+
}
69+
}
70+
71+
internal var params: SearchParametersStorage?
72+
73+
public init(query: String, queryLanguages: [Language]) {
74+
self.query = query
75+
self.queryLanguages = queryLanguages
76+
}
77+
78+
}
79+
80+
extension AnswersQuery: ExpressibleByStringLiteral {
81+
82+
public init(stringLiteral value: String) {
83+
self.init(query: value, queryLanguages: [.english])
84+
}
85+
86+
}
87+
88+
extension AnswersQuery: SearchParametersStorageContainer {
89+
90+
var searchParametersStorage: SearchParametersStorage {
91+
get {
92+
return params ?? .init()
93+
}
94+
95+
set {
96+
params = newValue
97+
}
98+
}
99+
100+
}
101+
102+
extension AnswersQuery: Builder {}

0 commit comments

Comments
 (0)