Skip to content

Commit

Permalink
Add advanced filter (#108)
Browse files Browse the repository at this point in the history
* Change search term to filters

* Provide helper for initializing PaginatedListParams

* Make the filter init and comparators public

* Update pagination documentation

* Add inline doc

* Add a test on custom field

* Add support for nil and not nil comparison

* Update error code on channel not found
  • Loading branch information
mederic-p authored Jan 8, 2019
1 parent 646b207 commit 5b94f54
Show file tree
Hide file tree
Showing 17 changed files with 352 additions and 181 deletions.
8 changes: 4 additions & 4 deletions OmiseGO.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
034F151C214F6E8E002BB513 /* RequestAdminFixtureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034F151B214F6E8E002BB513 /* RequestAdminFixtureTests.swift */; };
034F1520214F8708002BB513 /* WalletListForUserParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034F151F214F8708002BB513 /* WalletListForUserParams.swift */; };
034F1523214FA205002BB513 /* SortParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034F1522214FA205002BB513 /* SortParams.swift */; };
034F1525214FA21B002BB513 /* SearchParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034F1524214FA21B002BB513 /* SearchParams.swift */; };
034F1525214FA21B002BB513 /* FilterParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034F1524214FA21B002BB513 /* FilterParams.swift */; };
034F1527214FA228002BB513 /* PaginationParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034F1526214FA228002BB513 /* PaginationParams.swift */; };
034F1529214FA6FB002BB513 /* PaginatedListParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034F1528214FA6FB002BB513 /* PaginatedListParams.swift */; };
0379E7E921184BC900E65D4F /* SocketEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0379E7AB21184BC800E65D4F /* SocketEvent.swift */; };
Expand Down Expand Up @@ -317,7 +317,7 @@
034F151B214F6E8E002BB513 /* RequestAdminFixtureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestAdminFixtureTests.swift; sourceTree = "<group>"; };
034F151F214F8708002BB513 /* WalletListForUserParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletListForUserParams.swift; sourceTree = "<group>"; };
034F1522214FA205002BB513 /* SortParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortParams.swift; sourceTree = "<group>"; };
034F1524214FA21B002BB513 /* SearchParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchParams.swift; sourceTree = "<group>"; };
034F1524214FA21B002BB513 /* FilterParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterParams.swift; sourceTree = "<group>"; };
034F1526214FA228002BB513 /* PaginationParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationParams.swift; sourceTree = "<group>"; };
034F1528214FA6FB002BB513 /* PaginatedListParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedListParams.swift; sourceTree = "<group>"; };
0379E7AB21184BC800E65D4F /* SocketEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEvent.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -704,7 +704,7 @@
children = (
034F1528214FA6FB002BB513 /* PaginatedListParams.swift */,
034F1522214FA205002BB513 /* SortParams.swift */,
034F1524214FA21B002BB513 /* SearchParams.swift */,
034F1524214FA21B002BB513 /* FilterParams.swift */,
034F1526214FA228002BB513 /* PaginationParams.swift */,
);
path = CollectionParams;
Expand Down Expand Up @@ -1200,7 +1200,7 @@
0320EABF2119499B0006685C /* TransactionRequest+Client.swift in Sources */,
0320EAC72119865A0006685C /* ClientConfiguration.swift in Sources */,
0379E7F921184BC900E65D4F /* User.swift in Sources */,
034F1525214FA21B002BB513 /* SearchParams.swift in Sources */,
034F1525214FA21B002BB513 /* FilterParams.swift in Sources */,
0320EAC1211962060006685C /* HTTPClientAPI.swift in Sources */,
03E565B9211AFFCA00BC9124 /* AdminConfiguration.swift in Sources */,
0379E7EC21184BC900E65D4F /* SocketDelegate.swift in Sources */,
Expand Down
9 changes: 6 additions & 3 deletions Source/Admin/Models/Account+Admin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@
// Copyright © 2017-2018 Omise Go Pte. Ltd. All rights reserved.
//

extension Account: Searchable {
public enum SearchableFields: String, KeyEncodable {
extension Account: Filterable {
public enum FilterableFields: String, RawEnumerable {
case id
case name
case description
case createdAt = "created_at"
case updatedAt = "updated_at"
case metadata
}
}

extension Account: Sortable {
public enum SortableFields: String, KeyEncodable {
public enum SortableFields: String, RawEnumerable {
case id
case name
case description
Expand Down
11 changes: 8 additions & 3 deletions Source/Admin/Models/Wallet+Admin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@
// Copyright © 2017-2018 Omise Go Pte. Ltd. All rights reserved.
//

extension Wallet: Searchable {
public enum SearchableFields: String, KeyEncodable {
extension Wallet: Filterable {
public enum FilterableFields: String, RawEnumerable {
case address
case name
case identifier
case enabled
case createdAt = "created_at"
case updatedAt = "updated_at"
}
}

extension Wallet: Sortable {
public enum SortableFields: String, KeyEncodable {
public enum SortableFields: String, RawEnumerable {
case address
case name
case identifier
case createdAt = "created_at"
case updatedAt = "updated_at"
}
}

Expand Down
200 changes: 200 additions & 0 deletions Source/Core/Models/CollectionParams/FilterParams.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
//
// SearchParams.swift
// OmiseGO
//
// Created by Mederic Petit on 17/9/18.
// Copyright © 2017-2018 Omise Go Pte. Ltd. All rights reserved.
//

import BigInt

/// A type that represents a filterable ressource
public protocol Filterable {
associatedtype FilterableFields: RawEnumerable where FilterableFields.RawValue == String
}

/// A comparator for boolean value
///
/// - equal: The value must match exactly fhe field
/// - notEqual: The value must be different from the field
///
/// Note: equal with a true value is the same as notEqual with a false value
public enum BooleanFilterComparator: String, Encodable {
case equal = "eq"
case notEqual = "neq"
}

/// A comparator for string values
///
/// - equal: The value must match exactly fhe field
/// - notEqual: The value must be different from the field
/// - contains: The value must be a substring of the field
/// - startsWith: The field must start with the value
public enum StringFilterComparator: String, Encodable {
case equal = "eq"
case notEqual = "neq"
case contains
case startsWith = "starts_with"
}

/// A comparator for numeric values (BigInts)
///
/// - equal: The value must match exactly the field
/// - notEqual: The value must be different from the field
/// - lessThan: The value must be inferior to the field
/// - lessThanOrEqual: The value must be inferior or equal to the field
/// - greaterThan: The value must be superior to the field
/// - greaterThanOrEqual: The value must me superior or equal to the field
public enum NumericFilterComparator: String, Encodable {
case equal = "eq"
case notEqual = "neq"
case lessThan = "lt"
case lessThanOrEqual = "lte"
case greaterThan = "gt"
case greaterThanOrEqual = "gte"
}

/// A comparator for nil values
///
/// - nil: The value must be nil
/// - notNil: The value must not be nil
public enum NilComparator: String, Encodable {
case `nil` = "eq"
case notNil = "neq"
}

/// Represents a filter that can be used in filtrable queries
public struct Filter<F: Filterable>: APIParameters {
var field: String
var comparator: String
var value: Any?

enum CodingKeys: String, CodingKey {
case field
case comparator
case value
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(field, forKey: .field)
try container.encode(comparator, forKey: .comparator)
switch self.value {
case .none: try container.encodeNil(forKey: .value)
case let value as String: try container.encode(value, forKey: .value)
case let value as Bool: try container.encode(value, forKey: .value)
case let value as BigInt: try container.encode(value, forKey: .value)
default:
throw OMGError.unexpected(message: "Unexepected value type for filter")
}
}
}

extension Filterable {
/// Initialize a new Filter object with a String value
///
/// - Parameters:
/// - field: The field to filter
/// - comparator: The String comparator to use
/// - value: The value to use to filter
/// - Returns: A Filter object initialized with the specified properties
public static func filter(field: FilterableFields, comparator: StringFilterComparator, value: String) -> Filter<Self> {
return Filter(field: field.rawValue, comparator: comparator.rawValue, value: value)
}

/// Initialize a new Filter object with a Bool value
///
/// - Parameters:
/// - field: The field to filter
/// - comparator: The Boolean comparator to use
/// - value: The value to use to filter
/// - Returns: A Filter object initialized with the specified properties
public static func filter(field: FilterableFields, comparator: BooleanFilterComparator, value: Bool) -> Filter<Self> {
return Filter(field: field.rawValue, comparator: comparator.rawValue, value: value)
}

/// Initialize a new Filter object with a BigInt value
///
/// - Parameters:
/// - field: The field to filter
/// - comparator: The Numeric comparator to use
/// - value: The value to use to filter
/// - Returns: A Filter object initialized with the specified properties
public static func filter(field: FilterableFields, comparator: NumericFilterComparator, value: BigInt) -> Filter<Self> {
return Filter(field: field.rawValue, comparator: comparator.rawValue, value: value)
}

/// Initialize a new Filter object for a nil comparison
///
/// - Parameters:
/// - field: The field to filter
/// - comparator: The Nil comparator to use
/// - Returns: A Filter object initialized with the specified properties
public static func filter(field: FilterableFields, comparator: NilComparator) -> Filter<Self> {
return Filter(field: field.rawValue, comparator: comparator.rawValue, value: nil)
}

/// Initialize an advanced new Filter object with a String value
///
/// - Parameters:
/// - field: The field to filter. Can contain nested relations snake_cased.
/// - comparator: The String comparator to use
/// - value: The value to use to filter
/// - Returns: A Filter object initialized with the specified properties
public static func filter(field: String, comparator: StringFilterComparator, value: String?) -> Filter<Self> {
return Filter(field: field, comparator: comparator.rawValue, value: value)
}

/// Initialize an advanced new Filter object with a Bool value
///
/// - Parameters:
/// - field: The field to filter. Can contain nested relations snake_cased.
/// - comparator: The Boolean comparator to use
/// - value: The value to use to filter
/// - Returns: A Filter object initialized with the specified properties
public static func filter(field: String, comparator: BooleanFilterComparator, value: Bool) -> Filter<Self> {
return Filter(field: field, comparator: comparator.rawValue, value: value)
}

/// Initialize an advanced new Filter object with a BigInt value
///
/// - Parameters:
/// - field: The field to filter. Can contain nested relations snake_cased.
/// - comparator: The Numeric comparator to use
/// - value: The value to use to filter
/// - Returns: A Filter object initialized with the specified properties
public static func filter(field: String, comparator: NumericFilterComparator, value: BigInt?) -> Filter<Self> {
return Filter(field: field, comparator: comparator.rawValue, value: value)
}

/// Initialize a new Filter object for a nil comparison
///
/// - Parameters:
/// - field: The field to filter. Can contain nested relations snake_cased.
/// - comparator: The Nil comparator to use
/// - Returns: A Filter object initialized with the specified properties
public static func filter(field: String, comparator: NilComparator) -> Filter<Self> {
return Filter(field: field, comparator: comparator.rawValue, value: nil)
}
}

public struct FilterParams<F: Filterable>: APIParameters {
let matchAll: [Filter<F>]?
let matchAny: [Filter<F>]?

public init(matchAll: [Filter<F>]? = nil, matchAny: [Filter<F>]? = nil) {
self.matchAll = matchAll
self.matchAny = matchAny
}

enum CodingKeys: String, CodingKey {
case matchAll = "match_all"
case matchAny = "match_any"
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(matchAll, forKey: .matchAll)
try container.encodeIfPresent(matchAny, forKey: .matchAny)
}
}
60 changes: 21 additions & 39 deletions Source/Core/Models/CollectionParams/PaginatedListParams.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,68 +8,50 @@

import UIKit

public protocol RawEnumerable: Hashable, RawRepresentable, Encodable {}

public struct PaginatedListParams<T: PaginatedListable> {
let sortParams: SortParams<T>
let searchParams: SearchParams<T>?
let filterParams: FilterParams<T>?
let paginationParams: PaginationParams

/// Initialize the params used to query a paginated collection
///
/// - Parameters:
/// - page: The page requested (0 and 1 are the same)
/// - perPage: The number of result expected per page
/// - filters: A FilterParams struc used to filter the results
/// - sortBy: The field to sort by
/// - sortDirection: The sort direction (ascending or descending)
public init(page: Int,
perPage: Int,
filters: FilterParams<T>? = nil,
sortBy: T.SortableFields,
sortDirection: SortDirection) {
self.sortParams = SortParams<T>(sortBy: sortBy, sortDirection: sortDirection)
self.paginationParams = PaginationParams(page: page, perPage: perPage)
self.searchParams = nil
}

/// Initialize the params used to query a paginated collection
///
/// - Parameters:
/// - page: The page requested (0 and 1 are the same)
/// - perPage: The number of result expected per page
/// - searchTerm: The global search term used to search in any of the SearchableFields
/// - sortBy: The field to sort by
/// - sortDirection: The sort direction (ascending or descending)
public init(page: Int,
perPage: Int,
searchTerm: String,
sortBy: T.SortableFields,
sortDirection: SortDirection) {
self.sortParams = SortParams<T>(sortBy: sortBy, sortDirection: sortDirection)
self.searchParams = SearchParams<T>(searchTerm: searchTerm)
self.paginationParams = PaginationParams(page: page, perPage: perPage)
}

/// Initialize the params used to query a paginated collection
///
/// - Parameters:
/// - page: The page requested (0 and 1 are the same)
/// - perPage: The number of result expected per page
/// - searchTerms: A dictionary where each key is a Searchable field that and each value is a search term
/// - sortBy: The field to sort by
/// - sortDirection: The sort direction (ascending or descending)
public init(page: Int,
perPage: Int,
searchTerms: [T.SearchableFields: Any],
sortBy: T.SortableFields,
sortDirection: SortDirection) {
self.filterParams = filters
self.sortParams = SortParams<T>(sortBy: sortBy, sortDirection: sortDirection)
self.searchParams = SearchParams<T>(searchTerms: searchTerms)
self.paginationParams = PaginationParams(page: page, perPage: perPage)
}
}

extension PaginatedListParams: APIParameters {
public func encode(to encoder: Encoder) throws {
try self.sortParams.encode(to: encoder)
try self.searchParams?.encode(to: encoder)
try self.filterParams?.encode(to: encoder)
try self.paginationParams.encode(to: encoder)
}
}

extension PaginatedListable {
public static func paginatedListParams(page: Int,
perPage: Int,
filters: FilterParams<Self>? = nil,
sortBy: SortableFields,
sortDirection: SortDirection) -> PaginatedListParams<Self> {
return PaginatedListParams(page: page,
perPage: perPage,
filters: filters,
sortBy: sortBy,
sortDirection: sortDirection)
}
}
2 changes: 0 additions & 2 deletions Source/Core/Models/CollectionParams/PaginationParams.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
// Copyright © 2017-2018 Omise Go Pte. Ltd. All rights reserved.
//

public protocol KeyEncodable: Hashable, RawRepresentable, Encodable {}

/// Represents a structure used to query a paginated list
struct PaginationParams {
/// The page requested (0 and 1 are the same)
Expand Down
Loading

0 comments on commit 5b94f54

Please sign in to comment.