Skip to content

Commit

Permalink
added custom error for open rules (#10)
Browse files Browse the repository at this point in the history
added localized error
added different format for dates
fixed value for scheme
added max version for rule selection

Co-authored-by: Alexandr Chernyy <pingus.nikalex@gmail.com>
  • Loading branch information
alexchornyi and pingus-nikalex authored Jun 29, 2021
1 parent af68fec commit a4b10d4
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 46 deletions.
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import PackageDescription

let package = Package(
name: "CertLogic",
defaultLocalization: "en",
platforms: [
.macOS(.v10_13), .iOS(.v11), .tvOS(.v9), .watchOS(.v2)
],
Expand All @@ -24,7 +25,8 @@ let package = Package(
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "CertLogic",
dependencies: ["jsonlogic", "SwiftyJSON"]),
dependencies: ["jsonlogic", "SwiftyJSON"],
resources: [.process("Resources")]),
.testTarget(
name: "CertLogicTests",
dependencies: ["CertLogic", "jsonlogic", "SwiftyJSON"]),
Expand Down
8 changes: 8 additions & 0 deletions Resources/en.lproj/Localizible.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// File.swift
//
//
// Created by Alexandr Chernyy on 29.06.2021.
//
"unknown_error" = "An unexpected error occurred";
"recheck_rule" = "Check Re-open EU for further details and travel-related rules";
111 changes: 91 additions & 20 deletions Sources/CertLogic/CertLogic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ final public class CertLogicEngine {
}
rulesItems.forEach { rule in
if !checkSchemeVersion(for: rule, qrCodeSchemeVersion: qrCodeSchemeVersion) {
result.append(ValidationResult(rule: rule, result: .open, validationErrors: nil))
result.append(ValidationResult(rule: rule, result: .open, validationErrors: [CertLogicError.openState]))
} else {
do {
let jsonlogic = try JsonLogic(rule.logic.description)
Expand Down Expand Up @@ -90,13 +90,13 @@ final public class CertLogicEngine {
return true
}

// MARK: calculate scheme version in Int "1.0.0" -> 100, "1.2.0" -> 120, 2.0.0 -> 200
// MARK: calculate scheme version in Int "1.0.0" -> 10000, "1.2.0" -> 10200, 2.0.1 -> 20001
private func getVersion(from schemeString: String) -> Int {
let codeVersionItems = schemeString.components(separatedBy: ".")
var version: Int = 0
let maxIndex = codeVersionItems.count - 1
for index in 0...maxIndex {
let division = Int(pow(Double(10), Double(Constants.maxVersion - index)))
let division = Int(pow(Double(100), Double(Constants.maxVersion - index)))
let calcVersion: Int = Int(codeVersionItems[index]) ?? 1
let forSum: Int = calcVersion * division
version = version + forSum
Expand All @@ -116,9 +116,53 @@ final public class CertLogicEngine {

// Get List of Rules for Country by Code
private func getListOfRulesFor(external: ExternalParameter, issuerCountryCode: String) -> [Rule] {
return rules.filter { rule in
return rule.countryCode.lowercased() == external.countryCode.lowercased() && rule.ruleType == .acceptence || rule.countryCode.lowercased() == issuerCountryCode.lowercased() && rule.ruleType == .invalidation && rule.certificateFullType == external.certificationType || rule.certificateFullType == .general && external.validationClock >= rule.validFromDate && external.validationClock <= rule.validToDate
var returnedRulesItems: [Rule] = []
let generalRulesWithAcceptence = rules.filter { rule in
return rule.countryCode.lowercased() == external.countryCode.lowercased() && rule.ruleType == .acceptence && rule.certificateFullType == .general && external.validationClock >= rule.validFromDate && external.validationClock <= rule.validToDate
}
let generalRulesWithInvalidation = rules.filter { rule in
return rule.countryCode.lowercased() == issuerCountryCode.lowercased() && rule.ruleType == .invalidation && rule.certificateFullType == .general && external.validationClock >= rule.validFromDate && external.validationClock <= rule.validToDate
}
//General Rule with Acceptence type and max Version number
if generalRulesWithAcceptence.count > 0 {
if let maxRules = generalRulesWithAcceptence.max(by: { (ruleOne, ruleTwo) -> Bool in
return ruleOne.versionInt < ruleTwo.versionInt
}) {
returnedRulesItems.append( maxRules)
}
}
//General Rule with Invalidation type and max Version number
if generalRulesWithInvalidation.count > 0 {
if let maxRules = generalRulesWithInvalidation.max(by: { (ruleOne, ruleTwo) -> Bool in
return ruleOne.versionInt < ruleTwo.versionInt
}) {
returnedRulesItems.append( maxRules)
}
}
let certTypeRulesWithAcceptence = rules.filter { rule in
return rule.countryCode.lowercased() == external.countryCode.lowercased() && rule.ruleType == .acceptence && rule.certificateFullType == external.certificationType && external.validationClock >= rule.validFromDate && external.validationClock <= rule.validToDate
}
let certTypeRulesWithInvalidation = rules.filter { rule in
return rule.countryCode.lowercased() == issuerCountryCode.lowercased() && rule.ruleType == .invalidation && rule.certificateFullType == external.certificationType && external.validationClock >= rule.validFromDate && external.validationClock <= rule.validToDate
}

//Rule with CertificationType with Acceptence type and max Version number
if certTypeRulesWithAcceptence.count > 0 {
if let maxRules = certTypeRulesWithAcceptence.max(by: { (ruleOne, ruleTwo) -> Bool in
return ruleOne.versionInt < ruleTwo.versionInt
}) {
returnedRulesItems.append( maxRules)
}
}
//Rule with CertificationType with Invalidation type and max Version number
if certTypeRulesWithInvalidation.count > 0 {
if let maxRules = certTypeRulesWithInvalidation.max(by: { (ruleOne, ruleTwo) -> Bool in
return ruleOne.versionInt < ruleTwo.versionInt
}) {
returnedRulesItems.append( maxRules)
}
}
return returnedRulesItems
}

static public func getItems<T:Decodable>(from jsonString: String) -> [T] {
Expand All @@ -143,34 +187,61 @@ final public class CertLogicEngine {
public func getDetailsOfError(rule: Rule, external: ExternalParameter) -> String {
var value: String = ""
rule.affectedString.forEach { key in
var section = "test_entry"
if external.certificationType == .recovery {
section = "recovery_entry"
}
if external.certificationType == .vacctination {
section = "vaccination_entry"
var keyToGetValue: String? = nil
let arrayKeys = key.components(separatedBy: ".")
// For affected fields like "ma"
if arrayKeys.count == 0 {
keyToGetValue = key
}
if external.certificationType == .test {
section = "test_entry"
// For affected fields like r.0.fr
if arrayKeys.count == 3 {
keyToGetValue = arrayKeys.last
}
if let newValue = schema?["$defs"][section]["properties"][key]["description"].string {
if value.count == 0 {
value = value + "\(newValue)"
} else {
value = value + " / " + "\(newValue)"
// All other keys will skiped (example: "r.0")
if let keyToGetValue = keyToGetValue {
if let newValue = self.getValueFromSchemeBy(external: external, key: keyToGetValue) {
if value.count == 0 {
value = value + "\(newValue)"
} else {
value = value + " / " + "\(newValue)"
}
}
}
}
return value
}

private func getValueFromSchemeBy(external: ExternalParameter, key: String) -> String? {
var section = Constants.testEntry
if external.certificationType == .recovery {
section = Constants.recoveryEntry
}
if external.certificationType == .vacctination {
section = Constants.vaccinationEntry
}
if external.certificationType == .test {
section = Constants.testEntry
}
if let newValue = schema?[Constants.schemeDefsSection][section][Constants.properties][key][Constants.description].string {
return newValue
}
return nil
}

}

extension CertLogicEngine {
enum Constants {
private enum Constants {
static let payload = "payload"
static let external = "external"
static let defSchemeVersion = "1.0.0"
static let maxVersion: Int = 2
static let majorVersionForSkip: Int = 100
static let majorVersionForSkip: Int = 10000
static let testEntry = "test_entry"
static let vaccinationEntry = "vaccination_entry"
static let recoveryEntry = "recovery_entry"
static let schemeDefsSection = "$defs"
static let properties = "properties"
static let description = "description"
}
}
47 changes: 47 additions & 0 deletions Sources/CertLogic/CertLogicError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// File.swift
//
//
// Created by Alexandr Chernyy on 29.06.2021.
//

import Foundation

enum CertLogicError: Error {
// Throw when an schemeversion not valid
case openState
// Throw in all other cases
case unexpected(code: Int)
}

extension CertLogicError: CustomStringConvertible {
public var description: String {
switch self {
case .openState:
return NSLocalizedString(
"recheck_rule",
comment: "Invalid Password"
)
case .unexpected(_):
return NSLocalizedString(
"unknown_error",
comment: "Unexpected Error")
}
}
}

extension CertLogicError: LocalizedError {
public var errorDescription: String? {
switch self {
case .openState:
return NSLocalizedString(
"recheck_rule",
comment: "Invalid Password"
)
case .unexpected(_):
return NSLocalizedString(
"unknown_error",
comment: "Unexpected Error")
}
}
}
18 changes: 18 additions & 0 deletions Sources/CertLogic/Date+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ extension Date {
return formatter
}()

static var isoFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
formatter.calendar = Calendar(identifier: .iso8601)
formatter.timeZone = TimeZone(abbreviation: "UTC")
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()

static var isoFormatterNotFull: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
formatter.calendar = Calendar(identifier: .iso8601)
formatter.timeZone = TimeZone(abbreviation: "UTC")
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()

var ISO8601String: String { return Date.iso8601Full.string(from: self) }

}
38 changes: 26 additions & 12 deletions Sources/CertLogic/Rule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ public enum CertificateType: String {

fileprivate enum Constants {
static let defLanguage = "EN"
static let unknownError = "Unknown error"
static let unknownError = NSLocalizedString("unknown_error", comment: "Packege unknown error")
static let maxVersion = 2
static let zero = 0
}

public class Rule: Codable {
Expand Down Expand Up @@ -54,20 +56,32 @@ public class Rule: Codable {
}

public var validFromDate: Date {
get { return Date.backendFormatter.date(from: validFrom) ?? Date() }
get {
if let date = Date.backendFormatter.date(from: validFrom) { return date }
if let date = Date.isoFormatter.date(from: validFrom) { return date }
if let date = Date.iso8601Full.date(from: validFrom) { return date }
if let date = Date.isoFormatterNotFull.date(from: validFrom) { return date }
return Date()
}
}

public var validToDate: Date {
get { return Date.backendFormatter.date(from: validTo) ?? Date() }
get {
if let date = Date.backendFormatter.date(from: validTo) { return date }
if let date = Date.isoFormatter.date(from: validTo) { return date }
if let date = Date.iso8601Full.date(from: validTo) { return date }
if let date = Date.isoFormatterNotFull.date(from: validTo) { return date }
return Date()
}
}

public var versionInt: Int {
get {
let codeVersionItems = version.components(separatedBy: ".")
var version: Int = 0
var version: Int = Constants.zero
let maxIndex = codeVersionItems.count - 1
for index in 0...maxIndex {
let division = Int(pow(Double(10), Double(2 - index)))
for index in Constants.zero...maxIndex {
let division = Int(pow(Double(100), Double(Constants.maxVersion - index)))
let calcVersion: Int = Int(codeVersionItems[index]) ?? 1
let forSum: Int = calcVersion * division
version = version + forSum
Expand All @@ -80,21 +94,21 @@ public class Rule: Codable {
let filtered = self.description.filter { description in
description.lang.lowercased() == locale.lowercased()
}
if(filtered.count == 0) {
if(filtered.count == Constants.zero) {
let defFiltered = self.description.filter { description in
description.lang.lowercased() == Constants.defLanguage.lowercased()
}
if defFiltered.count == 0 {
if self.description.count == 0 {
if defFiltered.count == Constants.zero {
if self.description.count == Constants.zero {
return Constants.unknownError
} else {
return self.description[0].desc
return self.description[Constants.zero].desc
}
} else {
return defFiltered[0].desc
return defFiltered[Constants.zero].desc
}
} else {
return filtered[0].desc
return filtered[Constants.zero].desc
}
}

Expand Down
Loading

0 comments on commit a4b10d4

Please sign in to comment.