In vapor what is the proper way to create an ephemeral service? - vapor

I am trying to make an ephemeral service that reinitializes on each request.
I think this is the write way of doing so
// Services File
struct StackOverflowUrlService: Service {
let baseUrlString = "https://api.stackexchange.com/2.2/questions?order=desc&site=stackoverflow&sort=activity"
func requestForQuestions(for tag: String, timeAgo: TimeInterval) -> URLRepresentable {
return baseUrlString + "&tagged=\(tag)" + "fromdate=\(timeAgo)"
}
}
extension StackOverflowUrlService: ServiceType {
static func makeService(for worker: Container) throws -> StackOverflowUrlService {
return .init()
}
}
//Configure File
services.register(StackOverflowUrlService.self)
is there a better way?

Related

Initializing an s3 client in AWS SDK for Swift

The docs for the AWS SDk for Swift describe how to instantiate a service client:
let s3Client = try S3Client(region: "us-east-1")
However since this statement can throw errors it needs to be wrapped in a do catch block as shown on a separate documentation page:
do {
let s3 = try S3Client(region: "af-south-1")
// .....
} catch {
dump(error, name: "Error accessing S3 service")
exit(1)
}
I've created a public S3Manager class that will contain the specific functions I want to use in my code for manipulating s3 buckets. Typically I would instantiate the client in the class initializer but since it must be wrapped in a do catch block I have taken a different approach:
public class S3manager : ObservableObject {
public var client: S3Client?
public func initializeClient() {
do {
client = try S3Client(region: "us-west-2")
} catch {
fatalError("Error creating S3 client: \(error)")
}
}
public func listBucketFiles(bucket: String) async throws -> [String] {
if let clientInstance = client {
// ...
}
}
}
In my views I will reference the class as a StateObject or ObservedObject as appropriate and be certain to call initializeClient before calling any other methods I implement in the class (in the initial view's onAppear() method for example) Is this the best approach though?
I can't confirm that the code below works exactly as expected because the SDK is not finding my credentials in ~/.aws/credentialsor as environment variables, but I changed my approach to contain all of thes3` logic in it's own respective class:
import Foundation
import AWSS3
import ClientRuntime
import AWSClientRuntime
public class S3manager : ObservableObject {
private var client: S3Client?
public var files: [String]?
init() {
Task(priority: .high) {
do {
client = try S3Client(region: "us-west-2")
files = try await listBucketFiles(bucket: "bucket_name")
} catch {
print(error)
}
}
}
// from https://docs.aws.amazon.com/sdk-for-swift/latest/developer-guide/examples-s3-objects.html
public func listBucketFiles(bucket: String) async throws -> [String] {
if let clientInstance = client {
let input = ListObjectsV2Input(
bucket: bucket
)
let output = try await clientInstance.listObjectsV2(input: input)
var names: [String] = []
guard let objList = output.contents else {
return []
}
for obj in objList {
if let objName = obj.key {
names.append(objName)
}
}
return names
} else {
print("Client has not been initialized!")
return [String]()
}
}
}
This has the advantage that in ContentView I can simply reference the class member as follows:
struct ContentView: View {
#StateObject private var s3 = S3manager()
var body: some View {
VStack {
List(s3.files ?? ["Content Loading..."], id: \.self) {
Text($0)
}
}
}
}

How can I use Type Erasure with a protocol using associated type

I am working on a project that has a network client that basically follows the below pattern.
protocol EndpointType {
var baseURL: String { get }
}
enum ProfilesAPI {
case fetchProfileForUser(id: String)
}
extension ProfilesAPI: EndpointType {
var baseURL: String {
return "https://foo.bar"
}
}
protocol ClientType: class {
associatedtype T: EndpointType
func request(_ request: T) -> Void
}
class Client<T: EndpointType>: ClientType {
func request(_ request: T) -> Void {
print(request.baseURL)
}
}
let client = Client<ProfilesAPI>()
client.request(.fetchProfileForUser(id: "123"))
As part of tidying up this project and writing tests I have found the it is not possible to inject a client when conforming to the ClientType protocol.
let client: ClientType = Client<ProfilesAPI>() produces an error:
error: member 'request' cannot be used on value of protocol type
'ClientType'; use a generic constraint instead
I would like to maintain the current pattern ... = Client<ProfilesAPI>()
Is it possible to achieve this using type erasure? I have been reading but am not sure how to make this work.
To your actual question, the type eraser is straight-forward:
final class AnyClient<T: EndpointType>: ClientType {
let _request: (T) -> Void
func request(_ request: T) { _request(request) }
init<Client: ClientType>(_ client: Client) where Client.T == T {
_request = client.request
}
}
You'll need one of these _func/func pairs for each requirement in the protocol. You can use it this way:
let client = AnyClient(Client<ProfilesAPI>())
And then you can create a testing harness like:
class RecordingClient<T: EndpointType>: ClientType {
var requests: [T] = []
func request(_ request: T) -> Void {
requests.append(request)
print("recording: \(request.baseURL)")
}
}
And use that one instead:
let client = AnyClient(RecordingClient<ProfilesAPI>())
But I don't really recommend this approach if you can avoid it. Type erasers are a headache. Instead, I would look inside of Client, and extract the non-generic part into a ClientEngine protocol that doesn't require T. Then make that swappable when you construct the Client. Then you don't need type erasers, and you don't have to expose an extra protocol to the callers (just EndpointType).
For example, the engine part:
protocol ClientEngine: class {
func request(_ request: String) -> Void
}
class StandardClientEngine: ClientEngine {
func request(_ request: String) -> Void {
print(request)
}
}
The client that holds an engine. Notice how it uses a default parameter so that callers don't have to change anything.
class Client<T: EndpointType> {
let engine: ClientEngine
init(engine: ClientEngine = StandardClientEngine()) { self.engine = engine }
func request(_ request: T) -> Void {
engine.request(request.baseURL)
}
}
let client = Client<ProfilesAPI>()
And again, a recording version:
class RecordingClientEngine: ClientEngine {
var requests: [String] = []
func request(_ request: String) -> Void {
requests.append(request)
print("recording: \(request)")
}
}
let client = Client<ProfilesAPI>(engine: RecordingClientEngine())

How to proper inject dependency using swinject

I'm trying to inject dependency using Swinject, and I have no clue what I'm doing wrong.
I have protocol, that handles registring user.
protocol AuthServiceProtocol {
func registerUser(email: String, password: String, completion: #escaping CompletionHandler) }
and a class that conforms to this protocol make all the logic:
class AuthService: AuthServiceProtocol {
func registerUser(email: String, password: String, completion: #escaping CompletionHandler) {
let lowerCaseMail = email.lowercased()
let body: [String: Any] = [
"email": lowerCaseMail,
"password" : password
]
Alamofire.request(URL_REGISTER, method: .post, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseString { (response) in
if response.result.error == nil {
completion(true)
} else {
completion(false)
debugPrint(response.result.error as Any)
}
}
}
}
so, in AppDelegate we register container and it looks like:
let container = Container() { container in
container.register(AuthServiceProtocol.self) { _ in AuthService() }.inObjectScope(.container)
container.register(CreateAccountVC.self) { r in
let controller = CreateAccountVC()
controller.authService = r.resolve(AuthServiceProtocol.self)
return controller
}
}
but in CreateAccountVC authService is empty. Any ideas how can i do it?
CreateAccountVC is a subclass of ViewController, i have try'ed it by property, and constructors, but it's nil all the time.
Check your code:
var container : Container {
let container = Container()
container.register(AuthServiceProtocol.self) { _ in AuthService() }.inObjectScope(.container)
container.register(CreateAccountVC.self) { r in
let controller = CreateAccountVC()
controller.authService = r.resolve(AuthServiceProtocol.self)
print(r.resolve(AuthServiceProtocol.self))
return controller
}
return container
}
You have computed property and every time you call it, it creates a NEW Container object.
Refactor your code to have a single Container and I believe you will be good to go.
EDIT:
Here's a working code snippet.
Below is a small wrapper class to abstract concrete DI service (in case Swinject is one day replace by something else):
import Swinject
public class ConfigurationProvider {
// Currently using Swinject
private let backingService = Container()
// Singleton
public static let shared = ConfigurationProvider()
// Hidden initializer
private init() {}
// MARK: - Bind / Resolve
public func bind<T>(interface: T.Type, to assembly: T) {
backingService.register(interface) { _ in assembly }
}
public func resolve<T>(interface: T.Type) -> T! {
return backingService.resolve(interface)
}
}
// Extension methods to ignore 'shared.' call, like:
// ConfigurationProvider.bind(interface: IAssembly, to: Assembly())
// ConfigurationProvider.resolve(interface: IAssembly)
public extension ConfigurationProvider {
static func bind<T>(interface: T.Type, to assembly: T) {
ConfigurationProvider.shared.bind(interface: interface, to: assembly)
}
static func resolve<T>(interface: T.Type) -> T! {
return ConfigurationProvider.shared.resolve(interface: interface)
}
}
Usage:
class RSAuthLoginModuleAssembly: IAuthLoginModuleAssembly {
}
// Register:
ConfigurationProvider.bind(interface: IAuthLoginModuleAssembly.self, to: ConcreteAuthLoginModuleAssembly())
// Resolve:
guard let assembly = ConfigurationProvider.resolve(interface: IAuthLoginModuleAssembly.self) else {
throw NSError(domain: "Assembly cannot be nil", code: 999, userInfo: nil)
}

Static member cannot be used on protocol meta type

What I am trying to accomplish is to make Proxy protocol that will route my class to appropriate service.
I have 3 types of service per 1 proxy:OnlineService,OfflineService,DemoService each for one of modes (online, offline,demo).
I created protocol :
protocol Proxy {
associatedtype ServiceProtocol
associatedtype OfflineServiceType: OfflineService
associatedtype OnlineServiceType: WebService
associatedtype DemoServiceType: DemoService
}
extension Proxy {
static var service: ServiceProtocol.Type {
if isOnlineMode() {
return OfflineServiceType.self as! ServiceProtocol.Type
} else if isDemoMode(){
return DemoServiceType.self as! ServiceProtocol.Type
}else{
return OnlineServiceType.self as! ServiceProtocol.Type
}
}
}
and then on Customer proxy class
class CustomersServiceProxy: Proxy, CustomersService {
typealias ServiceProtocol = CustomersService
typealias OfflineServiceType = CustomersOfflineService
typealias OnlineServiceType = CustomerWebService
public static func customerDetails(for customer: Customer, completion: #escaping (CustomerDetails) -> Void) {
service.customerDetails(for: customer, completion: completion)
}
}
But I got error:
Static member 'customerDetails' cannot be used on protocol metataype 'CustomerServiceProxy.ServiceProtocol.Protocol' (aka 'CustomerService.Protocol').
I suggest that this happens because Proxy service variable is returning CustomerService.Type instead of Type that is conforming to CustomerService. Is there any workaround to this?
well, u r missing a step, for example:
protocol Proxcy {}
extention Proxcy {
static func a() { print("A")
}
struct A: Proxcy {}
struct B: Proxcy {
A.a()
}

Mocking a class with Rx extensions

Im trying to achieve dependency injection with protocols in Swift :)
My system under test calls .rx.signIn() on the dependency, meaning that it has extended Reactive where Base : ConcreteObject.
Is it possible to setup a protocol ready to receive .rx calls?
Any small example or alternative would be greatly appreciated.
I had the same problem during the upgrade from RxSwift2 to RxSwift3. In RxSwift2 I simply used a protocol for the rx-methods in my production code (ex. rx_JSON()) and injected a simple mock object adhering to that protocol in my unit tests.
After a short detour into generics, associated type and type erasure I settled with the following setup, which closely matches my RxSwift2-setup:
For example to hide URLSession as the concrete implementation of my HTTP-calls, I created a protocol RemoteClient:
protocol RemoteClient {
func json(url: URL) -> Observable<Any>
}
Then in my production code I reference only that protocol:
class SomeService {
var remoteClient: RemoteClient
init(remoteClient: RemoteClient) {
self.remoteClient = remoteClient
}
func getSomeData(_ id: String) -> Observable<JSONDictionary> {
let urlString = "..."
return remoteClient
.json(url: URL(string: urlString)!)
.map { data in
guard let show = data as? JSONDictionary else {
throw ParsingError.json
}
return show
}
}
}
In my unit tests I created mock objects which adhere to the RemoteClient protocol:
class RemoteClientMock: RemoteClient {
var output: Any?
var verifyInput: ((URL) -> Void)?
func json(url: URL) -> Observable<Any> {
verifyInput?(url)
return Observable.create { observer in
if let data = self.output {
observer.on(.next(data))
}
observer.on(.completed)
return Disposables.create()
}
}
}
And inject them in the system under test:
class SomeServiceTest: TestCase {
let disposeBag = DisposeBag()
func testGetSomeData() {
// setup
let expectation = self.expectation(description: "request succeeded")
let remoteClientMock = RemoteClientMock()
remoteClientMock.verifyInput = { url in
XCTAssertEqual(URL(string: "some url"), url)
}
remoteClientMock.output = ["id": 4711]
let service = SomeService(remoteClient: remoteClientMock)
// exercise
let observable = service.getSomeData("4711")
// verify
observable
.subscribe(onNext: { data in
XCTAssertEqual(4711, data["id"] as? Int)
expectation.fulfill()
})
.addDisposableTo(disposeBag)
waitForExpectations(timeout: 1, handler: nil)
}
}
So I simply hide the rx-namespace behind my protocol. I do that for now by using an adapter:
let remoteClient = URLSessionRemoteClientAdapter(urlSession: URLSession.shared)
let service = SomeService(remoteClient: remoteClient)
struct URLSessionRemoteClientAdapter: RemoteClient {
let urlSession: URLSession
init(urlSession: URLSession) {
self.urlSession = urlSession
}
func json(url: URL) -> Observable<Any> {
return urlSession.rx.json(url: url)
}
}

Resources