Adding API mocking code. API error handling refactoring
This commit is contained in:
parent
d83fc71810
commit
0d261b3452
@ -36,10 +36,23 @@
|
|||||||
7A40D60926998DCF009B0BC4 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A40D60726998DCF009B0BC4 /* Alert.swift */; };
|
7A40D60926998DCF009B0BC4 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A40D60726998DCF009B0BC4 /* Alert.swift */; };
|
||||||
7A40D60C2699A070009B0BC4 /* MockURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A40D60B2699A070009B0BC4 /* MockURLProtocol.swift */; };
|
7A40D60C2699A070009B0BC4 /* MockURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A40D60B2699A070009B0BC4 /* MockURLProtocol.swift */; };
|
||||||
7A503C03269F382F002C1A0D /* login_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A503C02269F382F002C1A0D /* login_success.json */; };
|
7A503C03269F382F002C1A0D /* login_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A503C02269F382F002C1A0D /* login_success.json */; };
|
||||||
7A503C05269F494C002C1A0D /* login_invalid_params.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A503C04269F494C002C1A0D /* login_invalid_params.json */; };
|
|
||||||
7A503C07269F49F4002C1A0D /* login_wrong_credentials.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A503C06269F49F4002C1A0D /* login_wrong_credentials.json */; };
|
|
||||||
7A683999269612EA00B2188A /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A683998269612EA00B2188A /* Response.swift */; };
|
7A683999269612EA00B2188A /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A683998269612EA00B2188A /* Response.swift */; };
|
||||||
7A68399A269612EA00B2188A /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A683998269612EA00B2188A /* Response.swift */; };
|
7A68399A269612EA00B2188A /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A683998269612EA00B2188A /* Response.swift */; };
|
||||||
|
7A971F0626AD6F2F007E527B /* ApiMethodMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F0526AD6F2F007E527B /* ApiMethodMock.swift */; };
|
||||||
|
7A971F0826AD7084007E527B /* LoginMethodMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F0726AD7084007E527B /* LoginMethodMock.swift */; };
|
||||||
|
7A971F0A26AD74FD007E527B /* ApiMethodMockProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F0926AD74FD007E527B /* ApiMethodMockProtocol.swift */; };
|
||||||
|
7A971F0D26AD7D4C007E527B /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F0C26AD7D4C007E527B /* AnyEncodable.swift */; };
|
||||||
|
7A971F0E26AD7D4C007E527B /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F0C26AD7D4C007E527B /* AnyEncodable.swift */; };
|
||||||
|
7A971F1526AD8AEB007E527B /* Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1026AD8AEB007E527B /* Initialization.swift */; };
|
||||||
|
7A971F1626AD8AEB007E527B /* Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1026AD8AEB007E527B /* Initialization.swift */; };
|
||||||
|
7A971F1726AD8AEB007E527B /* Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1126AD8AEB007E527B /* Querying.swift */; };
|
||||||
|
7A971F1826AD8AEB007E527B /* Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1126AD8AEB007E527B /* Querying.swift */; };
|
||||||
|
7A971F1926AD8AEB007E527B /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1226AD8AEB007E527B /* JSON.swift */; };
|
||||||
|
7A971F1A26AD8AEB007E527B /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1226AD8AEB007E527B /* JSON.swift */; };
|
||||||
|
7A971F1D26AD8AEB007E527B /* Merging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1426AD8AEB007E527B /* Merging.swift */; };
|
||||||
|
7A971F1E26AD8AEB007E527B /* Merging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1426AD8AEB007E527B /* Merging.swift */; };
|
||||||
|
7A971F2026ADC351007E527B /* ApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1F26ADC351007E527B /* ApiError.swift */; };
|
||||||
|
7A971F2126ADC351007E527B /* ApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1F26ADC351007E527B /* ApiError.swift */; };
|
||||||
7ACD05D72695C08A00557667 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD05D62695C08A00557667 /* Constants.swift */; };
|
7ACD05D72695C08A00557667 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD05D62695C08A00557667 /* Constants.swift */; };
|
||||||
7ACD05D82695C08A00557667 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD05D62695C08A00557667 /* Constants.swift */; };
|
7ACD05D82695C08A00557667 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD05D62695C08A00557667 /* Constants.swift */; };
|
||||||
7AEFAEED26985A3400ED2C85 /* ACProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEFAEEC26985A3400ED2C85 /* ACProgressView.swift */; };
|
7AEFAEED26985A3400ED2C85 /* ACProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEFAEEC26985A3400ED2C85 /* ACProgressView.swift */; };
|
||||||
@ -117,9 +130,16 @@
|
|||||||
7A40D60726998DCF009B0BC4 /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
|
7A40D60726998DCF009B0BC4 /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
|
||||||
7A40D60B2699A070009B0BC4 /* MockURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLProtocol.swift; sourceTree = "<group>"; };
|
7A40D60B2699A070009B0BC4 /* MockURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLProtocol.swift; sourceTree = "<group>"; };
|
||||||
7A503C02269F382F002C1A0D /* login_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = login_success.json; sourceTree = "<group>"; };
|
7A503C02269F382F002C1A0D /* login_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = login_success.json; sourceTree = "<group>"; };
|
||||||
7A503C04269F494C002C1A0D /* login_invalid_params.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = login_invalid_params.json; sourceTree = "<group>"; };
|
|
||||||
7A503C06269F49F4002C1A0D /* login_wrong_credentials.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = login_wrong_credentials.json; sourceTree = "<group>"; };
|
|
||||||
7A683998269612EA00B2188A /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = "<group>"; };
|
7A683998269612EA00B2188A /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = "<group>"; };
|
||||||
|
7A971F0526AD6F2F007E527B /* ApiMethodMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiMethodMock.swift; sourceTree = "<group>"; };
|
||||||
|
7A971F0726AD7084007E527B /* LoginMethodMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMethodMock.swift; sourceTree = "<group>"; };
|
||||||
|
7A971F0926AD74FD007E527B /* ApiMethodMockProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiMethodMockProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
7A971F0C26AD7D4C007E527B /* AnyEncodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; };
|
||||||
|
7A971F1026AD8AEB007E527B /* Initialization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Initialization.swift; sourceTree = "<group>"; };
|
||||||
|
7A971F1126AD8AEB007E527B /* Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Querying.swift; sourceTree = "<group>"; };
|
||||||
|
7A971F1226AD8AEB007E527B /* JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
|
||||||
|
7A971F1426AD8AEB007E527B /* Merging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Merging.swift; sourceTree = "<group>"; };
|
||||||
|
7A971F1F26ADC351007E527B /* ApiError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiError.swift; sourceTree = "<group>"; };
|
||||||
7ACD05D62695C08A00557667 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
7ACD05D62695C08A00557667 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||||
7AEFAEEC26985A3400ED2C85 /* ACProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACProgressView.swift; sourceTree = "<group>"; };
|
7AEFAEEC26985A3400ED2C85 /* ACProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACProgressView.swift; sourceTree = "<group>"; };
|
||||||
7AF552D82696E5C100578083 /* ApiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiTests.swift; sourceTree = "<group>"; };
|
7AF552D82696E5C100578083 /* ApiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiTests.swift; sourceTree = "<group>"; };
|
||||||
@ -179,6 +199,7 @@
|
|||||||
7A40D5782691C6D7009B0BC4 /* Shared */ = {
|
7A40D5782691C6D7009B0BC4 /* Shared */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
7A971F0B26AD7D27007E527B /* ThirdParty */,
|
||||||
7A40D6002694FF4C009B0BC4 /* Utils */,
|
7A40D6002694FF4C009B0BC4 /* Utils */,
|
||||||
7A40D5FC2693A90F009B0BC4 /* Extensions */,
|
7A40D5FC2693A90F009B0BC4 /* Extensions */,
|
||||||
7A40D5EB2693A1C3009B0BC4 /* ViewModels */,
|
7A40D5EB2693A1C3009B0BC4 /* ViewModels */,
|
||||||
@ -266,8 +287,7 @@
|
|||||||
7A40D5F42693A63A009B0BC4 /* AutoCat2Tests */ = {
|
7A40D5F42693A63A009B0BC4 /* AutoCat2Tests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7A503C00269F370A002C1A0D /* Responses */,
|
7A971F0326AD6EA1007E527B /* Api */,
|
||||||
7A40D60A2699A04F009B0BC4 /* Mocks */,
|
|
||||||
7A40D5F52693A63A009B0BC4 /* SettingsTests.swift */,
|
7A40D5F52693A63A009B0BC4 /* SettingsTests.swift */,
|
||||||
7AF552D82696E5C100578083 /* ApiTests.swift */,
|
7AF552D82696E5C100578083 /* ApiTests.swift */,
|
||||||
);
|
);
|
||||||
@ -287,6 +307,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7A40D6012694FF5D009B0BC4 /* Api.swift */,
|
7A40D6012694FF5D009B0BC4 /* Api.swift */,
|
||||||
|
7A971F1F26ADC351007E527B /* ApiError.swift */,
|
||||||
7ACD05D62695C08A00557667 /* Constants.swift */,
|
7ACD05D62695C08A00557667 /* Constants.swift */,
|
||||||
);
|
);
|
||||||
path = Utils;
|
path = Utils;
|
||||||
@ -295,7 +316,8 @@
|
|||||||
7A40D60A2699A04F009B0BC4 /* Mocks */ = {
|
7A40D60A2699A04F009B0BC4 /* Mocks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7A40D60B2699A070009B0BC4 /* MockURLProtocol.swift */,
|
7A971F0526AD6F2F007E527B /* ApiMethodMock.swift */,
|
||||||
|
7A971F0726AD7084007E527B /* LoginMethodMock.swift */,
|
||||||
);
|
);
|
||||||
path = Mocks;
|
path = Mocks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -311,13 +333,50 @@
|
|||||||
7A503C01269F3797002C1A0D /* Login */ = {
|
7A503C01269F3797002C1A0D /* Login */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7A503C06269F49F4002C1A0D /* login_wrong_credentials.json */,
|
|
||||||
7A503C04269F494C002C1A0D /* login_invalid_params.json */,
|
|
||||||
7A503C02269F382F002C1A0D /* login_success.json */,
|
7A503C02269F382F002C1A0D /* login_success.json */,
|
||||||
);
|
);
|
||||||
path = Login;
|
path = Login;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
7A971F0326AD6EA1007E527B /* Api */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7A971F0426AD6ED6007E527B /* Lib */,
|
||||||
|
7A40D60A2699A04F009B0BC4 /* Mocks */,
|
||||||
|
7A503C00269F370A002C1A0D /* Responses */,
|
||||||
|
);
|
||||||
|
path = Api;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
7A971F0426AD6ED6007E527B /* Lib */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7A40D60B2699A070009B0BC4 /* MockURLProtocol.swift */,
|
||||||
|
7A971F0926AD74FD007E527B /* ApiMethodMockProtocol.swift */,
|
||||||
|
);
|
||||||
|
path = Lib;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
7A971F0B26AD7D27007E527B /* ThirdParty */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7A971F0F26AD8AEB007E527B /* GenericJSON */,
|
||||||
|
7A971F0C26AD7D4C007E527B /* AnyEncodable.swift */,
|
||||||
|
);
|
||||||
|
path = ThirdParty;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
7A971F0F26AD8AEB007E527B /* GenericJSON */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7A971F1026AD8AEB007E527B /* Initialization.swift */,
|
||||||
|
7A971F1126AD8AEB007E527B /* Querying.swift */,
|
||||||
|
7A971F1226AD8AEB007E527B /* JSON.swift */,
|
||||||
|
7A971F1426AD8AEB007E527B /* Merging.swift */,
|
||||||
|
);
|
||||||
|
path = GenericJSON;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@ -498,9 +557,7 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7A503C07269F49F4002C1A0D /* login_wrong_credentials.json in Resources */,
|
|
||||||
7A503C03269F382F002C1A0D /* login_success.json in Resources */,
|
7A503C03269F382F002C1A0D /* login_success.json in Resources */,
|
||||||
7A503C05269F494C002C1A0D /* login_invalid_params.json in Resources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -512,13 +569,19 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7A40D5E926938BEC009B0BC4 /* AuthView.swift in Sources */,
|
7A40D5E926938BEC009B0BC4 /* AuthView.swift in Sources */,
|
||||||
|
7A971F0D26AD7D4C007E527B /* AnyEncodable.swift in Sources */,
|
||||||
7ACD05D72695C08A00557667 /* Constants.swift in Sources */,
|
7ACD05D72695C08A00557667 /* Constants.swift in Sources */,
|
||||||
|
7A971F1926AD8AEB007E527B /* JSON.swift in Sources */,
|
||||||
|
7A971F1D26AD8AEB007E527B /* Merging.swift in Sources */,
|
||||||
7A40D5E326924B09009B0BC4 /* Settings.swift in Sources */,
|
7A40D5E326924B09009B0BC4 /* Settings.swift in Sources */,
|
||||||
7AEFAEED26985A3400ED2C85 /* ACProgressView.swift in Sources */,
|
7AEFAEED26985A3400ED2C85 /* ACProgressView.swift in Sources */,
|
||||||
7A40D5A02691C6D8009B0BC4 /* AutoCat2App.swift in Sources */,
|
7A40D5A02691C6D8009B0BC4 /* AutoCat2App.swift in Sources */,
|
||||||
7A683999269612EA00B2188A /* Response.swift in Sources */,
|
7A683999269612EA00B2188A /* Response.swift in Sources */,
|
||||||
|
7A971F1526AD8AEB007E527B /* Initialization.swift in Sources */,
|
||||||
|
7A971F1726AD8AEB007E527B /* Querying.swift in Sources */,
|
||||||
7A40D60826998DCF009B0BC4 /* Alert.swift in Sources */,
|
7A40D60826998DCF009B0BC4 /* Alert.swift in Sources */,
|
||||||
7A40D5A42691C6D8009B0BC4 /* Persistence.swift in Sources */,
|
7A40D5A42691C6D8009B0BC4 /* Persistence.swift in Sources */,
|
||||||
|
7A971F2026ADC351007E527B /* ApiError.swift in Sources */,
|
||||||
7A40D5ED2693A1EA009B0BC4 /* AuthVM.swift in Sources */,
|
7A40D5ED2693A1EA009B0BC4 /* AuthVM.swift in Sources */,
|
||||||
7A40D59E2691C6D8009B0BC4 /* AutoCat2.xcdatamodeld in Sources */,
|
7A40D59E2691C6D8009B0BC4 /* AutoCat2.xcdatamodeld in Sources */,
|
||||||
7A40D5E126924AEC009B0BC4 /* User.swift in Sources */,
|
7A40D5E126924AEC009B0BC4 /* User.swift in Sources */,
|
||||||
@ -533,13 +596,19 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7A40D5EA26938BEC009B0BC4 /* AuthView.swift in Sources */,
|
7A40D5EA26938BEC009B0BC4 /* AuthView.swift in Sources */,
|
||||||
|
7A971F0E26AD7D4C007E527B /* AnyEncodable.swift in Sources */,
|
||||||
7ACD05D82695C08A00557667 /* Constants.swift in Sources */,
|
7ACD05D82695C08A00557667 /* Constants.swift in Sources */,
|
||||||
|
7A971F1A26AD8AEB007E527B /* JSON.swift in Sources */,
|
||||||
|
7A971F1E26AD8AEB007E527B /* Merging.swift in Sources */,
|
||||||
7A40D5A12691C6D8009B0BC4 /* AutoCat2App.swift in Sources */,
|
7A40D5A12691C6D8009B0BC4 /* AutoCat2App.swift in Sources */,
|
||||||
7AEFAEEE26985A3400ED2C85 /* ACProgressView.swift in Sources */,
|
7AEFAEEE26985A3400ED2C85 /* ACProgressView.swift in Sources */,
|
||||||
7A40D5A52691C6D8009B0BC4 /* Persistence.swift in Sources */,
|
7A40D5A52691C6D8009B0BC4 /* Persistence.swift in Sources */,
|
||||||
7A68399A269612EA00B2188A /* Response.swift in Sources */,
|
7A68399A269612EA00B2188A /* Response.swift in Sources */,
|
||||||
|
7A971F1626AD8AEB007E527B /* Initialization.swift in Sources */,
|
||||||
|
7A971F1826AD8AEB007E527B /* Querying.swift in Sources */,
|
||||||
7A40D60926998DCF009B0BC4 /* Alert.swift in Sources */,
|
7A40D60926998DCF009B0BC4 /* Alert.swift in Sources */,
|
||||||
7A40D5E526924B0C009B0BC4 /* User.swift in Sources */,
|
7A40D5E526924B0C009B0BC4 /* User.swift in Sources */,
|
||||||
|
7A971F2126ADC351007E527B /* ApiError.swift in Sources */,
|
||||||
7A40D5EE2693A1EA009B0BC4 /* AuthVM.swift in Sources */,
|
7A40D5EE2693A1EA009B0BC4 /* AuthVM.swift in Sources */,
|
||||||
7A40D59F2691C6D8009B0BC4 /* AutoCat2.xcdatamodeld in Sources */,
|
7A40D59F2691C6D8009B0BC4 /* AutoCat2.xcdatamodeld in Sources */,
|
||||||
7A40D5A32691C6D8009B0BC4 /* ContentView.swift in Sources */,
|
7A40D5A32691C6D8009B0BC4 /* ContentView.swift in Sources */,
|
||||||
@ -570,8 +639,11 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7A40D60C2699A070009B0BC4 /* MockURLProtocol.swift in Sources */,
|
7A40D60C2699A070009B0BC4 /* MockURLProtocol.swift in Sources */,
|
||||||
|
7A971F0826AD7084007E527B /* LoginMethodMock.swift in Sources */,
|
||||||
7AF552D92696E5C100578083 /* ApiTests.swift in Sources */,
|
7AF552D92696E5C100578083 /* ApiTests.swift in Sources */,
|
||||||
|
7A971F0A26AD74FD007E527B /* ApiMethodMockProtocol.swift in Sources */,
|
||||||
7A40D5F62693A63A009B0BC4 /* SettingsTests.swift in Sources */,
|
7A40D5F62693A63A009B0BC4 /* SettingsTests.swift in Sources */,
|
||||||
|
7A971F0626AD6F2F007E527B /* ApiMethodMock.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,12 +7,12 @@
|
|||||||
<key>AutoCat2 (iOS).xcscheme_^#shared#^_</key>
|
<key>AutoCat2 (iOS).xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>1</integer>
|
<integer>0</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>AutoCat2 (macOS).xcscheme_^#shared#^_</key>
|
<key>AutoCat2 (macOS).xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>0</integer>
|
<integer>1</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>AutoCatCore.xcscheme_^#shared#^_</key>
|
<key>AutoCatCore.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
|||||||
7
AutoCat2Tests/Api/Lib/ApiMethodMockProtocol.swift
Normal file
7
AutoCat2Tests/Api/Lib/ApiMethodMockProtocol.swift
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
protocol ApiMethodMockProtocol {
|
||||||
|
var path: String { get }
|
||||||
|
var httpMethod: String { get }
|
||||||
|
func response(headers: [String: String], params: [String: Any]) -> (status: Int, data: Data?)
|
||||||
|
}
|
||||||
96
AutoCat2Tests/Api/Lib/MockURLProtocol.swift
Normal file
96
AutoCat2Tests/Api/Lib/MockURLProtocol.swift
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension URL {
|
||||||
|
public var queryParameters: [String: String]? {
|
||||||
|
guard
|
||||||
|
let components = URLComponents(url: self, resolvingAgainstBaseURL: true),
|
||||||
|
let queryItems = components.queryItems else { return nil }
|
||||||
|
return queryItems.reduce(into: [String: String]()) { (result, item) in
|
||||||
|
result[item.name] = item.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension URLRequest {
|
||||||
|
func bodySteamAsJSON() -> [String: Any]? {
|
||||||
|
guard let bodyStream = self.httpBodyStream else { return nil }
|
||||||
|
|
||||||
|
bodyStream.open()
|
||||||
|
|
||||||
|
// Will read 16 chars per iteration. Can use bigger buffer if needed
|
||||||
|
let bufferSize: Int = 16
|
||||||
|
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
|
||||||
|
var dat = Data()
|
||||||
|
|
||||||
|
while bodyStream.hasBytesAvailable {
|
||||||
|
let readDat = bodyStream.read(buffer, maxLength: bufferSize)
|
||||||
|
dat.append(buffer, count: readDat)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.deallocate()
|
||||||
|
bodyStream.close()
|
||||||
|
|
||||||
|
return try? JSONSerialization.jsonObject(with: dat, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: Any]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockURLProtocol: URLProtocol {
|
||||||
|
|
||||||
|
static var baseUrl: String = ""
|
||||||
|
static var apiMethodMocks: [ApiMethodMockProtocol] = []
|
||||||
|
|
||||||
|
override class func canInit(with request: URLRequest) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
override func startLoading() {
|
||||||
|
guard let requestUrl = request.url else { return }
|
||||||
|
|
||||||
|
let methodMock = MockURLProtocol.apiMethodMocks.first {
|
||||||
|
return request.url?.absoluteString == MockURLProtocol.baseUrl + $0.path
|
||||||
|
&& request.httpMethod == $0.httpMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let methodMock = methodMock else {
|
||||||
|
if let response = HTTPURLResponse(url: requestUrl, statusCode: 404, httpVersion: "HTTP/2", headerFields: [:]) {
|
||||||
|
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
|
||||||
|
client?.urlProtocolDidFinishLoading(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming we use url parameters in GET requests and JSON-encoded body in everything else
|
||||||
|
var params: [String: Any] = [:]
|
||||||
|
if request.httpBodyStream != nil {
|
||||||
|
if let bodyDict = request.bodySteamAsJSON() {
|
||||||
|
params = bodyDict
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let urlParams = requestUrl.queryParameters {
|
||||||
|
params = urlParams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = methodMock.response(headers: request.allHTTPHeaderFields ?? [:], params: params)
|
||||||
|
guard let response = HTTPURLResponse(url: requestUrl, statusCode: result.status, httpVersion: "HTTP/2", headerFields: [:]) else { return }
|
||||||
|
|
||||||
|
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
|
||||||
|
|
||||||
|
if let data = result.data {
|
||||||
|
client?.urlProtocol(self, didLoad: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
client?.urlProtocolDidFinishLoading(self)
|
||||||
|
|
||||||
|
//client?.urlProtocol(self, didFailWithError: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func stopLoading() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
39
AutoCat2Tests/Api/Mocks/ApiMethodMock.swift
Normal file
39
AutoCat2Tests/Api/Mocks/ApiMethodMock.swift
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import Foundation
|
||||||
|
import AutoCat2
|
||||||
|
|
||||||
|
open class ApiMethodMock: ApiMethodMockProtocol {
|
||||||
|
|
||||||
|
private(set) var path: String
|
||||||
|
private(set) var httpMethod: String
|
||||||
|
|
||||||
|
init(httpMethod: String, path: String) {
|
||||||
|
self.httpMethod = httpMethod
|
||||||
|
self.path = path
|
||||||
|
}
|
||||||
|
|
||||||
|
func readData(from path: String) -> Data? {
|
||||||
|
guard let url = Bundle(for: type(of: self)).url(forResource: path, withExtension: "json") else { return nil }
|
||||||
|
return try? Data(contentsOf: url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func error(message: String, code: ApiErrorCode? = nil) -> Data? {
|
||||||
|
var errorData: [String: AnyEncodable] = [
|
||||||
|
"success": false,
|
||||||
|
"error": AnyEncodable(message)
|
||||||
|
]
|
||||||
|
|
||||||
|
if let code = code {
|
||||||
|
errorData["errorCode"] = AnyEncodable(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return try? JSONEncoder().encode(errorData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func notFoundResponse() -> (status: Int, data: Data?) {
|
||||||
|
return (status: 404, data: self.error(message: "Not found"))
|
||||||
|
}
|
||||||
|
|
||||||
|
open func response(headers: [String : String], params: [String : Any]) -> (status: Int, data: Data?) {
|
||||||
|
return self.notFoundResponse()
|
||||||
|
}
|
||||||
|
}
|
||||||
24
AutoCat2Tests/Api/Mocks/LoginMethodMock.swift
Normal file
24
AutoCat2Tests/Api/Mocks/LoginMethodMock.swift
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
class LoginMethodMock: ApiMethodMock {
|
||||||
|
private var login: String
|
||||||
|
private var password: String
|
||||||
|
|
||||||
|
init(httpMethod: String, path: String, login: String, password: String) {
|
||||||
|
self.login = login
|
||||||
|
self.password = password
|
||||||
|
super.init(httpMethod: httpMethod, path: path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func response(headers: [String : String], params: [String : Any]) -> (status: Int, data: Data?) {
|
||||||
|
guard let login = params["email"] as? String, let password = params["password"] as? String else {
|
||||||
|
return (status: 400, data: self.error(message: "Invalid parameters"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if login != self.login || password != self.password {
|
||||||
|
return (status: 200, data: self.error(message: "Incorrect login or password", code: .invalidLoginOrPassword))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (status: 200, data: readData(from: "login_success"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,8 +4,16 @@ import AutoCat2
|
|||||||
class ApiTests: XCTestCase {
|
class ApiTests: XCTestCase {
|
||||||
|
|
||||||
private var api: Api!
|
private var api: Api!
|
||||||
|
|
||||||
|
private let testLogin = "test@gmail.com"
|
||||||
|
private let testPassword = "12345"
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
|
MockURLProtocol.baseUrl = Constants.baseUrl
|
||||||
|
MockURLProtocol.apiMethodMocks = [
|
||||||
|
LoginMethodMock(httpMethod: "POST", path: "user/login", login: self.testLogin, password: self.testPassword)
|
||||||
|
]
|
||||||
|
|
||||||
let sessionConfig = URLSessionConfiguration.default
|
let sessionConfig = URLSessionConfiguration.default
|
||||||
sessionConfig.protocolClasses = [MockURLProtocol.self]
|
sessionConfig.protocolClasses = [MockURLProtocol.self]
|
||||||
let session = URLSession(configuration: sessionConfig)
|
let session = URLSession(configuration: sessionConfig)
|
||||||
@ -13,31 +21,25 @@ class ApiTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
override func tearDownWithError() throws {
|
||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
MockURLProtocol.baseUrl = ""
|
||||||
|
MockURLProtocol.apiMethodMocks = []
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoginSuccess() async throws {
|
func testLoginSuccess() async throws {
|
||||||
MockURLProtocol.responseType = MockResponseType.loginSuccess
|
let user = try await self.api.login(email: self.testLogin, password: self.testPassword)
|
||||||
let user = try await self.api.login(email: "", password: "")
|
|
||||||
XCTAssertTrue(!user.token.isEmpty)
|
XCTAssertTrue(!user.token.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoginInvalidParams() async throws {
|
func testLoginInvalidParams() async throws {
|
||||||
MockURLProtocol.responseType = MockResponseType.loginWrongCredentials
|
|
||||||
do {
|
do {
|
||||||
_ = try await self.api.login(email: "", password: "")
|
_ = try await self.api.login(email: "", password: "")
|
||||||
} catch {
|
} catch let error as ApiError {
|
||||||
|
XCTAssertTrue(error.code == .invalidLoginOrPassword)
|
||||||
return
|
return
|
||||||
|
} catch {
|
||||||
|
XCTFail("Wrong exception type")
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTFail("Exception expected")
|
XCTFail("Exception expected")
|
||||||
}
|
}
|
||||||
|
|
||||||
// func testPerformanceExample() throws {
|
|
||||||
// // This is an example of a performance test case.
|
|
||||||
// self.measure {
|
|
||||||
// // Put the code you want to measure the time of here.
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
enum MockResponseType: String {
|
|
||||||
case loginSuccess = "login_success"
|
|
||||||
case loginWrongCredentials = "login_wrong_credentials"
|
|
||||||
case
|
|
||||||
}
|
|
||||||
|
|
||||||
class MockURLProtocol: URLProtocol {
|
|
||||||
|
|
||||||
static var responseType: MockResponseType?
|
|
||||||
|
|
||||||
override class func canInit(with request: URLRequest) -> Bool {
|
|
||||||
// To check if this protocol can handle the given request.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
|
|
||||||
// Here you return the canonical version of the request but most of the time you pass the orignal one.
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
|
|
||||||
override func startLoading() {
|
|
||||||
|
|
||||||
guard let respType = MockURLProtocol.responseType else { return }
|
|
||||||
guard let jsonUrl = Bundle(for: type(of: self)).url(forResource: respType.rawValue, withExtension: "json") else { return }
|
|
||||||
guard let response = HTTPURLResponse(url: jsonUrl, statusCode: 200, httpVersion: "HTTP/2", headerFields: [:]) else { return }
|
|
||||||
|
|
||||||
let data = try? Data(contentsOf: jsonUrl)
|
|
||||||
|
|
||||||
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
|
|
||||||
|
|
||||||
if let data = data {
|
|
||||||
client?.urlProtocol(self, didLoad: data)
|
|
||||||
}
|
|
||||||
|
|
||||||
client?.urlProtocolDidFinishLoading(self)
|
|
||||||
|
|
||||||
//client?.urlProtocol(self, didFailWithError: error)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func stopLoading() {
|
|
||||||
// This is called if the request gets canceled or completed.
|
|
||||||
print("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": "Invalid parameters"
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": "Incorrect email or password"
|
|
||||||
}
|
|
||||||
@ -2,16 +2,22 @@ import CoreLocation
|
|||||||
|
|
||||||
extension NSError {
|
extension NSError {
|
||||||
public var displayMessage: (title: String, body: String) {
|
public var displayMessage: (title: String, body: String) {
|
||||||
if let description = self.userInfo[NSLocalizedDescriptionKey] as? String {
|
// if let description = self.userInfo[NSLocalizedDescriptionKey] as? String {
|
||||||
return (title: "Error", body: description)
|
// return (title: "Error", body: description)
|
||||||
} else if let failure = self.userInfo[NSLocalizedFailureErrorKey] as? String, let reason = self.localizedFailureReason {
|
// } else if let failure = self.userInfo[NSLocalizedFailureErrorKey] as? String, let reason = self.localizedFailureReason {
|
||||||
if let recovery = self.localizedRecoverySuggestion {
|
// if let recovery = self.localizedRecoverySuggestion {
|
||||||
return (title: failure, body: reason + "\n" + recovery)
|
// return (title: failure, body: reason + "\n" + recovery)
|
||||||
} else {
|
// } else {
|
||||||
return (title: failure, body: reason)
|
// return (title: failure, body: reason)
|
||||||
}
|
// }
|
||||||
|
// } else {
|
||||||
|
// return (title: "Error", body: "")
|
||||||
|
// }
|
||||||
|
|
||||||
|
if let recovery = self.localizedRecoverySuggestion {
|
||||||
|
return (title: "Error", body: self.localizedDescription + "\n" + recovery)
|
||||||
} else {
|
} else {
|
||||||
return (title: "Error", body: "")
|
return (title: "Error", body: self.localizedDescription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,13 @@ class Response<T>: Decodable where T: Decodable {
|
|||||||
let success: Bool
|
let success: Bool
|
||||||
let data: T?
|
let data: T?
|
||||||
let error: String?
|
let error: String?
|
||||||
|
let errorCode: ApiErrorCode?
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case success
|
case success
|
||||||
case data
|
case data
|
||||||
case error
|
case error
|
||||||
|
case errorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws {
|
required init(from decoder: Decoder) throws {
|
||||||
@ -17,8 +19,10 @@ class Response<T>: Decodable where T: Decodable {
|
|||||||
if success {
|
if success {
|
||||||
data = try container.decode(T.self, forKey: .data)
|
data = try container.decode(T.self, forKey: .data)
|
||||||
error = nil
|
error = nil
|
||||||
|
errorCode = nil
|
||||||
} else {
|
} else {
|
||||||
error = try container.decode(String.self, forKey: .error)
|
error = try container.decode(String.self, forKey: .error)
|
||||||
|
errorCode = try container.decodeIfPresent(ApiErrorCode.self, forKey: .errorCode)
|
||||||
data = nil
|
data = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
273
Shared/ThirdParty/AnyEncodable.swift
vendored
Normal file
273
Shared/ThirdParty/AnyEncodable.swift
vendored
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
#if canImport(Foundation)
|
||||||
|
import Foundation
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
A type-erased `Encodable` value.
|
||||||
|
|
||||||
|
The `AnyEncodable` type forwards encoding responsibilities
|
||||||
|
to an underlying value, hiding its specific underlying type.
|
||||||
|
|
||||||
|
You can encode mixed-type values in dictionaries
|
||||||
|
and other collections that require `Encodable` conformance
|
||||||
|
by declaring their contained type to be `AnyEncodable`:
|
||||||
|
|
||||||
|
let dictionary: [String: AnyEncodable] = [
|
||||||
|
"boolean": true,
|
||||||
|
"integer": 1,
|
||||||
|
"double": 3.141592653589793,
|
||||||
|
"string": "string",
|
||||||
|
"array": [1, 2, 3],
|
||||||
|
"nested": [
|
||||||
|
"a": "alpha",
|
||||||
|
"b": "bravo",
|
||||||
|
"c": "charlie"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
let json = try! encoder.encode(dictionary)
|
||||||
|
*/
|
||||||
|
#if swift(>=5.1)
|
||||||
|
@frozen public struct AnyEncodable: Encodable {
|
||||||
|
public let value: Any
|
||||||
|
|
||||||
|
public init<T>(_ value: T?) {
|
||||||
|
self.value = value ?? ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
public struct AnyEncodable: Encodable {
|
||||||
|
public let value: Any
|
||||||
|
|
||||||
|
public init<T>(_ value: T?) {
|
||||||
|
self.value = value ?? ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if swift(>=4.2)
|
||||||
|
@usableFromInline
|
||||||
|
protocol _AnyEncodable {
|
||||||
|
var value: Any { get }
|
||||||
|
init<T>(_ value: T?)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
protocol _AnyEncodable {
|
||||||
|
var value: Any { get }
|
||||||
|
init<T>(_ value: T?)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extension AnyEncodable: _AnyEncodable {}
|
||||||
|
|
||||||
|
// MARK: - Encodable
|
||||||
|
|
||||||
|
extension _AnyEncodable {
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
|
||||||
|
switch value {
|
||||||
|
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
|
||||||
|
case let number as NSNumber:
|
||||||
|
try encode(nsnumber: number, into: &container)
|
||||||
|
#endif
|
||||||
|
#if canImport(Foundation)
|
||||||
|
case is NSNull:
|
||||||
|
try container.encodeNil()
|
||||||
|
#endif
|
||||||
|
case is Void:
|
||||||
|
try container.encodeNil()
|
||||||
|
case let bool as Bool:
|
||||||
|
try container.encode(bool)
|
||||||
|
case let int as Int:
|
||||||
|
try container.encode(int)
|
||||||
|
case let int8 as Int8:
|
||||||
|
try container.encode(int8)
|
||||||
|
case let int16 as Int16:
|
||||||
|
try container.encode(int16)
|
||||||
|
case let int32 as Int32:
|
||||||
|
try container.encode(int32)
|
||||||
|
case let int64 as Int64:
|
||||||
|
try container.encode(int64)
|
||||||
|
case let uint as UInt:
|
||||||
|
try container.encode(uint)
|
||||||
|
case let uint8 as UInt8:
|
||||||
|
try container.encode(uint8)
|
||||||
|
case let uint16 as UInt16:
|
||||||
|
try container.encode(uint16)
|
||||||
|
case let uint32 as UInt32:
|
||||||
|
try container.encode(uint32)
|
||||||
|
case let uint64 as UInt64:
|
||||||
|
try container.encode(uint64)
|
||||||
|
case let float as Float:
|
||||||
|
try container.encode(float)
|
||||||
|
case let double as Double:
|
||||||
|
try container.encode(double)
|
||||||
|
case let string as String:
|
||||||
|
try container.encode(string)
|
||||||
|
#if canImport(Foundation)
|
||||||
|
case let date as Date:
|
||||||
|
try container.encode(date)
|
||||||
|
case let url as URL:
|
||||||
|
try container.encode(url)
|
||||||
|
#endif
|
||||||
|
case let array as [Any?]:
|
||||||
|
try container.encode(array.map { AnyEncodable($0) })
|
||||||
|
case let dictionary as [String: Any?]:
|
||||||
|
try container.encode(dictionary.mapValues { AnyEncodable($0) })
|
||||||
|
case let enc as Encodable:
|
||||||
|
//try container.encode(enc)
|
||||||
|
try enc.encode(to: encoder)
|
||||||
|
default:
|
||||||
|
let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "AnyEncodable value cannot be encoded")
|
||||||
|
throw EncodingError.invalidValue(value, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
|
||||||
|
private func encode(nsnumber: NSNumber, into container: inout SingleValueEncodingContainer) throws {
|
||||||
|
switch CFNumberGetType(nsnumber) {
|
||||||
|
case .charType:
|
||||||
|
try container.encode(nsnumber.boolValue)
|
||||||
|
case .sInt8Type:
|
||||||
|
try container.encode(nsnumber.int8Value)
|
||||||
|
case .sInt16Type:
|
||||||
|
try container.encode(nsnumber.int16Value)
|
||||||
|
case .sInt32Type:
|
||||||
|
try container.encode(nsnumber.int32Value)
|
||||||
|
case .sInt64Type:
|
||||||
|
try container.encode(nsnumber.int64Value)
|
||||||
|
case .shortType:
|
||||||
|
try container.encode(nsnumber.uint16Value)
|
||||||
|
case .longType:
|
||||||
|
try container.encode(nsnumber.uint32Value)
|
||||||
|
case .longLongType:
|
||||||
|
try container.encode(nsnumber.uint64Value)
|
||||||
|
case .intType, .nsIntegerType, .cfIndexType:
|
||||||
|
try container.encode(nsnumber.intValue)
|
||||||
|
case .floatType, .float32Type:
|
||||||
|
try container.encode(nsnumber.floatValue)
|
||||||
|
case .doubleType, .float64Type, .cgFloatType:
|
||||||
|
try container.encode(nsnumber.doubleValue)
|
||||||
|
#if swift(>=5.0)
|
||||||
|
@unknown default:
|
||||||
|
let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "NSNumber cannot be encoded because its type is not handled")
|
||||||
|
throw EncodingError.invalidValue(nsnumber, context)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: Equatable {
|
||||||
|
public static func == (lhs: AnyEncodable, rhs: AnyEncodable) -> Bool {
|
||||||
|
switch (lhs.value, rhs.value) {
|
||||||
|
case is (Void, Void):
|
||||||
|
return true
|
||||||
|
case let (lhs as Bool, rhs as Bool):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int, rhs as Int):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int8, rhs as Int8):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int16, rhs as Int16):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int32, rhs as Int32):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int64, rhs as Int64):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt, rhs as UInt):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt8, rhs as UInt8):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt16, rhs as UInt16):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt32, rhs as UInt32):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt64, rhs as UInt64):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Float, rhs as Float):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Double, rhs as Double):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as String, rhs as String):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as [String: AnyEncodable], rhs as [String: AnyEncodable]):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as [AnyEncodable], rhs as [AnyEncodable]):
|
||||||
|
return lhs == rhs
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: CustomStringConvertible {
|
||||||
|
public var description: String {
|
||||||
|
switch value {
|
||||||
|
case is Void:
|
||||||
|
return String(describing: nil as Any?)
|
||||||
|
case let value as CustomStringConvertible:
|
||||||
|
return value.description
|
||||||
|
default:
|
||||||
|
return String(describing: value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: CustomDebugStringConvertible {
|
||||||
|
public var debugDescription: String {
|
||||||
|
switch value {
|
||||||
|
case let value as CustomDebugStringConvertible:
|
||||||
|
return "AnyEncodable(\(value.debugDescription))"
|
||||||
|
default:
|
||||||
|
return "AnyEncodable(\(description))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: ExpressibleByNilLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByBooleanLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByIntegerLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByFloatLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByStringLiteral {}
|
||||||
|
#if swift(>=5.0)
|
||||||
|
extension AnyEncodable: ExpressibleByStringInterpolation {}
|
||||||
|
#endif
|
||||||
|
extension AnyEncodable: ExpressibleByArrayLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByDictionaryLiteral {}
|
||||||
|
|
||||||
|
extension _AnyEncodable {
|
||||||
|
public init(nilLiteral _: ()) {
|
||||||
|
self.init(nil as Any?)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(booleanLiteral value: Bool) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(integerLiteral value: Int) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(floatLiteral value: Double) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(extendedGraphemeClusterLiteral value: String) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(stringLiteral value: String) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(arrayLiteral elements: Any...) {
|
||||||
|
self.init(elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(dictionaryLiteral elements: (AnyHashable, Any)...) {
|
||||||
|
self.init([AnyHashable: Any](elements, uniquingKeysWith: { first, _ in first }))
|
||||||
|
}
|
||||||
|
}
|
||||||
124
Shared/ThirdParty/GenericJSON/Initialization.swift
vendored
Normal file
124
Shared/ThirdParty/GenericJSON/Initialization.swift
vendored
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
private struct InitializationError: Error {}
|
||||||
|
|
||||||
|
extension JSON {
|
||||||
|
|
||||||
|
/// Create a JSON value from anything.
|
||||||
|
///
|
||||||
|
/// Argument has to be a valid JSON structure: A `Double`, `Int`, `String`,
|
||||||
|
/// `Bool`, an `Array` of those types or a `Dictionary` of those types.
|
||||||
|
///
|
||||||
|
/// You can also pass `nil` or `NSNull`, both will be treated as `.null`.
|
||||||
|
public init(_ value: Any) throws {
|
||||||
|
switch value {
|
||||||
|
case _ as NSNull:
|
||||||
|
self = .null
|
||||||
|
case let opt as Optional<Any> where opt == nil:
|
||||||
|
self = .null
|
||||||
|
case let num as NSNumber:
|
||||||
|
if num.isBool {
|
||||||
|
self = .bool(num.boolValue)
|
||||||
|
} else {
|
||||||
|
self = .number(num.doubleValue)
|
||||||
|
}
|
||||||
|
case let str as String:
|
||||||
|
self = .string(str)
|
||||||
|
case let bool as Bool:
|
||||||
|
self = .bool(bool)
|
||||||
|
case let array as [Any]:
|
||||||
|
self = .array(try array.map(JSON.init))
|
||||||
|
case let dict as [String:Any]:
|
||||||
|
self = .object(try dict.mapValues(JSON.init))
|
||||||
|
default:
|
||||||
|
throw InitializationError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JSON {
|
||||||
|
|
||||||
|
/// Create a JSON value from an `Encodable`. This will give you access to the “raw”
|
||||||
|
/// encoded JSON value the `Encodable` is serialized into.
|
||||||
|
public init<T: Encodable>(encodable: T) throws {
|
||||||
|
let encoded = try JSONEncoder().encode(encodable)
|
||||||
|
self = try JSONDecoder().decode(JSON.self, from: encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JSON: ExpressibleByBooleanLiteral {
|
||||||
|
|
||||||
|
public init(booleanLiteral value: Bool) {
|
||||||
|
self = .bool(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JSON: ExpressibleByNilLiteral {
|
||||||
|
|
||||||
|
public init(nilLiteral: ()) {
|
||||||
|
self = .null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JSON: ExpressibleByArrayLiteral {
|
||||||
|
|
||||||
|
public init(arrayLiteral elements: JSON...) {
|
||||||
|
self = .array(elements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JSON: ExpressibleByDictionaryLiteral {
|
||||||
|
|
||||||
|
public init(dictionaryLiteral elements: (String, JSON)...) {
|
||||||
|
var object: [String:JSON] = [:]
|
||||||
|
for (k, v) in elements {
|
||||||
|
object[k] = v
|
||||||
|
}
|
||||||
|
self = .object(object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JSON: ExpressibleByFloatLiteral {
|
||||||
|
|
||||||
|
public init(floatLiteral value: Double) {
|
||||||
|
self = .number(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JSON: ExpressibleByIntegerLiteral {
|
||||||
|
|
||||||
|
public init(integerLiteral value: Int) {
|
||||||
|
self = .number(Double(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JSON: ExpressibleByStringLiteral {
|
||||||
|
|
||||||
|
public init(stringLiteral value: String) {
|
||||||
|
self = .string(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - NSNumber
|
||||||
|
|
||||||
|
extension NSNumber {
|
||||||
|
|
||||||
|
/// Boolean value indicating whether this `NSNumber` wraps a boolean.
|
||||||
|
///
|
||||||
|
/// For example, when using `NSJSONSerialization` Bool values are converted into `NSNumber` instances.
|
||||||
|
///
|
||||||
|
/// - seealso: https://stackoverflow.com/a/49641315/3589408
|
||||||
|
fileprivate var isBool: Bool {
|
||||||
|
let objCType = String(cString: self.objCType)
|
||||||
|
if (self.compare(trueNumber) == .orderedSame && objCType == trueObjCType) || (self.compare(falseNumber) == .orderedSame && objCType == falseObjCType) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let trueNumber = NSNumber(value: true)
|
||||||
|
private let falseNumber = NSNumber(value: false)
|
||||||
|
private let trueObjCType = String(cString: trueNumber.objCType)
|
||||||
|
private let falseObjCType = String(cString: falseNumber.objCType)
|
||||||
82
Shared/ThirdParty/GenericJSON/JSON.swift
vendored
Normal file
82
Shared/ThirdParty/GenericJSON/JSON.swift
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// A JSON value representation. This is a bit more useful than the naïve `[String:Any]` type
|
||||||
|
/// for JSON values, since it makes sure only valid JSON values are present & supports `Equatable`
|
||||||
|
/// and `Codable`, so that you can compare values for equality and code and decode them into data
|
||||||
|
/// or strings.
|
||||||
|
@dynamicMemberLookup public enum JSON: Equatable {
|
||||||
|
case string(String)
|
||||||
|
case number(Double)
|
||||||
|
case object([String:JSON])
|
||||||
|
case array([JSON])
|
||||||
|
case bool(Bool)
|
||||||
|
case null
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JSON: Codable {
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
case let .array(array):
|
||||||
|
try container.encode(array)
|
||||||
|
case let .object(object):
|
||||||
|
try container.encode(object)
|
||||||
|
case let .string(string):
|
||||||
|
try container.encode(string)
|
||||||
|
case let .number(number):
|
||||||
|
try container.encode(number)
|
||||||
|
case let .bool(bool):
|
||||||
|
try container.encode(bool)
|
||||||
|
case .null:
|
||||||
|
try container.encodeNil()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
|
||||||
|
if let object = try? container.decode([String: JSON].self) {
|
||||||
|
self = .object(object)
|
||||||
|
} else if let array = try? container.decode([JSON].self) {
|
||||||
|
self = .array(array)
|
||||||
|
} else if let string = try? container.decode(String.self) {
|
||||||
|
self = .string(string)
|
||||||
|
} else if let bool = try? container.decode(Bool.self) {
|
||||||
|
self = .bool(bool)
|
||||||
|
} else if let number = try? container.decode(Double.self) {
|
||||||
|
self = .number(number)
|
||||||
|
} else if container.decodeNil() {
|
||||||
|
self = .null
|
||||||
|
} else {
|
||||||
|
throw DecodingError.dataCorrupted(
|
||||||
|
.init(codingPath: decoder.codingPath, debugDescription: "Invalid JSON value.")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JSON: CustomDebugStringConvertible {
|
||||||
|
|
||||||
|
public var debugDescription: String {
|
||||||
|
switch self {
|
||||||
|
case .string(let str):
|
||||||
|
return str.debugDescription
|
||||||
|
case .number(let num):
|
||||||
|
return num.debugDescription
|
||||||
|
case .bool(let bool):
|
||||||
|
return bool.description
|
||||||
|
case .null:
|
||||||
|
return "null"
|
||||||
|
default:
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
encoder.outputFormatting = [.prettyPrinted]
|
||||||
|
return try! String(data: encoder.encode(self), encoding: .utf8)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JSON: Hashable {}
|
||||||
41
Shared/ThirdParty/GenericJSON/Merging.swift
vendored
Normal file
41
Shared/ThirdParty/GenericJSON/Merging.swift
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension JSON {
|
||||||
|
|
||||||
|
/// Return a new JSON value by merging two other ones
|
||||||
|
///
|
||||||
|
/// If we call the current JSON value `old` and the incoming JSON value
|
||||||
|
/// `new`, the precise merging rules are:
|
||||||
|
///
|
||||||
|
/// 1. If `old` or `new` are anything but an object, return `new`.
|
||||||
|
/// 2. If both `old` and `new` are objects, create a merged object like this:
|
||||||
|
/// 1. Add keys from `old` not present in `new` (“no change” case).
|
||||||
|
/// 2. Add keys from `new` not present in `old` (“create” case).
|
||||||
|
/// 3. For keys present in both `old` and `new`, apply merge recursively to their values (“update” case).
|
||||||
|
public func merging(with new: JSON) -> JSON {
|
||||||
|
|
||||||
|
// If old or new are anything but an object, return new.
|
||||||
|
guard case .object(let lhs) = self, case .object(let rhs) = new else {
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
|
||||||
|
var merged: [String: JSON] = [:]
|
||||||
|
|
||||||
|
// Add keys from old not present in new (“no change” case).
|
||||||
|
for (key, val) in lhs where rhs[key] == nil {
|
||||||
|
merged[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add keys from new not present in old (“create” case).
|
||||||
|
for (key, val) in rhs where lhs[key] == nil {
|
||||||
|
merged[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// For keys present in both old and new, apply merge recursively to their values.
|
||||||
|
for key in lhs.keys where rhs[key] != nil {
|
||||||
|
merged[key] = lhs[key]?.merging(with: rhs[key]!)
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.object(merged)
|
||||||
|
}
|
||||||
|
}
|
||||||
107
Shared/ThirdParty/GenericJSON/Querying.swift
vendored
Normal file
107
Shared/ThirdParty/GenericJSON/Querying.swift
vendored
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public extension JSON {
|
||||||
|
|
||||||
|
/// Return the string value if this is a `.string`, otherwise `nil`
|
||||||
|
var stringValue: String? {
|
||||||
|
if case .string(let value) = self {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the double value if this is a `.number`, otherwise `nil`
|
||||||
|
var doubleValue: Double? {
|
||||||
|
if case .number(let value) = self {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the bool value if this is a `.bool`, otherwise `nil`
|
||||||
|
var boolValue: Bool? {
|
||||||
|
if case .bool(let value) = self {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the object value if this is an `.object`, otherwise `nil`
|
||||||
|
var objectValue: [String: JSON]? {
|
||||||
|
if case .object(let value) = self {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the array value if this is an `.array`, otherwise `nil`
|
||||||
|
var arrayValue: [JSON]? {
|
||||||
|
if case .array(let value) = self {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` iff this is `.null`
|
||||||
|
var isNull: Bool {
|
||||||
|
if case .null = self {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is an `.array`, return item at index
|
||||||
|
///
|
||||||
|
/// If this is not an `.array` or the index is out of bounds, returns `nil`.
|
||||||
|
subscript(index: Int) -> JSON? {
|
||||||
|
if case .array(let arr) = self, arr.indices.contains(index) {
|
||||||
|
return arr[index]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is an `.object`, return item at key
|
||||||
|
subscript(key: String) -> JSON? {
|
||||||
|
if case .object(let dict) = self {
|
||||||
|
return dict[key]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dynamic member lookup sugar for string subscripts
|
||||||
|
///
|
||||||
|
/// This lets you write `json.foo` instead of `json["foo"]`.
|
||||||
|
subscript(dynamicMember member: String) -> JSON? {
|
||||||
|
return self[member]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the JSON type at the keypath if this is an `.object`, otherwise `nil`
|
||||||
|
///
|
||||||
|
/// This lets you write `json[keyPath: "foo.bar.jar"]`.
|
||||||
|
subscript(keyPath keyPath: String) -> JSON? {
|
||||||
|
return queryKeyPath(keyPath.components(separatedBy: "."))
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryKeyPath<T>(_ path: T) -> JSON? where T: Collection, T.Element == String {
|
||||||
|
|
||||||
|
// Only object values may be subscripted
|
||||||
|
guard case .object(let object) = self else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is the path non-empty?
|
||||||
|
guard let head = path.first else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we have a value at the required key?
|
||||||
|
guard let value = object[head] else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let tail = path.dropFirst()
|
||||||
|
|
||||||
|
return tail.isEmpty ? value : value.queryKeyPath(tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -53,6 +53,9 @@ public class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (data, response) = try await self.session.data(for: request)
|
let (data, response) = try await self.session.data(for: request)
|
||||||
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
|
throw self.genError("non-HTTP response received", suggestion: "")
|
||||||
|
}
|
||||||
|
|
||||||
// let str = String(data: data, encoding: .utf8)
|
// let str = String(data: data, encoding: .utf8)
|
||||||
// print("================================")
|
// print("================================")
|
||||||
@ -64,11 +67,15 @@ public class Api {
|
|||||||
// print("================================")
|
// print("================================")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let resp = try JSONDecoder().decode(Response<T>.self, from: data)
|
if data.count > 0 {
|
||||||
if resp.success {
|
let resp = try JSONDecoder().decode(Response<T>.self, from: data)
|
||||||
return resp.data!
|
if resp.success {
|
||||||
|
return resp.data!
|
||||||
|
} else {
|
||||||
|
throw ApiError(httpStatus: httpResponse.statusCode, message: resp.error, code: resp.errorCode)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw self.genError(resp.error!, suggestion: "")
|
throw ApiError(httpStatus: httpResponse.statusCode)
|
||||||
}
|
}
|
||||||
} catch let error as Swift.DecodingError {
|
} catch let error as Swift.DecodingError {
|
||||||
throw CocoaError.error((error as CustomDebugStringConvertible).debugDescription)
|
throw CocoaError.error((error as CustomDebugStringConvertible).debugDescription)
|
||||||
|
|||||||
35
Shared/Utils/ApiError.swift
Normal file
35
Shared/Utils/ApiError.swift
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum ApiErrorCode: Int, CustomStringConvertible, Codable {
|
||||||
|
case invalidLoginOrPassword = 0
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
switch self {
|
||||||
|
case .invalidLoginOrPassword: return "Invalid login or password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ApiError: LocalizedError {
|
||||||
|
public let httpStatus: Int
|
||||||
|
public let message: String?
|
||||||
|
public let code: ApiErrorCode?
|
||||||
|
|
||||||
|
init(httpStatus: Int, message: String? = nil, code: ApiErrorCode? = nil) {
|
||||||
|
self.httpStatus = httpStatus
|
||||||
|
self.message = message
|
||||||
|
self.code = code
|
||||||
|
}
|
||||||
|
|
||||||
|
public var errorDescription: String? {
|
||||||
|
if let code = code {
|
||||||
|
return code.description
|
||||||
|
}
|
||||||
|
|
||||||
|
if let message = message {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
return "General http error (status \(self.httpStatus))"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user