Skip to content
Draft
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
32 changes: 17 additions & 15 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.

// swift-tools-version:6.0
import PackageDescription

let package = Package(
name: "soto-elasticsearch-nio-client",
name: "soto-elasticsearch",
platforms: [
.macOS(.v10_15)
.macOS(.v13)
],
products: [
.library(name: "SotoElasticsearchNIOClient", targets: ["SotoElasticsearchNIOClient"])
.library(
name: "SotoElasticsearch",
targets: ["SotoElasticsearch"]
)
],
dependencies: [
.package(url: "https://github.com/brokenhandsio/elasticsearch-nio-client.git", from: "0.4.0"),
.package(url: "https://github.com/soto-project/soto.git", from: "5.0.0"),
.package(
url: "https://github.com/brokenhandsio/elasticsearch-nio-client.git", branch: "swift-6"),
.package(url: "https://github.com/soto-project/soto.git", from: "7.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "SotoElasticsearchNIOClient", dependencies: [
.product(name: "ElasticsearchNIOClient", package: "elasticsearch-nio-client"),
name: "SotoElasticsearch",
dependencies: [
.product(name: "Elasticsearch", package: "elasticsearch-nio-client"),
.product(name: "SotoElasticsearchService", package: "soto"),
]),
]),
.testTarget(
name: "SotoElasticsearchNIOClientTests",
dependencies: ["SotoElasticsearchNIOClient"]),
name: "SotoElasticsearchTests",
dependencies: ["SotoElasticsearch"]
),
]
)
70 changes: 70 additions & 0 deletions Sources/SotoElasticsearchNIOClient/.swift-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"fileScopedDeclarationPrivacy": {
"accessLevel": "private"
},
"indentation": {
"spaces": 4
},
"indentConditionalCompilationBlocks": true,
"indentSwitchCaseLabels": false,
"lineBreakAroundMultilineExpressionChainComponents": false,
"lineBreakBeforeControlFlowKeywords": false,
"lineBreakBeforeEachArgument": false,
"lineBreakBeforeEachGenericRequirement": false,
"lineLength": 140,
"maximumBlankLines": 1,
"multiElementCollectionTrailingCommas": true,
"noAssignmentInExpressions": {
"allowedFunctions": [
"XCTAssertNoThrow"
]
},
"prioritizeKeepingFunctionOutputTogether": false,
"respectsExistingLineBreaks": true,
"rules": {
"AllPublicDeclarationsHaveDocumentation": false,
"AlwaysUseLiteralForEmptyCollectionInit": false,
"AlwaysUseLowerCamelCase": true,
"AmbiguousTrailingClosureOverload": true,
"BeginDocumentationCommentWithOneLineSummary": false,
"DoNotUseSemicolons": true,
"DontRepeatTypeInStaticProperties": true,
"FileScopedDeclarationPrivacy": true,
"FullyIndirectEnum": true,
"GroupNumericLiterals": true,
"IdentifiersMustBeASCII": true,
"NeverForceUnwrap": false,
"NeverUseForceTry": false,
"NeverUseImplicitlyUnwrappedOptionals": false,
"NoAccessLevelOnExtensionDeclaration": true,
"NoAssignmentInExpressions": true,
"NoBlockComments": true,
"NoCasesWithOnlyFallthrough": true,
"NoEmptyTrailingClosureParentheses": true,
"NoLabelsInCasePatterns": true,
"NoLeadingUnderscores": false,
"NoParensAroundConditions": true,
"NoPlaygroundLiterals": true,
"NoVoidReturnOnFunctionSignature": true,
"OmitExplicitReturns": false,
"OneCasePerLine": true,
"OneVariableDeclarationPerLine": true,
"OnlyOneTrailingClosureArgument": true,
"OrderedImports": true,
"ReplaceForEachWithForLoop": true,
"ReturnVoidInsteadOfEmptyTuple": true,
"TypeNamesShouldBeCapitalized": true,
"UseEarlyExits": false,
"UseExplicitNilCheckInConditions": true,
"UseLetInEveryBoundCaseVariable": true,
"UseShorthandTypeNames": true,
"UseSingleLinePropertyGetter": true,
"UseSynthesizedInitializer": true,
"UseTripleSlashForDocumentationComments": true,
"UseWhereClausesInForLoops": false,
"ValidateDocumentationComments": false
},
"spacesAroundRangeFormationOperators": false,
"tabWidth": 4,
"version": 1
}
126 changes: 126 additions & 0 deletions Sources/SotoElasticsearchNIOClient/SotoElasticsearchClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import AsyncHTTPClient
@_exported import Elasticsearch
import Foundation
import SotoElasticsearchService

public struct SotoElasticsearchClient {
public let elasticSearchClient: ElasticsearchClient

public init(
awsClient: AWSClient,
region: Region? = nil,
logger: Logger,
httpClient: HTTPClient,
scheme: String = "http",
host: String,
port: Int? = 9200,
username: String? = nil,
password: String? = nil,
jsonEncoder: JSONEncoder = JSONEncoder(),
jsonDecoder: JSONDecoder = JSONDecoder()
) throws {
let requester = SotoElasticsearchRequester(
awsClient: awsClient,
region: region,
logger: logger,
client: httpClient
)
self.elasticSearchClient = try ElasticsearchClient(
requester: requester,
logger: logger,
scheme: scheme,
host: host,
port: port,
username: username,
password: password,
jsonEncoder: jsonEncoder,
jsonDecoder: jsonDecoder
)
}

public func get<Document: Decodable>(id: String, from indexName: String) async throws -> ESGetSingleDocumentResponse<Document> {
try await self.elasticSearchClient.get(id: id, from: indexName)
}

public func bulk<Document: Encodable>(_ operations: [ESBulkOperation<Document, String>]) async throws -> ESBulkResponse {
try await self.elasticSearchClient.bulk(operations)
}

public func createDocument<Document: Encodable>(
_ document: Document, in indexName: String
) async throws -> ESCreateDocumentResponse<String> {
try await self.elasticSearchClient.createDocument(document, in: indexName)
}

public func createDocumentWithID<Document: Encodable & Identifiable>(
_ document: Document, in indexName: String
) async throws -> ESCreateDocumentResponse<Document.ID> {
try await self.elasticSearchClient.createDocumentWithID(document, in: indexName)
}

public func updateDocument<Document: Encodable, ID: Hashable>(
_ document: Document, id: ID, in indexName: String
) async throws -> ESUpdateDocumentResponse<ID> {
try await self.elasticSearchClient.updateDocument(document, id: id, in: indexName)
}

public func updateDocument<Document: Encodable & Identifiable>(
_ document: Document, in indexName: String
) async throws -> ESUpdateDocumentResponse<Document.ID> {
try await self.elasticSearchClient.updateDocument(document, in: indexName)
}

public func updateDocumentWithScript<Script: Encodable, ID: Hashable>(
_ script: Script, id: ID, in indexName: String
) async throws -> ESUpdateDocumentResponse<ID> {
try await self.elasticSearchClient.updateDocumentWithScript(script, id: id, in: indexName)
}

public func deleteDocument<ID: Hashable>(id: ID, from indexName: String) async throws -> ESDeleteDocumentResponse {
try await self.elasticSearchClient.deleteDocument(id: id, from: indexName)
}

public func searchDocuments<Document: Decodable>(
from indexName: String, searchTerm: String, type: Document.Type = Document.self
) async throws -> ESGetMultipleDocumentsResponse<Document> {
try await self.elasticSearchClient.searchDocuments(from: indexName, searchTerm: searchTerm, type: type)
}

public func searchDocumentsCount(from indexName: String, searchTerm: String?) async throws -> ESCountResponse {
try await self.elasticSearchClient.searchDocumentsCount(from: indexName, searchTerm: searchTerm)
}

public func searchDocumentsPaginated<Document: Decodable>(
from indexName: String, searchTerm: String, size: Int = 10, offset: Int = 0,
type: Document.Type = Document.self
) async throws -> ESGetMultipleDocumentsResponse<Document> {
try await self.elasticSearchClient.searchDocumentsPaginated(
from: indexName, searchTerm: searchTerm, size: size, offset: offset, type: type)
}

public func searchDocumentsCount<Query: Encodable>(from indexName: String, query: Query) async throws -> ESCountResponse {
try await self.elasticSearchClient.searchDocumentsCount(from: indexName, query: query)
}

public func searchDocumentsPaginated<Document: Decodable, QueryBody: Encodable>(
from indexName: String, queryBody: QueryBody, size: Int = 10, offset: Int = 0,
type: Document.Type = Document.self
) async throws -> ESGetMultipleDocumentsResponse<Document> {
try await self.elasticSearchClient.searchDocumentsPaginated(
from: indexName, queryBody: queryBody, size: size, offset: offset, type: type)
}

public func customSearch<Document: Decodable, Query: Encodable>(
from indexName: String, query: Query, type: Document.Type = Document.self
) async throws -> ESGetMultipleDocumentsResponse<Document> {
try await self.elasticSearchClient.customSearch(from: indexName, query: query, type: type)
}

public func deleteIndex(_ name: String) async throws -> ESAcknowledgedResponse {
try await self.elasticSearchClient.deleteIndex(name)
}

public func checkIndexExists(_ name: String) async throws -> Bool {
try await self.elasticSearchClient.checkIndexExists(name)
}
}

This file was deleted.

58 changes: 34 additions & 24 deletions Sources/SotoElasticsearchNIOClient/SotoElasticsearchRequester.swift
Original file line number Diff line number Diff line change
@@ -1,41 +1,51 @@
import ElasticsearchNIOClient
import SotoElasticsearchService
import AsyncHTTPClient
import Elasticsearch
import Foundation
import HTTPTypes
import Logging
import SotoCore
import SotoElasticsearchService

struct SotoElasticsearchRequester: ElasticsearchRequester {
let awsClient: AWSClient
let region: Region?
let eventLoop: EventLoop
let logger: Logger
let client: HTTPClient

func executeRequest(url urlString: String, method: HTTPMethod, headers: HTTPHeaders, body: ByteBuffer?) -> EventLoopFuture<HTTPClient.Response> {
func executeRequest(
url urlString: String,
method: HTTPRequest.Method,
headers: HTTPFields,
body: HTTPClientRequest.Body?
) async throws -> HTTPClientResponse {
let es = ElasticsearchService(client: awsClient, region: self.region)
guard let url = URL(string: urlString) else {
return self.eventLoop.makeFailedFuture(ElasticSearchClientError(message: "Failed to convert \(urlString) to a URL", status: nil))
}
let awsBody: AWSPayload
if let body = body {
awsBody = AWSPayload.byteBuffer(body)
} else {
awsBody = .empty
throw ElasticsearchClientError(message: "Failed to convert \(urlString) to a URL", status: nil)
}
return es.signHeaders(url: url, httpMethod: method, headers: headers, body: awsBody).flatMap { headers in
let request: HTTPClient.Request
do {
request = try HTTPClient.Request(url: url, method: method, headers: headers, body: awsBody.asByteBuffer().map { .byteBuffer($0) }
)
} catch {
return self.eventLoop.makeFailedFuture(error)
}
self.logger.trace("Request: \(request)")
if let requestBody = body {
let bodyString = String(buffer: requestBody)
self.logger.trace("Request body: \(bodyString)")

let awsBody =
if let body = body {
AWSHTTPBody(asyncSequence: body, length: nil)
} else {
AWSHTTPBody()
}
return self.client.execute(request: request, eventLoop: HTTPClient.EventLoopPreference.delegateAndChannel(on: self.eventLoop), logger: self.logger)

let httpMethod = HTTPMethod(rawValue: method.rawValue)

var httpHeaders = HTTPHeaders()
for header in headers {
httpHeaders.add(name: header.name.canonicalName, value: header.value)
}

let headers = try await es.signHeaders(url: url, httpMethod: httpMethod, headers: httpHeaders, body: awsBody)

var request = HTTPClientRequest(url: urlString)
request.method = httpMethod
request.headers = headers
request.body = body

self.logger.trace("Request: \(request)")

return try await self.client.execute(request, timeout: .seconds(30))
}
}
Loading