Last active
March 14, 2019 21:39
-
-
Save igor9silva/98de8b4270afe6839dddfc961743bebc to your computer and use it in GitHub Desktop.
Swift Recursive Enum
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
public indirect enum Filter: ODataRepresentable { | |
public enum Operator: String { | |
case equalTo = "eq" | |
case notEqualTo = "ne" | |
case greaterThan = "gt" | |
case greaterThanOrEqualTo = "ge" | |
case lessThan = "lt" | |
case lessThanOrEqualTo = "le" | |
} | |
case filter(String, Operator, ODataRepresentable) | |
case and([Filter]) | |
case or([Filter]) | |
case any(String, Filter) | |
case all(String, Filter) | |
public var asQuery: String { | |
switch self { | |
case .filter(let key, let op, let value): | |
return "\(key) \(op.rawValue) \(value.asQuery)" | |
case .and(let filters): | |
return "(\(filters.map { $0.asQuery }.joined(separator: " and ")))" | |
case .or(let filters): | |
return "(\(filters.map { $0.asQuery }.joined(separator: " or ")))" | |
case .any(let property, let filter): | |
return "\(property)/any(d:d/\(filter.asQuery.trimmedParentheses))" | |
case .all(let property, let filter): | |
return "\(property)/all(d:d/\(filter.asQuery.trimmedParentheses))" | |
} | |
} | |
/// Recursively OR' inputed string array into a new Filter. | |
/// Values must have at least 1 element. Otherwise, it returns nil. | |
/// | |
/// - Parameters: | |
/// - key: the key to compare to | |
/// - values: and array of values | |
/// - Returns: a new filter, OR'ding every entry in 'values'. e.g.: | |
/// | |
/// for(key: "ID", equalToAnyOf: ["1", "2", "3"] | |
/// | |
/// is equivalent to: | |
/// | |
/// .or([ | |
/// .filter("ID", .equalTo, "1"), | |
/// .filter("ID", .equalTo, "2"), | |
/// .filter("ID", .equalTo, "3"), | |
/// ]) | |
public static func `for`(key: String, equalToAnyOf values: [ODataRepresentable]) -> Filter? { | |
guard values.count > 0 else { | |
return nil | |
} | |
return .or(values.map { Filter.filter(key, .equalTo, $0) }) | |
} | |
} | |
// MARK: ODataRepresentable | |
public protocol ODataRepresentable { | |
var asQuery: String { get } | |
} | |
extension String: ODataRepresentable { | |
public var asQuery: String { | |
return "'\(self)'" | |
} | |
} | |
extension Date: ODataRepresentable { | |
public var asQuery: String { | |
return "datetime'\(self.asOData)'" | |
} | |
} | |
extension Double: ODataRepresentable { | |
public var asQuery: String { | |
return "\(self)" | |
} | |
} | |
extension Int: ODataRepresentable { | |
public var asQuery: String { | |
return "\(self)" | |
} | |
} | |
// MARK: Extensions | |
extension String { | |
// "(something)" -> "something" | |
var trimmedParentheses: String { | |
if let first = self.first, let last = self.last, first == "(", last == ")" { | |
return String(self.dropFirst().dropLast()) | |
} | |
return self | |
} | |
} | |
extension Date { | |
// 1997-01-22T03:36:00 | |
var asOData: String { | |
let formatter = DateFormatter() | |
formatter.calendar = Calendar(identifier: .iso8601) | |
formatter.locale = Locale(identifier: "en_US_POSIX") | |
formatter.timeZone = TimeZone(secondsFromGMT: 0) | |
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" | |
return formatter.string(from: self) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let filter1: Filter = .or([ | |
.and([ | |
.filter("Gender", .equalTo, "M"), | |
.filter("Age", .greaterThanOrEqualTo, 18), | |
.filter("City", .equalTo, "Santos"), | |
]), | |
.and([ | |
.filter("Gender", .equalTo, "F"), | |
.filter("Age", .greaterThanOrEqualTo, 15), | |
]), | |
]) | |
let filter2 = Filter.for(key: "ID", equalToAnyOf: [ | |
"0000001", | |
"0000002", | |
"0000003", | |
])! | |
let filter3: Filter = .all("Item", .filter("Price", .greaterThan, 100)) | |
let filter4: Filter = .and([ | |
.filter("Total", .greaterThan, 1000), | |
.all("Item", .filter("Value", .greaterThan, 99)), | |
]) | |
print(filter1.asQuery) // ((Gender eq 'M' and Age ge 18 and City eq 'Santos') or (Gender eq 'F' and Age ge 15)) | |
print(filter2.asQuery) // (ID eq '0000001' or ID eq '0000002' or ID eq '0000003') | |
print(filter3.asQuery) // Item/any(d:d/Price gt 100) | |
print(filter4.asQuery) // (Total gt 1000 and Item/all(d:d/Value gt 99)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Indented using spaces cause Gist was using 8-spaces-length for tabs. Don't ask me why.