Is there a way to make a function to accept any Enum types that have a rawValue of String? - swift

One way I came up with is to make a protocol that other Enum must conform to.
protocol StringRepresentable
{
var rawValue: String { get }
}
struct Endpoint
{
enum User: String, StringRepresentable
{
case Login = "/user/login"
case Register = "/user/register"
}
enum Item: String, StringRepresentable
{
case Like = "/item/like"
case Buy = "/item/buy"
}
}
func urlString(endpoint: StringRepresentable) -> String
{
return "http://www.example.com\(endpoint.rawValue)"
}
let userLoginEndpoint = urlString(Endpoint.User.Login)
let buyItemEndpoint = urlString(Endpoint.Item.Buy)
Is there any other way that better than this?
Or is there a protocol, already provided something like this, that I missed?

There is already the RawRepresentable protocol which does what you want.
And you can extend based on whether RawValue == String

Related

SOLVED - Swift Enum - Casting Nested Enums to String Enum to allow .rawValue

SOLVED
Thank you #New Dev and #Joakim Danielson for your help. I used #Joakim Danielson's answer to improve my code.
I have an extension method to assign accessibilityIdentifiers to views based on a given String Enum. I updated the method to directly accept String Enum Cases as a parameter, thus COMPLETELY eliminating the need for the AccessibilityId enum class as shown below, awesome!
Changes
Before:
.accessibility(identifier: .home(.clickButton))
// Simplified for StackOverflow.
// Imagine 20 more cases..
enum AccessibilityId {
case home(HomeId)
var rawValue: String {
switch self {
case .home(let id):
return id.rawValue
}
}
}
extension View {
func accessibility(identifier: AccessibilityId) -> ModifiedContent<Self, AccessibilityAttachmentModifier> {
self.accessibility(identifier: identifier.rawValue)
}
}
After:
.accessibility(identifier: HomeId.clickButton)
extension View {
func accessibility<T: RawRepresentable>(identifier: T) -> ModifiedContent<Self, AccessibilityAttachmentModifier> where T.RawValue == String {
self.accessibility(identifier: identifier.rawValue)
}
}
---------------------------------------------------------------
Original Question
What I have
enum Item {
case animal(AnimalId)
case vehicle(VehicleId)
case food(FoodId)
var rawValue: String {
switch self {
case .animal(let id):
return id.rawValue
case .vehicle(let id):
return id.rawValue
case .food(let id):
return id.rawValue
}
}
}
enum AnimalId: String {
case cat
case dog
}
// etc.
// Imagine more cases and more enums.
What I want
enum Item {
case animal(AnimalId)
case vehicle(VehicleId)
case food(FoodId)
var rawValue: String {
switch self {
case self as StringEnum:
return id.rawValue
default:
return ""
}
}
}
Usage
func test() {
foo(.animal(.cat))
foo(.vehicle(.plane))
foo(.food(.tacos))
}
func foo(_ item: Item) {
print(item.rawValue)
}
I am happy with the usage, but I'd like to reduce the amount of duplicate cases in the given switch statement. Notice how they all have return id.rawValue. The above is just an example, in reality I have around 30 cases.
My Question
Is there a way for me to catch all Nested String Enums in a single switch or let case to reduce the duplicate code I have to write without losing the intended usage?
Thank you for your efforts, I hope to find an improvement for my code!
Here is a solution that is not based on Item being an enum but instead a generic struct
struct Item<T: RawRepresentable> where T.RawValue == String {
let thing: T
var rawValue: String {
thing.rawValue
}
}
With this solution you don't need to change your other enums.
Example
let item1 = Item(thing: AnimalId.cat)
let item2 = Item(thing: VehicleId.car)
print(item1.rawValue, item2.rawValue)
outputs
cat car
You need something common between all these associated values, like a conformance to a shared protocol, e.g. protocol RawStringValue:
protocol RawStringValue {
var rawValue: String { get }
}
// String enums already conform without any extra implementation
extension AnimalId: RawStringValue {}
extension VehicleId: RawStringValue {}
extension FoodId: RawStringValue {}
Then you could create a switch self inside like so:
var rawValue: String {
switch self {
case .animal (let id as RawStringValue),
.vehicle (let id as RawStringValue),
.food (let id as RawStringValue):
return id.rawValue
}
}
That being said, enum with associated values isn't the most convenient type to work with, so be sure that it's the right choice.

How to define a common interface for a group of string enums to be used by another class interchangeably

I want to do this or something like it to get the same functionality
could someone help?
protocol FilterEnum: CaseIterable {
}
enum SomeFilterEnum: String, FilterEnum {
case owner = "Owner"
case karat = "Karat"
}
class SomeCls {
private let filterTypes: [FilterEnum]
init(filterTypes: [FilterEnum]){
self.filterTypes = filterTypes
}
func useFilterTypes() {
for type in filterTypes{
print(type.rawValue)
}
}
}
let sm = SomeCls(filterTypes: SomeFilterEnum.allCases)
Judging from your usage, you need this common interface to be both CaseIterable and RawRepresentable. Well, that is CaseIterable & RawRepresentable, but we can't use that as a type directly because they both have associated types. We have to introduce a generic parameter on SomeCls:
class SomeCls<T> where T : CaseIterable & RawRepresentable, T.RawValue == String {
private let filterTypes: [T]
init(filterTypes: [T]){
self.filterTypes = filterTypes
}
func useFilterTypes() {
for type in filterTypes{
print(type.rawValue)
}
}
}

Is there a way to simplify this 'matrix of overloads' based on argument types which are all ultimately representable by a specific type?

We're trying to create a function addQueryItem which ultimately uses a string and an optional string internally.
For more flexibility in the API, rather than use String for the argument types, we are instead using CustomStringConvertible (which String implements) so we can use anything that can be represented as a string.
Additionally, so we can pass it String-based enums, we also want it to accept RawRepresentable types where RawValue is a CustomStringConvertible itself.
However, since we're now technically accepting two different kinds of values for each parameter, we end up having to create a 'matrix of overloads'--four total--for each combination of the two types.
My first thought was to use protocol-oriented programming by extending RawRepresentable so it adheres to CustomStringConvertible if its RawValue was also a CustomStringConvertible. Then I could just pass that directly to the version which takes two CustomStringConvertible arguments and eliminate the other three. However, the compiler didn't like it because I'm trying to extend a protocol, not a concrete type.
// This doesn't work
extension RawRepresentable : CustomStringConvertible
where RawValue:CustomStringConvertible {
var description: String {
return self.rawValue
}
}
As a result of not being able to do the above, as mentioned, I have to have all four of the following:
func addQueryItem(name:CustomStringConvertible, value:CustomStringConvertible?){
if let valueAsString = value.flatMap({ String(describing:$0) }) {
queryItems.append(name: String(describing:name), value: valueAsString)
}
}
func addQueryItem<TName:RawRepresentable>(name:TName, value:CustomStringConvertible?)
where TName.RawValue:CustomStringConvertible {
addQueryItem(name: name.rawValue, value: value)
}
func addQueryItem<TValue:RawRepresentable>(name:CustomStringConvertible, value:TValue?)
where TValue.RawValue:CustomStringConvertible {
addQueryItem(name: name, value: value?.rawValue)
}
func addQueryItem<TName:RawRepresentable, TValue:RawRepresentable>(name:TName, value:TValue?)
where TName.RawValue:CustomStringConvertible,
TValue.RawValue:CustomStringConvertible
{
addQueryItem(name: name.rawValue, value: value?.rawValue)
}
So, since it doesn't look like it's possible to make RawRepresentable to adhere to CustomStringConvertible, is there any other way to solve this 'matrix-of-overloads' issue?
To expand on my comments, I believe you're fighting the Swift type system. In Swift you generally should not try to auto-convert types. Callers should explicitly conform their types when they want a feature. So to your example of an Order enum, I believe it should be implemented this way:
First, have a protocol for names and values:
protocol QueryName {
var queryName: String { get }
}
protocol QueryValue {
var queryValue: String { get }
}
Now for string-convertible enums, it's nice to not have to implement this yourself.
extension QueryName where Self: RawRepresentable, Self.RawValue == String {
var queryName: String { return self.rawValue }
}
extension QueryValue where Self: RawRepresentable, Self.RawValue == String {
var queryValue: String { return self.rawValue }
}
But, for type-safety, you need to explicitly conform to the protocol. This way you don't collide with things that didn't mean to be used this way.
enum Order: String, RawRepresentable, QueryName {
case buy
}
enum Item: String, RawRepresentable, QueryValue {
case widget
}
Now maybe QueryItems really has to take strings. OK.
class QueryItems {
func append(name: String, value: String) {}
}
But the thing that wraps this can be type-safe. That way Order.buy and Purchase.buy don't collide (because they can't both be passed):
class QueryBuilder<Name: QueryName, Value: QueryValue> {
var queryItems = QueryItems()
func addQueryItem(name: QueryName, value: QueryValue?) {
if let value = value {
queryItems.append(name: name.queryName, value: value.queryValue)
}
}
}
You can use the above to make it less type-safe (using things like StringCustomConvertible and making QueryBuilder non-generic, which I do not recommend, but you can do it). But I would still strongly recommend that you have callers explicitly tag the types they plan to use this way by explicitly labelling (and nothing else) that they conform to the protocol.
To show what the less-safe version would look like:
protocol QueryName {
var queryName: String { get }
}
protocol QueryValue {
var queryValue: String { get }
}
extension QueryName where Self: RawRepresentable, Self.RawValue == String {
var queryName: String { return self.rawValue }
}
extension QueryValue where Self: RawRepresentable, Self.RawValue == String {
var queryValue: String { return self.rawValue }
}
extension QueryName where Self: CustomStringConvertible {
var queryName: String { return self.description }
}
extension QueryValue where Self: CustomStringConvertible {
var queryValue: String { return self.description }
}
class QueryItems {
func append(name: String, value: String) {}
}
class QueryBuilder {
var queryItems = QueryItems()
func addQueryItem<Name: QueryName, Value: QueryValue>(name: Name, value: Value?) {
if let value = value {
queryItems.append(name: name.queryName, value: value.queryValue)
}
}
}
enum Order: String, RawRepresentable, QueryName {
case buy
}
enum Item: String, RawRepresentable, QueryValue {
case widget
}
No, you cannot conform a protocol to another protocol via an extension. The language does not support it.

Multiple enum implementing protocols questions

I defined enums as confirming to a protocol Eventable:
protocol Eventable {
var name: String { get }
static var all: [Eventable] { get }
}
enum MyEnum: String, Eventable {
case bla = "bla"
case blu = "blu"
var name: String {
return self.rawValue
}
static var all: [Eventable] {
return [
MyEnum.bla,
MyEnum.blu
]
}
}
I have other enums like MyEnum also under the form:
enum Bla: String, Eventable {
}
I have two questions:
for the enums having a String data type, I would like to avoid duplicating the generation of the variable name:
var name: String
I am not sure how to write that in Swift. I tried to play around with the "where" clause but not success. How can I achieve that?
when I write my enums and conform to the protocol for that part:
static var all: [Eventable] { get }.
I would like that for the enum MyEnum, it constrains the variable to:
static var all: [MyEnum] { ... }
because for now I can put in the returned array any element being an Eventable and it's not what I need.
Amongst other things, I tried to define a generic constraint in the protocol for it, but I get the following error:
Protocol 'Eventable' can only be used as a generic constraint because
it has Self or associated type requirements
Thank you very much for the help!
For your second question, you just need to use Self:
protocol Eventable {
var name: String { get }
static var all: [Self] { get }
}
Self, similar to self, just means "the current type".
The first question is a little bit harder because you can't really get all the values of an enum safely. See here for more info. The closest I got was:
extension Eventable where Self: RawRepresentable, Self.RawValue == String {
var name: String {
return self.rawValue
}
}
This means that you can omit the declaration of name in MyEnum, but not all.
For the part of the question we need to extend Eventable protocol where Self inherits RawRepresentable
protocol Eventable {
var name: String { get }
static var all: [Self] { get }
}
extension Eventable where Self: RawRepresentable {
var name: Self.RawValue {
return self.rawValue
}
}
enum MyEnum: String, Eventable {
case bla = "bla"
case blu = "blu"
static var all: [MyEnum] = [bla, blu]
}
For the second part of your question we need to configure the function to handle a generic type
I would suggest making the function generic as well here is an example
func printEnum<T: Eventable>(_ event: T) {
print(event.name)
}
and we can use it for any object confirms to Eventable
printEnum(MyEnum.bla)

How do I get a CustomStringConvertible description from this enum?

I have the following enum
enum Properties: CustomStringConvertible {
case binaryOperation(BinaryOperationProperties),
brackets(BracketsProperties),
chemicalElement(ChemicalElementProperties),
differential(DifferentialProperties),
function(FunctionProperties),
number(NumberProperties),
particle(ParticleProperties),
relation(RelationProperties),
stateSymbol(StateSymbolProperties),
symbol(SymbolProperties)
}
and the structs all look like this
struct BinaryOperationProperties: Decodable, CustomStringConvertible {
let operation: String
var description: String { return operation }
}
So how do I make that enum conform to CustomStringConvertible? I tried with a simple getter but obviously that calls itself and I'd like to call the specific struct's instead.
Bonus points: does an enum defined like that have a name?
Such an enum is called enum with associated values.
I'd implement description by manually switching over the cases:
extension Properties: CustomStringConvertible {
var description: String {
switch self {
case .binaryOperation(let props):
return "binaryOperation(\(props))"
case .brackets(let props):
return "brackets(\(props))"
...
}
}
}
Edit: an alternative is to use Swift's Mirror reflection API. The enum case of an instance is listed as the mirror's child and you can print its label and value like this:
extension Properties: CustomStringConvertible {
var description: String {
let mirror = Mirror(reflecting: self)
var result = ""
for child in mirror.children {
if let label = child.label {
result += "\(label): \(child.value)"
} else {
result += "\(child.value)"
}
}
return result
}
}
(This is a generic solution that should be usable for many types, not just enums. You'll probably have to add some line breaks for types that have more than a single child.)
Edit 2: Mirror is also what print and String(describing:) use for types that don't conform to Custom[Debug]StringConvertible. You can check out the source code here.