Skip to content
This repository was archived by the owner on Feb 17, 2026. It is now read-only.

Commit efc52d3

Browse files
Aleksei ZelentsovAleksei Zelentsov
authored andcommitted
add methods
0 parents  commit efc52d3

26 files changed

Lines changed: 1408 additions & 0 deletions

.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>SchemeUserState</key>
6+
<dict>
7+
<key>Networking.xcscheme_^#shared#^_</key>
8+
<dict>
9+
<key>orderHint</key>
10+
<integer>1</integer>
11+
</dict>
12+
<key>SmsHubApi.xcscheme_^#shared#^_</key>
13+
<dict>
14+
<key>orderHint</key>
15+
<integer>0</integer>
16+
</dict>
17+
</dict>
18+
<key>SuppressBuildableAutocreation</key>
19+
<dict>
20+
<key>SmsHubApi</key>
21+
<dict>
22+
<key>primary</key>
23+
<true/>
24+
</dict>
25+
<key>SmsHubApiTests</key>
26+
<dict>
27+
<key>primary</key>
28+
<true/>
29+
</dict>
30+
</dict>
31+
</dict>
32+
</plist>

Package.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
// swift-tools-version: 5.6
3+
// The swift-tools-version declares the minimum version of Swift required to build this package.
4+
5+
import PackageDescription
6+
7+
let package = Package(
8+
name: "SmsHubApi",
9+
platforms: [
10+
// This package supports iOS 15 and later.
11+
.iOS(.v15),
12+
], products: [
13+
// Products define the executables and libraries a package produces, and make them visible to other packages.
14+
.library(
15+
name: "SmsHubApi",
16+
targets: ["SmsHubApi"]),
17+
],
18+
dependencies: [
19+
// Dependencies declare other packages that this package depends on.
20+
// .package(url: /* package url */, from: "1.0.0"),
21+
],
22+
targets: [
23+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
24+
// Targets can depend on other targets in this package, and on products in packages this package depends on.
25+
.target(
26+
name: "SmsHubApi",
27+
dependencies: []),
28+
.testTarget(
29+
name: "SmsHubApiTests",
30+
dependencies: ["SmsHubApi"]),
31+
]
32+
)

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
# Networking
3+
Networking is a network layer for the application. It allows communication with the server and handles all network requests for the app. It is implemented using the Swift Package Manager and can be easily integrated into any project. With Networking, you can make HTTP requests, handle responses and errors, and parse JSON data.
4+
5+
To use Networking, simply add it as a dependency in your Package.swift file and import it wherever you need to make network requests. The API is easy to use and fully documented within the codebase.
6+
7+
Networking is constantly being updated and improved to provide the best experience for developers. Give it a try and streamline your networking code in your next project!
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Alexey on 20.03.2023.
6+
//
7+
8+
import Foundation
9+
10+
public protocol ISmsHubAPI: AnyObject {
11+
/// Retrieves the account balance.
12+
func getBalance() async throws -> String
13+
14+
/// Buys a phone number with specified parameters.
15+
/// - Parameter getNumber: A GetNumberRequest object containing the service, operator, and country information.
16+
/// - Return: ID, Phone
17+
func purchasePhoneNumber(by getNumber: GetNumberRequest) async throws -> (Int, Int)
18+
19+
/// Sets the status of an activation.
20+
/// - Parameters:
21+
/// - id: The activation ID received after the request was made buyNumber
22+
/// - status: The status to set for the activation.
23+
func setStatus(id: Int, status: ActivationStatus) async throws -> SetStatusResponse
24+
25+
/// Retrieves the status of an activation.
26+
/// - Parameter id: The activation ID received after the request was made buyNumber
27+
func getStatus(id: Int) async throws -> (GetStatusResponse, String?)
28+
29+
}
30+
31+
public final class SmsHubAPI: HTTPClient, ISmsHubAPI {
32+
public init(apiKey: String) {
33+
Constants.apiKey = apiKey
34+
}
35+
36+
public func getBalance() async throws -> String {
37+
let endpoint = SmsHubEndpoint.getBalance
38+
let result = await sendRequest(endpoint: endpoint, responseModel: String.self)
39+
let response = try result.get()
40+
if let balance = response.components(separatedBy: ":").last {
41+
return balance
42+
}
43+
throw SMSHubError.badAnswer
44+
}
45+
46+
public func purchasePhoneNumber(by getNumber: GetNumberRequest) async throws -> (Int, Int) {
47+
let endpoint = SmsHubEndpoint.purchasePhoneNumber(getNumber)
48+
let result = await sendRequest(endpoint: endpoint, responseModel: String.self)
49+
let response = try result.get()
50+
let components = response.components(separatedBy: ":")
51+
if components.count == 3,
52+
let id = components[1].toInt(),
53+
let phone = components[2].toInt() {
54+
return (id, phone)
55+
}
56+
throw SMSHubError.badAnswer
57+
}
58+
59+
public func setStatus(id: Int, status: ActivationStatus) async throws -> SetStatusResponse {
60+
let endpoint = SmsHubEndpoint.setStatus(id: id, status: status)
61+
let result = await sendRequest(endpoint: endpoint, responseModel: String.self)
62+
let response = try result.get()
63+
if let status = SetStatusResponse(rawValue: response) {
64+
return status
65+
}
66+
throw SMSHubError.badAnswer
67+
}
68+
69+
public func getStatus(id: Int) async throws -> (GetStatusResponse, String?) {
70+
let endpoint = SmsHubEndpoint.getStatus(id: id)
71+
let result = await sendRequest(endpoint: endpoint, responseModel: String.self)
72+
let response = try result.get()
73+
let components = response.components(separatedBy: ":")
74+
if components.count == 2,
75+
let status = GetStatusResponse(rawValue: components[0]) {
76+
let code: String = components[1]
77+
return (status, code)
78+
} else if components.count == 1,
79+
let status = GetStatusResponse(rawValue: components[0]) {
80+
return (status, nil)
81+
}
82+
throw SMSHubError.badAnswer
83+
}
84+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
import Foundation
3+
4+
public enum Constants {
5+
public static let baseScheme: String = "https"
6+
public static let baseHost: String = "smshub.org/stubs/handler_api.php"
7+
public static var apiKey: String = ""
8+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
2+
import Foundation
3+
4+
public protocol Endpoint {
5+
var url: URL? { get }
6+
var method: HTTPRequestMethods { get }
7+
var header: [String: String]? { get }
8+
var body: BodyInfo? { get }
9+
}
10+
11+
public protocol CustomEndpoint: Endpoint {
12+
var queryItems: [URLQueryItem]? { get }
13+
var path: String { get }
14+
}
15+
16+
public struct BodyInfo {
17+
private let bodyDict: [String: String]?
18+
private let bodyData: Data?
19+
20+
public var data: Data? {
21+
if let bodyDict = bodyDict {
22+
return try? JSONEncoder().encode(bodyDict)
23+
}
24+
if let bodyData = bodyData {
25+
return bodyData
26+
}
27+
return nil
28+
}
29+
30+
public init(bodyDict: [String: String]?) {
31+
self.bodyData = nil
32+
self.bodyDict = bodyDict
33+
}
34+
35+
public init(bodyData: Data?) {
36+
self.bodyDict = nil
37+
self.bodyData = bodyData
38+
}
39+
40+
public init<T: Encodable>(object: T) {
41+
self.bodyDict = nil
42+
self.bodyData = try? JSONEncoder().encode(object)
43+
}
44+
45+
/// If you need to initialize body with empty parameters
46+
public init() {
47+
self.bodyData = nil
48+
self.bodyDict = nil
49+
}
50+
51+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
2+
import Foundation
3+
4+
public protocol HTTPClient: AnyObject {
5+
func sendRequest<T: Decodable>(session: URLSession,
6+
endpoint: any Endpoint,
7+
responseModel: T.Type) async -> Result<T, HTTPRequestError>
8+
}
9+
10+
public extension HTTPClient {
11+
12+
func sendRequest<T: Decodable>(
13+
session: URLSession = .shared,
14+
endpoint: any Endpoint,
15+
responseModel: T.Type
16+
) async -> Result<T, HTTPRequestError> {
17+
guard let url = endpoint.url else {
18+
return .failure(.invalidURL)
19+
}
20+
var request: URLRequest = .init(url: url, timeoutInterval: 30)
21+
request.httpMethod = endpoint.method.rawValue
22+
request.allHTTPHeaderFields = endpoint.header
23+
request.httpBody = endpoint.body?.data
24+
25+
do {
26+
let (data, response) = try await URLSession.shared.data(for: request)
27+
return handlingDataTask(data: data,
28+
response: response,
29+
error: nil,
30+
responseModel: responseModel)
31+
} catch {
32+
return .failure(.request(localizedDiscription: error.localizedDescription))
33+
}
34+
}
35+
36+
/// A helper method that handles the response from a request.
37+
func handlingDataTask<T: Decodable>(
38+
data: Data?,
39+
response: URLResponse?,
40+
error: Error?,
41+
responseModel: T.Type
42+
) -> Result<T, HTTPRequestError> {
43+
if let error = error {
44+
return .failure(.request(localizedDiscription: error.localizedDescription))
45+
}
46+
guard let responseCode = (response as? HTTPURLResponse)?.statusCode else {
47+
return .failure(.noResponse)
48+
}
49+
50+
switch responseCode {
51+
case 200...299:
52+
if let emptyModel = EmptyResponse() as? T {
53+
return .success(emptyModel)
54+
}
55+
if responseModel is Data.Type {
56+
return .success(responseModel as! T)
57+
}
58+
if let decodeData = data?.decode(model: responseModel) {
59+
return .success(decodeData)
60+
} else if let data, responseModel is String.Type {
61+
let response = String(decoding: data, as: UTF8.self)
62+
let error = SMSHubError.allCases.first { $0.rawValue == response }
63+
if let error {
64+
return .failure(.request(localizedDiscription: error.errorDescription!))
65+
} else {
66+
return .success(response as! T)
67+
}
68+
} else {
69+
return .failure(.decode)
70+
}
71+
case 400:
72+
if let decodeData = data?.decode(model: ValidatorErrorResponse.self) {
73+
return .failure(.validator(error: decodeData))
74+
}
75+
return .failure(.unexpectedStatusCode(code: responseCode,
76+
localized: responseCode.localStatusCode))
77+
case 401: return .failure(.unauthorizate)
78+
default: return .failure(.unexpectedStatusCode(code: responseCode,
79+
localized: responseCode.localStatusCode))
80+
}
81+
}
82+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
2+
import Foundation
3+
4+
public struct ValidatorErrorResponse: Codable {
5+
public let code: Int
6+
public let desc: String
7+
}
8+
9+
/// Types of HTTP Request Errors
10+
public enum HTTPRequestError: Error {
11+
/// Model decoding error
12+
case decode
13+
/// URL validation error
14+
case invalidURL
15+
/// Error receiving response from server
16+
case noResponse
17+
/// Error getting data
18+
case request(localizedDiscription: String)
19+
/// End of current session error
20+
case unauthorizate
21+
/// Error for unexpected status codes
22+
case unexpectedStatusCode(code: Int, localized: String?)
23+
/// Server request processing error
24+
case defaultServerError(error: String)
25+
/// Loading data missing error
26+
case noBodyData
27+
/// Submitted data validation error
28+
case validator(error: ValidatorErrorResponse)
29+
/// A timeout occurs when an operation exceeds a set time limit
30+
case timeout
31+
/// Connection lost, occurs when an established network connection is broken
32+
case connectionLost
33+
/// No internet connection
34+
case notInternetConnection
35+
/// Unknown error
36+
case unknown
37+
}
38+
39+
extension HTTPRequestError: LocalizedError {
40+
public var errorDescription: String? {
41+
switch self {
42+
case .decode:
43+
return "Decoding error"
44+
case .invalidURL:
45+
return "Invalid URL"
46+
case .noResponse:
47+
return "No answer"
48+
case .request(let localizedDiscription):
49+
return localizedDiscription
50+
case .unauthorizate:
51+
return "Session ended"
52+
case .unexpectedStatusCode(let code, let local):
53+
return "unexpectedStatusCode: \(code) - " + (local ?? "")
54+
case .defaultServerError(error: let error):
55+
return "server error: " + error
56+
case .noBodyData:
57+
return "No data being transmitted"
58+
case .validator(let error):
59+
return "Validator error: \(error.desc)"
60+
case .unknown:
61+
return "Unknown error"
62+
case .timeout:
63+
return "Long connection wait"
64+
case .connectionLost:
65+
return "Connection lost"
66+
case .notInternetConnection:
67+
return "No internet connection"
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)