diff --git a/Sources/ElasticsearchQueryBuilder/Components.swift b/Sources/ElasticsearchQueryBuilder/Components.swift index 9a5ca2f..d866ba1 100644 --- a/Sources/ElasticsearchQueryBuilder/Components.swift +++ b/Sources/ElasticsearchQueryBuilder/Components.swift @@ -199,6 +199,82 @@ extension esb { } } + /// Adds `term` block to the query syntax. + /// + /// Excludes the component if value is nil. + public struct Term: DictComponent { + let field: String + let value: String? + public init(_ field: String, _ value: String?) { + self.field = field + self.value = value + } + public init(_ field: String, _ value: V?) where V.RawValue == String { + self.field = field + self.value = value?.rawValue + } + public init(_ field: String, describing value: V?) { + self.field = field + self.value = value?.description + } + public func makeDict() -> QueryDict { + guard let value = self.value else { return [:] } + return [ "term" : [ self.field : .string(value) ] ] + } + } + + /// Adds multiple `term` block to the query syntax. + /// + /// Excludes the component if values is empty. + public struct TermsAND: ArrayComponent { + let field: String + let values: [String] + public init(_ field: String, _ values: [String]) { + self.field = field + self.values = values + } + public init(_ field: String, _ values: [V]) where V: RawRepresentable, V.RawValue == String { + self.field = field + self.values = values.map(\.rawValue) + } + public init(_ field: String, describing values: [V]) where V: CustomStringConvertible { + self.field = field + self.values = values.map(\.description) + } + public func makeArray() -> [QueryDict] { + return self.values.map { + [ "term" : [ self.field : .string($0) ] ] + } + } + } + + /// Adds a `terms` block to the query syntax. + /// + /// Excludes the component if values is empty. + public struct TermsOR: DictComponent { + let field: String + let values: [String] + public init(_ field: String, _ values: [String]) { + self.field = field + self.values = values + } + public init(_ field: String, _ values: [V]) where V: RawRepresentable, V.RawValue == String { + self.field = field + self.values = values.map(\.rawValue) + } + public init(_ field: String, describing values: [V]) where V: CustomStringConvertible { + self.field = field + self.values = values.map(\.description) + } + public func makeDict() -> QueryDict { + if self.values.isEmpty { + return [:] + } else { + return [ "terms" : [ self.field : .array(self.values) ] ] + } + } + } + /// Adds `knn` block to the query syntax. public struct kNearestNeighbor: DictComponent { let field: String diff --git a/Tests/ElasticsearchQueryBuilderTests/ComponentTests.swift b/Tests/ElasticsearchQueryBuilderTests/ComponentTests.swift index 35c58da..8dcd9a0 100644 --- a/Tests/ElasticsearchQueryBuilderTests/ComponentTests.swift +++ b/Tests/ElasticsearchQueryBuilderTests/ComponentTests.swift @@ -256,6 +256,64 @@ final class BoolTests: XCTestCase { } } +final class TermTests: XCTestCase { + func testBuild() throws { + @ElasticsearchQueryBuilder func build() -> some esb.QueryDSL { + esb.Term("name", "joe") + } + XCTAssertNoDifference(build().makeQuery(), [ + "term": [ "name": "joe" ] + ]) + } + func testBuildEmpty() throws { + @ElasticsearchQueryBuilder func build() -> some esb.QueryDSL { + esb.Term("name", nil) + } + XCTAssertNoDifference(build().makeQuery(), [:]) + } +} + +final class TermsORTests: XCTestCase { + func testBuild() throws { + @ElasticsearchQueryBuilder func build() -> some esb.QueryDSL { + esb.TermsOR("name", ["joe", "mary"]) + } + XCTAssertNoDifference(build().makeQuery(), [ + "terms": [ "name": ["joe", "mary"] ] + ]) + } + func testBuildEmpty() throws { + @ElasticsearchQueryBuilder func build() -> some esb.QueryDSL { + esb.TermsOR("name", []) + } + XCTAssertNoDifference(build().makeQuery(), [:]) + } +} + +final class TermsANDTests: XCTestCase { + func testBuild() throws { + @ElasticsearchQueryBuilder func build() -> some esb.QueryDSL { + esb.Filter { + esb.TermsAND("name", ["joe", "mary"]) + } + } + XCTAssertNoDifference(build().makeQuery(), [ + "filter": [ + [ "term": [ "name": "joe" ] ], + [ "term": [ "name": "mary" ] ], + ] + ]) + } + func testBuildEmpty() throws { + @ElasticsearchQueryBuilder func build() -> some esb.QueryDSL { + esb.Filter { + esb.TermsAND("name", []) + } + } + XCTAssertNoDifference(build().makeQuery(), [:]) + } +} + final class KNearestNeighborTests: XCTestCase { func testBuildBasic() throws { @ElasticsearchQueryBuilder func build() -> some esb.QueryDSL {