Skip to content

Instantly share code, notes, and snippets.

@igor9silva
Last active March 14, 2019 21:39
Show Gist options
  • Save igor9silva/98de8b4270afe6839dddfc961743bebc to your computer and use it in GitHub Desktop.
Save igor9silva/98de8b4270afe6839dddfc961743bebc to your computer and use it in GitHub Desktop.
Swift Recursive Enum
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)
}
}
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))
@igor9silva
Copy link
Author

Indented using spaces cause Gist was using 8-spaces-length for tabs. Don't ask me why.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment