Skip to content
This repository has been archived by the owner on Nov 16, 2020. It is now read-only.

Commit

Permalink
Merge pull request #14 from vapor/singleton-updates
Browse files Browse the repository at this point in the history
singleton updates
  • Loading branch information
tanner0101 authored Feb 21, 2018
2 parents e02507a + 8116ea5 commit ed02e0f
Show file tree
Hide file tree
Showing 12 changed files with 91 additions and 210 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ let package = Package(
.package(url: "https://github.com/vapor/async.git", "1.0.0-beta.1"..<"1.0.0-beta.2"),

// A library to aid Vapor users with better debugging around the framework
.package(url: "https://github.com/vapor/core.git", "3.0.0-beta.1"..<"3.0.0-beta.2"),
.package(url: "https://github.com/vapor/core.git", "3.0.0-beta.2"..<"3.0.0-beta.3"),
],
targets: [
.target(name: "Service", dependencies: ["Async", "Debugging"]),
Expand Down
6 changes: 0 additions & 6 deletions Sources/Service/BasicServiceFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ public struct BasicServiceFactory: ServiceFactory {

/// See ServiceFactory.serviceTag
public var serviceTag: String?

/// See ServiceFactory.serviceIsSingleton
public var serviceIsSingleton: Bool

/// Accepts a container and worker, returning an
/// initialized service.
public typealias ServiceFactoryClosure = (Container) throws -> Any
Expand All @@ -25,13 +21,11 @@ public struct BasicServiceFactory: ServiceFactory {
_ type: Any.Type,
tag: String?,
supports interfaces: [Any.Type],
isSingleton: Bool,
factory closure: @escaping ServiceFactoryClosure
) {
self.serviceType = type
self.serviceTag = tag
self.serviceSupports = interfaces
self.serviceIsSingleton = isSingleton
self.closure = closure
}

Expand Down
28 changes: 23 additions & 5 deletions Sources/Service/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ public struct Config {
let specific = ServiceIdentifier(interface: interface, client: client)
let all = ServiceIdentifier(interface: interface, client: nil)
guard let preference = preferences[specific] ?? preferences[all] else {
throw ServiceError(.other(identifier: "invalid-interface", reason: "Please choose which \(interface) you prefer, multiple are available: \(available.readable)"))
throw ServiceError(
identifier: "ambiguity",
reason: "Please choose which \(interface) you prefer, multiple are available: \(available.readable).",
suggestedFixes: available.map { service in
return "`config.prefer(\(service.serviceType).self, for: \(interface).self)`."
}
)
}

let chosen = available.filter { factory in
Expand All @@ -57,9 +63,15 @@ public struct Config {

guard chosen.count == 1 else {
if chosen.count < 1 {
throw ServiceError(.other(identifier: "invalid-tag", reason: "No service \(preference.type) (\(preference.tag ?? "*")) has been registered for \(interface)."))
throw ServiceError(
identifier: "tag",
reason: "No service \(preference.type) (\(preference.tag ?? "*")) has been registered for \(interface)."
)
} else {
throw ServiceError(.other(identifier: "multiple-matching-tag", reason: "Too many services were found mathcing this tag"))
throw ServiceError(
identifier: "tags",
reason: "Too many services were found mathcing this tag."
)
}

}
Expand All @@ -80,12 +92,18 @@ public struct Config {
}

guard requirement.type == chosen.serviceType else {
throw ServiceError(.other(identifier: "type-not-required", reason: "\(interface) \(chosen.serviceType) is not required type \(requirement.type)."))
throw ServiceError(
identifier: "typeRequirement",
reason: "\(interface) \(chosen.serviceType) is not required type \(requirement.type)."
)
}

if let tag = requirement.tag {
guard chosen.serviceTag == tag else {
throw ServiceError(.other(identifier: "tag-not-matching", reason: "\(chosen.serviceType) tag \(chosen.serviceTag ?? "none") does not equal \(tag)"))
throw ServiceError(
identifier: "tagRequirement",
reason: "\(chosen.serviceType) tag \(chosen.serviceTag ?? "none") does not equal \(tag)"
)
}
}
}
Expand Down
38 changes: 19 additions & 19 deletions Sources/Service/Container+Service.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,14 @@ extension Container {
} else if available.count == 0 {
// no services are available matching
// the type requested.
throw ServiceError(.noneAvailable(type: interface))
throw ServiceError(
identifier: "make",
reason: "No services are available for '\(interface)'.",
suggestedFixes: [
"Register a service for '\(interface)'.",
"`services.register(\(interface).self) { ... }`."
]
)
} else {
// only one service matches, no need to disambiguate.
// let's use it!
Expand All @@ -69,25 +76,18 @@ extension Container {
neededBy: client
)

// lazy loading

if chosen.serviceIsSingleton {
// attempt to fetch singleton from cache
if let singleton = try serviceCache.getSingleton(chosen.serviceType) {
return singleton
} else {
do {
let item = try chosen.makeService(for: self)
serviceCache.setSingleton(.service(item), type: chosen.serviceType)
return item
} catch {
serviceCache.setSingleton(.error(error), type: chosen.serviceType)
throw error
}
}
// attempt to fetch singleton from cache
if let singleton = try serviceCache.getSingleton(chosen.serviceType) {
return singleton
} else {
// create an instance of this service type.
return try chosen.makeService(for: self)
do {
let item = try chosen.makeService(for: self)
serviceCache.setSingleton(.service(item), type: chosen.serviceType)
return item
} catch {
serviceCache.setSingleton(.error(error), type: chosen.serviceType)
throw error
}
}
}
}
Expand Down
1 change: 0 additions & 1 deletion Sources/Service/ServiceCacheable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public final class ServiceCache {
self.singletons = [:]
}


/// Gets the cached service if one exists.
/// - throws if the service was cached as an error
internal func get<Interface, Client>(
Expand Down
149 changes: 34 additions & 115 deletions Sources/Service/ServiceError.swift
Original file line number Diff line number Diff line change
@@ -1,120 +1,39 @@
import Foundation
import Debugging

public struct ServiceError: Error, Debuggable, Encodable {
var problem: Problem

init(_ problem: Problem) {
self.problem = problem
}

enum Problem {
case other(identifier: String, reason: String)
case multipleInstances(
type: Any.Type
)
case disambiguationRequired(
key: String,
available: [String],
type: Any.Type
)
case unknownService(
available: [String],
type: Any.Type
)
case incorrectType(
type: Any.Type,
desired: Any.Type
)
case noneAvailable(type: Any.Type)
case unknown(Error)
}
}

extension ServiceError {
public var reason: String {
switch self.problem {
case .other(_, let reason):
return reason
case .multipleInstances(let type):
return "Multiple instances available for '\(type)'. Unable to disambiguate."
case .noneAvailable(let type):
return "No services are available for '\(type)'"
case .disambiguationRequired(_, _, let type):
return "Multiple services available for '\(type)', please disambiguate using config."
case .unknownService(_, let type):
return "No service was found while making a `\(type)`."
case .incorrectType(let type, let desired):
return "Service factory for `\(type)` did not create a service that conforms to `\(desired)`."
case .unknown(let error):
return "Unknown: \(error)"
}
}
/// An error using Services.
public struct ServiceError: Debuggable {
public static let readableName = "Service Error"
public let identifier: String
public var reason: String
public var file: String
public var function: String
public var line: UInt
public var column: UInt
public var stackTrace: [String]
public var possibleCauses: [String]
public var suggestedFixes: [String]

public var identifier: String {
switch self.problem {
case .other(let identifier, _):
return identifier
case .multipleInstances:
return "multipleInstances"
case .noneAvailable:
return "none"
case .disambiguationRequired:
return "disambiguationRequired"
case .unknownService:
return "unknownService"
case .incorrectType:
return "incorrectType"
case .unknown:
return "unknown"
}
}

public var possibleCauses: [String] {
switch self.problem {
case .other(_):
return []
case .multipleInstances:
return []
case .noneAvailable:
return [
"A provider for this service was not properly configured."
]
case .disambiguationRequired:
return []
case .unknownService:
return []
case .incorrectType:
return []
case .unknown:
return []
}
}

public var suggestedFixes: [String] {
switch self.problem {
case .other(_):
return []
case .multipleInstances:
return [
"Register instances as service types instead, so they can be disambiguated using config."
]
case .noneAvailable(let type):
return [
"Register a service that conforms to '\(type)' to the Container."
]
case .disambiguationRequired(let key, let available, _):
return [
"Specify one of the available services in `app.json` at key `\(key)`.",
"Use `try config.set(\"app\", \"\(key)\", to: \"<service>\")` with one of the available service names.",
"Available services: \(available)"
]
case .unknownService(let available, _):
let string = available.joined(separator: ", ")
return ["Try using one of the available types: \(string)"]
case .incorrectType:
return []
case .unknown:
return []
}
/// Creates a new Apple TLS error
public init(
identifier: String,
reason: String,
possibleCauses: [String] = [],
suggestedFixes: [String] = [],
file: String = #file,
function: String = #function,
line: UInt = #line,
column: UInt = #column
) {
self.identifier = identifier
self.reason = reason
self.file = file
self.function = function
self.line = line
self.column = column
self.stackTrace = ServiceError.makeStackTrace()
self.possibleCauses = possibleCauses
self.suggestedFixes = suggestedFixes
}
}

3 changes: 0 additions & 3 deletions Sources/Service/ServiceFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ public protocol ServiceFactory {
/// it from identical service types.
var serviceTag: String? { get }

/// If true, the service will only ever be booted once.
var serviceIsSingleton: Bool { get }

/// Creates an instance of the service for the supplied
/// container and worker
func makeService(for worker: Container) throws -> Any
Expand Down
10 changes: 1 addition & 9 deletions Sources/Service/ServiceType.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import Async

public protocol ServiceType {
public protocol ServiceType: Service {
/// An array of protocols (or types) that this
/// service conforms to.
/// ex. when `container.make(X.self)`
/// is called, all services that support `X`
/// will be considered.
static var serviceSupports: [Any.Type] { get }

/// If true, the service will only be initialized once.
static var serviceIsSingleton: Bool { get }

/// Creates a new instance of the service
/// Using the service container.
static func makeService(for worker: Container) throws -> Self
Expand All @@ -19,11 +16,6 @@ public protocol ServiceType {
/// MARK: Optional

extension ServiceType {
/// See `ServiceType.serviceIsSingleton`
public static var serviceIsSingleton: Bool {
return false
}

/// See `ServiceType.serviceSupports`
public static var serviceSupports: [Any.Type] {
return []
Expand Down
Loading

0 comments on commit ed02e0f

Please sign in to comment.