├── .gitignore ├── Dockerfile ├── LICENSE ├── Package.swift ├── README.md ├── SendGrid.podspec ├── Sources └── SendGrid │ ├── API │ ├── Models │ │ ├── Account │ │ │ └── Subuser.swift │ │ ├── Events │ │ │ ├── Block.swift │ │ │ ├── Bounce.swift │ │ │ ├── GlobalUnsubscribe.swift │ │ │ ├── InvalidEmail.swift │ │ │ └── SpamReport.swift │ │ └── Stats │ │ │ ├── Statistic.Metric.swift │ │ │ ├── Statistic.Sample.swift │ │ │ └── Statistic.swift │ └── V3 │ │ ├── Mail │ │ └── Send │ │ │ ├── ASM.swift │ │ │ ├── Address.swift │ │ │ ├── Attachment.swift │ │ │ ├── Content.swift │ │ │ ├── Email.Personalization.swift │ │ │ ├── Email.swift │ │ │ └── Settings │ │ │ ├── Mail │ │ │ ├── BCCSetting.swift │ │ │ ├── BypassListManagement.swift │ │ │ ├── Footer.swift │ │ │ ├── MailSettings.swift │ │ │ ├── SandboxMode.swift │ │ │ └── SpamChecker.swift │ │ │ └── Tracking │ │ │ ├── ClickTracking.swift │ │ │ ├── GoogleAnalytics.swift │ │ │ ├── OpenTracking.swift │ │ │ ├── SubscriptionTracking.swift │ │ │ └── TrackingSettings.swift │ │ ├── Stats │ │ ├── RetrieveCategoryStatistics.swift │ │ ├── RetrieveGlobalStatistics.swift │ │ └── Statistic.Subuser.swift │ │ ├── Subuser │ │ └── RetrieveSubusers.swift │ │ └── Suppression │ │ ├── Blocks │ │ ├── DeleteBlocks.swift │ │ └── RetrieveBlocks.swift │ │ ├── Bounces │ │ ├── DeleteBounces.swift │ │ └── RetrieveBounces.swift │ │ ├── Global Unsubscribes │ │ ├── AddGlobalUnsubscribes.swift │ │ ├── DeleteGlobalUnsubscribe.swift │ │ └── RetrieveGlobalUnsubscribes.swift │ │ ├── Invalid Emails │ │ ├── DeleteInvalidEmails.swift │ │ └── RetrieveInvalidEmails.swift │ │ ├── Spam Reports │ │ ├── DeleteSpamReports.swift │ │ └── RetrieveSpamReports.swift │ │ ├── SuppressionListDeleter.swift │ │ └── SuppressionListReader.swift │ ├── Classes │ ├── FormURLEncoder.swift │ ├── Request.swift │ └── Session.swift │ ├── Deprecations │ └── 2.0.0 │ │ ├── Deprecations.AutoEncodable.2.0.0.swift │ │ ├── Deprecations.Email.2.0.0.swift │ │ ├── Deprecations.JSONValue.2.0.0.swift │ │ ├── Deprecations.Pagination.2.0.0.swift │ │ ├── Deprecations.RateLimit.2.0.0.swift │ │ ├── Deprecations.Request.2.0.0.swift │ │ ├── Deprecations.Response.2.0.0.swift │ │ ├── Deprecations.Statistics.2.0.0.swift │ │ ├── Deprecations.Subuser.Get.2.0.0.swift │ │ └── Deprecations.Suppressions.2.0.0.swift │ ├── Enums │ ├── ContentDisposition.swift │ ├── HTTPMethod.swift │ ├── Statistic.Aggregation.swift │ └── Statistic.Dimension.swift │ ├── Errors │ ├── Exception+API.swift │ ├── Exception+Authentication.swift │ ├── Exception+ContentType.swift │ ├── Exception+Global.swift │ ├── Exception+Mail.swift │ ├── Exception+Request.swift │ ├── Exception+Session.swift │ ├── Exception+Statistic.swift │ └── Exception.swift │ ├── Extensions │ └── HTTPURLResponse+APIHeaders.swift │ ├── Protocols │ ├── EmailEventRepresentable.swift │ ├── EmailHeaderRepresentable.swift │ ├── Scheduling.swift │ └── Validatable.swift │ └── Structs │ ├── Authentication.swift │ ├── Constants │ └── Constants.swift │ ├── ContentType.swift │ ├── DecodingStrategy.swift │ ├── EncodingStrategy.swift │ ├── Page.swift │ ├── Pagination.swift │ ├── RateLimit.swift │ └── Validate.swift ├── Tests ├── LinuxMain.swift └── SendGridTests │ ├── API │ ├── Models │ │ └── Events │ │ │ ├── BlockTests.swift │ │ │ ├── BounceTests.swift │ │ │ ├── GlobalUnsubscribeTests.swift │ │ │ ├── InvalidEmailTests.swift │ │ │ └── SpamReportTests.swift │ └── V3 │ │ ├── Mail │ │ └── Send │ │ │ ├── ASMTests.swift │ │ │ ├── AddressTests.swift │ │ │ ├── AttachmentTests.swift │ │ │ ├── ContentTests.swift │ │ │ ├── EmailTests.swift │ │ │ ├── PersonalizationTests.swift │ │ │ ├── Settings │ │ │ ├── Mail │ │ │ │ ├── BCCSettingTests.swift │ │ │ │ ├── BypassListManagementTests.swift │ │ │ │ ├── FooterTests.swift │ │ │ │ ├── MailSettingsTests.swift │ │ │ │ ├── SandboxModeTests.swift │ │ │ │ └── SpamCheckerTests.swift │ │ │ └── Tracking │ │ │ │ ├── ClickTrackingTests.swift │ │ │ │ ├── GoogleAnalyticsTests.swift │ │ │ │ ├── OpenTrackingTests.swift │ │ │ │ ├── SubscriptionTrackingTests.swift │ │ │ │ └── TrackingSettingsTests.swift │ │ │ └── TemplatedPersonalizationTests.swift │ │ ├── Stats │ │ ├── RetrieveCategoryStatisticsTests.swift │ │ ├── RetrieveGlobalStatisticsTests.swift │ │ └── RetrieveSubuserStatisticsTests.swift │ │ ├── Subuser │ │ └── RetrieveSubusersTests.swift │ │ └── Suppression │ │ ├── Blocks │ │ ├── DeleteBlocksTests.swift │ │ └── RetrieveBlocks.swift │ │ ├── Bounces │ │ ├── DeleteBouncesTests.swift │ │ └── RetrieveBouncesTests.swift │ │ ├── Global Unsubscribes │ │ ├── AddGlobalUnsubscribesTests.swift │ │ ├── DeleteGlobalUnsubscribeTests.swift │ │ └── RetrieveGlobalUnsubscribesTests.swift │ │ ├── Invalid Emails │ │ ├── DeleteInvalidEmailsTests.swift │ │ └── RetrieveInvalidEmailsTests.swift │ │ ├── Spam Reports │ │ ├── DeleteSpamReportsTests.swift │ │ └── RetrieveSpamReportsTests.swift │ │ ├── SuppressionListDeleterTests.swift │ │ └── SuppressionListReaderTests.swift │ ├── Classes │ └── SessionTests.swift │ ├── Enums │ └── HTTPMethodTests.swift │ ├── Helpers │ ├── EncodingTester.swift │ ├── Exception.swift │ └── XCTFailUnknownError.swift │ ├── Structs │ ├── AuthenticationTests.swift │ ├── ContentTypeTests.swift │ ├── PaginationTests.swift │ ├── RateLimitTests.swift │ └── ValidateTests.swift │ └── XCTestManifests.swift └── docker-compose.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | /sandbox 6 | /build 7 | /docs -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM swift:5.1 2 | 3 | # Copy over source 4 | RUN mkdir -p /app 5 | WORKDIR /app 6 | 7 | COPY . /app 8 | 9 | # Build source 10 | CMD ["swift", "build"] 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Scott K. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SendGrid", 8 | products: [ 9 | .library( 10 | name: "SendGrid", 11 | targets: ["SendGrid"]), 12 | ], 13 | dependencies: [], 14 | targets: [ 15 | .target( 16 | name: "SendGrid", 17 | dependencies: []), 18 | .testTarget( 19 | name: "SendGridTests", 20 | dependencies: ["SendGrid"]), 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /SendGrid.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint SendGrid.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "SendGrid" 19 | s.version = "2.2.1" 20 | s.summary = "A library that allows you to easily make SendGrid V3 API calls." 21 | 22 | # This description is used to generate tags and improve search results. 23 | # * Think: What does it do? Why did you write it? What is the focus? 24 | # * Try to keep it short, snappy and to the point. 25 | # * Write the description between the DESC delimiters below. 26 | # * Finally, don't worry about the indent, CocoaPods strips it! 27 | s.description = <<-DESC 28 | A library that allows you to easily make API calls via SendGrid's V3 API. 29 | DESC 30 | 31 | s.homepage = "https://github.com/scottkawai/sendgrid-swift" 32 | 33 | 34 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 35 | # 36 | # Licensing your code is important. See http://choosealicense.com for more info. 37 | # CocoaPods will detect a license file if there is a named LICENSE* 38 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 39 | # 40 | 41 | s.license = { :type => "MIT", :file => "LICENSE" } 42 | 43 | 44 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 45 | # 46 | # Specify the authors of the library, with email addresses. Email addresses 47 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 48 | # accepts just a name if you'd rather not provide an email address. 49 | # 50 | # Specify a social_media_url where others can refer to, for example a twitter 51 | # profile URL. 52 | # 53 | 54 | s.author = "Scott K." 55 | 56 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 57 | # 58 | # If this Pod runs only on iOS or OS X, then specify the platform and 59 | # the deployment target. You can optionally include the target after the platform. 60 | # 61 | 62 | # When using multiple platforms 63 | s.ios.deployment_target = "8.0" 64 | s.osx.deployment_target = "10.10" 65 | # s.watchos.deployment_target = "2.0" 66 | # s.tvos.deployment_target = "9.0" 67 | 68 | 69 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 70 | # 71 | # Specify the location from where the source should be retrieved. 72 | # Supports git, hg, bzr, svn and HTTP. 73 | # 74 | 75 | s.source = { :git => "https://github.com/scottkawai/sendgrid-swift.git", :tag => s.version.to_s } 76 | 77 | 78 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 79 | # 80 | # CocoaPods is smart about how it includes source code. For source files 81 | # giving a folder will include any swift, h, m, mm, c & cpp files. 82 | # For header files it will include any header in the folder. 83 | # Not including the public_header_files will make all headers public. 84 | # 85 | 86 | s.source_files = "Sources/**/*.swift" 87 | # s.exclude_files = "Classes/Exclude" 88 | # s.public_header_files = "Classes/**/*.h" 89 | 90 | 91 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 92 | # 93 | # A list of resources included with the Pod. These are copied into the 94 | # target bundle with a build phase script. Anything else will be cleaned. 95 | # You can preserve files from being cleaned, please don't preserve 96 | # non-essential files like tests, examples and documentation. 97 | # 98 | 99 | # s.resource = "icon.png" 100 | # s.resources = "Resources/*.png" 101 | 102 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave" 103 | 104 | 105 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 106 | # 107 | # Link your library with frameworks, or libraries. Libraries do not include 108 | # the lib prefix of their name. 109 | # 110 | 111 | # s.framework = "SomeFramework" 112 | # s.frameworks = "SomeFramework", "AnotherFramework" 113 | 114 | # s.library = "iconv" 115 | # s.libraries = "iconv", "xml2" 116 | 117 | 118 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 119 | # 120 | # If your library depends on compiler flags you can set them in the xcconfig hash 121 | # where they will only apply to your library. If you depend on other Podspecs 122 | # you can include multiple dependencies to ensure it works. 123 | 124 | # s.requires_arc = true 125 | 126 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 127 | # s.dependency "JSONKit", "~> 1.4" 128 | 129 | end 130 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/Models/Account/Subuser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Subuser` struct represents a subuser on a parent account. 4 | public struct Subuser: Codable { 5 | // MARK: - Properties 6 | 7 | /// The unique ID of the subuser account. 8 | public let id: Int 9 | 10 | /// The subuser's username. 11 | public let username: String 12 | 13 | /// The email address on the subuser's profile. 14 | public let email: String 15 | 16 | /// A boolean indicating if the subuser is disabled or not. 17 | public let disabled: Bool 18 | 19 | // MARK: - Initialization 20 | 21 | /// Initializes the struct with an ID, username, email, and "disabled" flag. 22 | /// 23 | /// - Parameters: 24 | /// - id: The unique ID of the subuser account. 25 | /// - username: The subuser's username. 26 | /// - email: The email address on the subuser's profile. 27 | /// - disabled: A boolean indicating if the subuser is disabled or not. 28 | public init(id: Int, username: String, email: String, disabled: Bool) { 29 | self.id = id 30 | self.username = username 31 | self.email = email 32 | self.disabled = disabled 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/Models/Events/Block.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Block` struct represents a block event. 4 | public struct Block: EmailEventRepresentable, Codable { 5 | // MARK: - Properties 6 | 7 | /// The email address on the event. 8 | public let email: String 9 | 10 | /// The date and time the event occurred on. 11 | public let created: Date 12 | 13 | /// The response from the recipient server. 14 | public let reason: String 15 | 16 | /// The status code of the event. 17 | public let status: String 18 | 19 | // MARK: - Initialization 20 | 21 | /// Initializes the event with all the required properties. 22 | /// 23 | /// - Parameters: 24 | /// - email: The email address on the event. 25 | /// - created: The date and time the event occurred on. 26 | /// - reason: The response from the recipient server. 27 | /// - status: The status code of the event. 28 | public init(email: String, created: Date, reason: String, status: String) { 29 | self.email = email 30 | self.created = created 31 | self.reason = reason 32 | self.status = status 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/Models/Events/Bounce.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Bounce` struct represents a bounce event. 4 | public struct Bounce: EmailEventRepresentable, Codable { 5 | // MARK: - Properties 6 | 7 | /// The email address on the event. 8 | public let email: String 9 | 10 | /// The date and time the event occurred on. 11 | public let created: Date 12 | 13 | /// The response from the recipient server. 14 | public let reason: String 15 | 16 | /// The status code of the event. 17 | public let status: String 18 | 19 | // MARK: - Initialization 20 | 21 | /// Initializes the event with all the required properties. 22 | /// 23 | /// - Parameters: 24 | /// - email: The email address on the event. 25 | /// - created: The date and time the event occurred on. 26 | /// - reason: The response from the recipient server. 27 | /// - status: The status code of the event. 28 | public init(email: String, created: Date, reason: String, status: String) { 29 | self.email = email 30 | self.created = created 31 | self.reason = reason 32 | self.status = status 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/Models/Events/GlobalUnsubscribe.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `GlobalUnsubscribe` struct represents an entry on the "Global 4 | /// Unsubscribe" suppression list. 5 | public struct GlobalUnsubscribe: EmailEventRepresentable, Codable { 6 | // MARK: - Properties 7 | 8 | /// The email address on the event. 9 | public let email: String 10 | 11 | /// The date and time the event occurred on. 12 | public let created: Date 13 | 14 | // MARK: - Initialization 15 | 16 | /// Initializes the event with all the required properties. 17 | /// 18 | /// - Parameters: 19 | /// - email: The email address on the event. 20 | /// - created: The date and time the event occurred on. 21 | public init(email: String, created: Date) { 22 | self.email = email 23 | self.created = created 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/Models/Events/InvalidEmail.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `InvalidEmail` struct represents an entry on the "Invalid Email" 4 | /// suppression list. 5 | public struct InvalidEmail: EmailEventRepresentable, Codable { 6 | // MARK: - Properties 7 | 8 | /// The email address on the event. 9 | public let email: String 10 | 11 | /// The date and time the event occurred on. 12 | public let created: Date 13 | 14 | /// The description of why the email was classified as invalid. 15 | public let reason: String 16 | 17 | // MARK: - Initialization 18 | 19 | /// Initializes the event with all the required properties. 20 | /// 21 | /// - Parameters: 22 | /// - email: The email address on the event. 23 | /// - created: The date and time the event occurred on. 24 | /// - reason: The description of why the email was classified as 25 | /// invalid. 26 | public init(email: String, created: Date, reason: String) { 27 | self.email = email 28 | self.created = created 29 | self.reason = reason 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/Models/Events/SpamReport.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `SpamReport` struct represents a spam report event. 4 | public struct SpamReport: EmailEventRepresentable, Codable { 5 | // MARK: - Properties 6 | 7 | /// The email address on the event. 8 | public let email: String 9 | 10 | /// The date and time the event occurred on. 11 | public let created: Date 12 | 13 | /// The IP address of the user when they marked the email as spam. 14 | public let ip: String 15 | 16 | // MARK: - Initialization 17 | 18 | /// Initializes the event with all the required properties. 19 | /// 20 | /// - Parameters: 21 | /// - email: The email address on the event. 22 | /// - created: The date and time the event occurred on. 23 | /// - ip: The IP address of the user when they marked the email as 24 | /// spam. 25 | public init(email: String, created: Date, ip: String) { 26 | self.email = email 27 | self.created = created 28 | self.ip = ip 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/Models/Stats/Statistic.Sample.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Statistic { 4 | /// The `Statistic.Sample` struct represents a single group of statistics, 5 | /// with the raw stats being made available via the `metrics` property. 6 | struct Sample: Codable { 7 | // MARK: - Properties 8 | 9 | /// The raw metrics for each email event type. 10 | public let metrics: Statistic.Metric 11 | 12 | /// The name of the sample, if applicable. For instance, if these are 13 | /// category stats, then this property will have the name of the 14 | /// category. 15 | public let name: String? 16 | 17 | /// The dimension type these stats have been grouped by. 18 | public let type: Statistic.Dimension? 19 | 20 | // MARK: - Initialization 21 | 22 | /// Initializes the struct. 23 | /// 24 | /// - Parameters: 25 | /// - metrics: The raw metrics for each email event type. 26 | /// - name: The name of the sample (e.g. the name of the 27 | /// category). 28 | /// - type: The dimension type these stats have been grouped by. 29 | public init(metrics: Statistic.Metric, name: String? = nil, type: Statistic.Dimension? = nil) { 30 | self.metrics = metrics 31 | self.name = name 32 | self.type = type 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/Models/Stats/Statistic.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Statistic` struct represents the enclosing structure of statistics 4 | /// returning from the various stat API calls. It contains the 5 | /// date of the aggregated time period, along with the raw stats. 6 | public struct Statistic: Codable { 7 | // MARK: - Properties 8 | 9 | /// The date for this statistic set. 10 | public let date: Date 11 | 12 | /// The individual stat samples that make up this collection. 13 | public let stats: [Statistic.Sample] 14 | 15 | // MARK: - Initialization 16 | 17 | /// Initializes the struct. 18 | /// 19 | /// - Parameters: 20 | /// - date: The date for this statistic set. 21 | /// - stats: The individual stat samples that make up this collection. 22 | public init(date: Date, stats: [Statistic.Sample]) { 23 | self.date = date 24 | self.stats = stats 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/ASM.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `ASM` class is used to specify an unsubscribe group to associate the 4 | /// email with. 5 | public struct ASM: Encodable { 6 | // MARK: - Properties 7 | 8 | /// The ID of the unsubscribe group to use. 9 | public let id: Int 10 | 11 | /// A list of IDs that should be shown on the "Manage Subscription" page for 12 | /// this email. 13 | public let groupsToDisplay: [Int]? 14 | 15 | // MARK: - Initialization 16 | 17 | /// Initializes the struct with a group ID and a list of group IDs to 18 | /// display on the manage subscription page for the email. 19 | /// 20 | /// - Parameters: 21 | /// - groupID: The ID of the unsubscribe group to use. 22 | /// - groupsToDisplay: An array of integers representing the IDs of 23 | /// other unsubscribe groups to display on the 24 | /// subscription page. 25 | public init(groupID: Int, groupsToDisplay: [Int]? = nil) { 26 | self.id = groupID 27 | self.groupsToDisplay = groupsToDisplay 28 | } 29 | } 30 | 31 | extension ASM /* Encodable Conformance */ { 32 | /// :nodoc: 33 | public enum CodingKeys: String, CodingKey { 34 | case id = "group_id" 35 | case groupsToDisplay = "groups_to_display" 36 | } 37 | } 38 | 39 | extension ASM: Validatable { 40 | /// Validates that there are no more than 25 groups to display. 41 | public func validate() throws { 42 | if let display = self.groupsToDisplay { 43 | guard display.count <= Constants.UnsubscribeGroups.MaximumNumberOfDisplayGroups else { 44 | throw Exception.Mail.tooManyUnsubscribeGroups 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Address.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Address` struct represents an email address and contains the email 4 | /// address along with an optional display name. 5 | public struct Address: Encodable { 6 | // MARK: - Properties 7 | 8 | /// An optional name to display instead of the email address. 9 | public let name: String? 10 | 11 | /// An email address. 12 | public let email: String 13 | 14 | // MARK: - Initialization 15 | 16 | /// Initializes the address with an email address and an optional display name. 17 | /// 18 | /// - Parameters: 19 | /// - email: The email address. 20 | /// - name: An optional display name. 21 | public init(email: String, name: String? = nil) { 22 | self.email = email 23 | self.name = name 24 | } 25 | } 26 | 27 | extension Address: Validatable { 28 | /// Validates that the email address is an RFC compliant email address. 29 | public func validate() throws { 30 | guard Validate.email(self.email) else { 31 | throw Exception.Mail.malformedEmailAddress(self.email) 32 | } 33 | } 34 | } 35 | 36 | extension Address: ExpressibleByStringLiteral /* Allow initialization from a raw `String` */ { 37 | /// This initializer allows you to create an `Address` instance from a 38 | /// `String`: 39 | /// 40 | /// ``` 41 | /// let address: Address = "foo@bar.none" 42 | /// ``` 43 | /// 44 | /// - Parameter value: The email address to use in the `Address`. 45 | public init(stringLiteral value: String) { 46 | self.init(email: value) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Attachment.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Attachment` class represents a file to attach to an email. 4 | open class Attachment: Encodable { 5 | // MARK: - Properties 6 | 7 | /// The content, or data, of the attachment. 8 | public let content: Data 9 | 10 | /// The filename of the attachment. 11 | public let filename: String 12 | 13 | /// The content (or MIME) type of the attachment. 14 | public let type: ContentType? 15 | 16 | /// The content-disposition of the attachment specifying how you would like 17 | /// the attachment to be displayed. For example, "inline" results in the 18 | /// attached file being displayed automatically within the message while 19 | /// "attachment" results in the attached file requiring some action to be 20 | /// taken before it is displayed (e.g. opening or downloading the file). 21 | public let disposition: ContentDisposition 22 | 23 | /// A unique id that you specify for the attachment. This is used when the 24 | /// disposition is set to "inline" and the attachment is an image, allowing 25 | /// the file to be displayed within the body of your email. Ex: 26 | /// `` 27 | public let contentID: String? 28 | 29 | // MARK: - Initialization 30 | 31 | /// Initializes the attachment. 32 | /// 33 | /// - Parameters: 34 | /// - filename: The filename of the attachment. 35 | /// - content: The data of the attachment. 36 | /// - disposition: The content-disposition of the attachment (defaults 37 | /// to `ContentDisposition.Attachment`). 38 | /// - type: The content-type of the attachment. 39 | /// - contentID: The CID of the attachment, used to show the 40 | /// attachments inline with the body of the email. 41 | public init(filename: String, content: Data, disposition: ContentDisposition = .attachment, type: ContentType? = nil, contentID: String? = nil) { 42 | self.filename = filename 43 | self.content = content 44 | self.disposition = disposition 45 | self.type = type 46 | self.contentID = contentID 47 | } 48 | } 49 | 50 | extension Attachment /* Encodable Conformance */ { 51 | /// :nodoc: 52 | public enum CodingKeys: String, CodingKey { 53 | case content 54 | case filename 55 | case type 56 | case disposition 57 | case contentID = "content_id" 58 | } 59 | } 60 | 61 | extension Attachment: Validatable { 62 | /// Validates that the content type of the attachment is correct. 63 | open func validate() throws { 64 | try self.type?.validate() 65 | 66 | if let id = self.contentID { 67 | guard Validate.noCLRF(in: id) else { throw Exception.Mail.invalidContentID(id) } 68 | } 69 | 70 | guard Validate.noCLRF(in: self.filename) else { 71 | throw Exception.Mail.invalidFilename(self.filename) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Content.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Content` class represents a MIME part of the email message (i.e. the 4 | /// plain text and HTML parts of an email). 5 | public struct Content: Encodable { 6 | // MARK: - Properties 7 | 8 | /// The content type of the content. 9 | public let type: ContentType 10 | 11 | /// The value of the content. 12 | public let value: String 13 | 14 | // MARK: - Initialization 15 | 16 | /// Initializes the content with a content type and value. 17 | /// 18 | /// - Parameters: 19 | /// - contentType: The content type. 20 | /// - aValue: The value of the content. 21 | public init(contentType: ContentType, value aValue: String) { 22 | self.type = contentType 23 | self.value = aValue 24 | } 25 | } 26 | 27 | extension Content: Validatable { 28 | /// Validates the content. 29 | public func validate() throws { 30 | guard self.value.count > 0 else { 31 | throw Exception.Mail.contentHasEmptyString 32 | } 33 | try self.type.validate() 34 | } 35 | } 36 | 37 | extension Content /* Convenience class initializers */ { 38 | /// Creates a new `Content` instance used to represent a plain text body. 39 | /// 40 | /// - Parameter value: The plain text value of the body. 41 | /// - Returns: A `Content` instance with the "text/plain" content 42 | /// type. 43 | public static func plainText(body value: String) -> Content { 44 | Content(contentType: .plainText, value: value) 45 | } 46 | 47 | /// Creates a new `Content` instance used to represent an HTML body. 48 | /// 49 | /// - Parameter value: The HTML text value of the body. 50 | /// - Returns: A `Content` instance with the "text/html" content 51 | /// type. 52 | public static func html(body value: String) -> Content { 53 | Content(contentType: .htmlText, value: value) 54 | } 55 | 56 | /// Return an array containing a plain text and html body. 57 | /// 58 | /// - Parameters: 59 | /// - plain: The text value for the plain text body. 60 | /// - html: The HTML text value for the HTML body. 61 | /// - Returns: An array of `Content` instances. 62 | public static func emailBody(plain: String, html: String) -> [Content] { 63 | [ 64 | Content.plainText(body: plain), 65 | Content.html(body: html) 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Settings/Mail/BCCSetting.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This allows you to have a blind carbon copy automatically sent to the 4 | /// specified email address for every email that is sent. 5 | public struct BCCSetting: Encodable { 6 | // MARK: - Properties 7 | 8 | /// A bool indicating if the setting should be toggled on or off. 9 | public let enable: Bool 10 | 11 | /// The email address that you would like to receive the BCC. 12 | public let email: String? 13 | 14 | // MARK: - Initialization 15 | 16 | /// Initializes the setting with an email to use as the BCC address. This 17 | /// setting can also be used to turn off the BCC app if it is normally on by 18 | /// default on your SendGrid account. To turn off the setting for this 19 | /// specific email, pass in `nil` as the email address (`nil` is the default 20 | /// value as well, so if you want to turn off the setting you can initialize 21 | /// the setting with no parameters, i.e. `BCCSetting()`). 22 | /// 23 | /// - Parameters: 24 | /// - email: A `String` representing the email address to set as the BCC. 25 | public init(email: String? = nil) { 26 | self.email = email 27 | self.enable = email != nil 28 | } 29 | 30 | /// Initializes the setting with an email to use as the BCC address. 31 | /// 32 | /// - Parameters: 33 | /// - email: An `Address` instance to set as the BCC. 34 | public init(address: Address) { 35 | self.init(email: address.email) 36 | } 37 | } 38 | 39 | extension BCCSetting: Validatable { 40 | /// Validates that the BCC email is a valid email address. 41 | public func validate() throws { 42 | if let em = self.email { 43 | guard Validate.email(em) else { 44 | throw Exception.Mail.malformedEmailAddress(em) 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Settings/Mail/BypassListManagement.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `BypassListManagement` class allows you to bypass all unsubscribe groups 4 | /// and suppressions to ensure that the email is attempted to be delivered to 5 | /// every single recipient. This should only be used in emergencies when it is 6 | /// absolutely necessary that every recipient receives your email. Ex: outage 7 | /// emails, or forgot password emails. 8 | public struct BypassListManagement: Encodable { 9 | // MARK: - Properties 10 | 11 | /// A bool indicating if the setting should be toggled on or off. 12 | public let enable: Bool 13 | 14 | // MARK: - Initialization 15 | 16 | /// Initializes the setting with a flag indicating if its enabled or not. 17 | /// 18 | /// - Parameter enable: A bool indicating if the setting should be toggle on 19 | /// or off (default is `true`). 20 | public init(enable: Bool = true) { 21 | self.enable = enable 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Settings/Mail/Footer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Footer` mail setting allows you to specify a footer that will be 4 | /// appended to the bottom of every email. 5 | public struct Footer: Encodable { 6 | // MARK: - Properties 7 | 8 | /// The plain text content of your footer. 9 | public let text: String? 10 | 11 | /// The HTML content of your footer. 12 | public let html: String? 13 | 14 | /// A `Bool` indicating if the setting is enabled or not. 15 | public let enable: Bool 16 | 17 | // MARK: - Initialization 18 | 19 | /// Initializes the setting with plain text and HTML to use in the footer. 20 | /// This will enable the setting for this email and use the provided 21 | /// content. 22 | /// 23 | /// If the footer setting is enabled by default on your SendGrid account and 24 | /// you want to disable it for this email, use the `init()` initializer 25 | /// instead. 26 | /// 27 | /// - Parameters: 28 | /// - text: The plain text content of your footer. 29 | /// - html: The HTML content of your footer. 30 | public init(text: String, html: String) { 31 | self.text = text 32 | self.html = html 33 | self.enable = true 34 | } 35 | 36 | /// Initializes the setting with no templates, indicating that the footer 37 | /// setting should be disabled for this particular email (assuming it's 38 | /// normally enabled on the SendGrid account). 39 | /// 40 | /// If you want to enable the footer setting, use the `init(text:html:)` 41 | /// initializer instead. 42 | public init() { 43 | self.text = nil 44 | self.html = nil 45 | self.enable = false 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Settings/Mail/MailSettings.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `MailSetting` struct houses any mail settings an email should be 4 | /// configured with. 5 | public struct MailSettings: Encodable { 6 | // MARK: - Properties 7 | 8 | /// The BCC setting. 9 | public var bcc: BCCSetting? 10 | 11 | /// The bypass list management setting. 12 | public var bypassListManagement: BypassListManagement? 13 | 14 | /// The footer setting. 15 | public var footer: Footer? 16 | 17 | /// The sandbox mode setting. 18 | public var sandboxMode: SandboxMode? 19 | 20 | /// The spam checker setting. 21 | public var spamCheck: SpamChecker? 22 | 23 | /// A `Bool` indicating if at least one of the settings have been specified. 24 | public var hasSettings: Bool { 25 | return self.bcc != nil || 26 | self.bypassListManagement != nil || 27 | self.footer != nil || 28 | self.sandboxMode != nil || 29 | self.spamCheck != nil 30 | } 31 | 32 | // MARK: - Initialization 33 | 34 | /// Initializes the struct with no settings set. 35 | public init() {} 36 | } 37 | 38 | public extension MailSettings /* Encodable conformance */ { 39 | /// :nodoc: 40 | enum CodingKeys: String, CodingKey { 41 | case bcc 42 | case bypassListManagement = "bypass_list_management" 43 | case footer 44 | case sandboxMode = "sandbox_mode" 45 | case spamCheck = "spam_check" 46 | } 47 | } 48 | 49 | extension MailSettings: Validatable { 50 | /// Bubbles up the validations for `bcc` and `spamCheck`. 51 | public func validate() throws { 52 | try self.bcc?.validate() 53 | try self.spamCheck?.validate() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Settings/Mail/SandboxMode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This allows you to send a test email to ensure that your request body is 4 | /// valid and formatted correctly. For more information, please see the 5 | /// [SendGrid docs](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/sandbox_mode.html). 6 | public struct SandboxMode: Encodable { 7 | // MARK: - Properties 8 | 9 | /// A bool indicating if the setting should be toggled on or off. 10 | public let enable: Bool 11 | 12 | // MARK: - Initialization 13 | 14 | /// Initializes the setting with a flag indicating if its enabled or not. 15 | /// 16 | /// - Parameter enable: A bool indicating if the setting should be toggle on 17 | /// or off (default is `true`). 18 | public init(enable: Bool = true) { 19 | self.enable = enable 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Settings/Mail/SpamChecker.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `SpamChecker` mail setting allows you to test the content of your email 4 | /// for spam. 5 | public struct SpamChecker: Encodable { 6 | // MARK: - Properties 7 | 8 | /// The threshold used to determine if your content qualifies as spam on a 9 | /// scale from 1 to 10, with 10 being most strict, or most likely to be 10 | /// considered as spam. 11 | public let threshold: Int? 12 | 13 | /// A webhook URL that you would like a copy of your email along with the 14 | /// spam report to be POSTed to. 15 | public let postURL: URL? 16 | 17 | /// A `Bool` indicating if the setting is enabled or not. 18 | public let enable: Bool 19 | 20 | // MARK: - Initialization 21 | 22 | /// Initializes the setting with a threshold and optional URL to POST spam 23 | /// reports to. This will enable the setting for this specific email even if 24 | /// it's normally disabled on your SendGrid account. 25 | /// 26 | /// If the spam checker setting is enabled by default on your SendGrid 27 | /// account and you want to disable it for this specific email, then use the 28 | /// `init()` initializer instead. 29 | /// 30 | /// - Parameters: 31 | /// - threshold: An integer used to determine if your content qualifies 32 | /// as spam on a scale from 1 to 10, with 10 being most 33 | /// strict, or most likely to be considered as spam. 34 | /// - url: A webhook URL that you would like a copy of your email 35 | /// along with the spam report to be POSTed to. 36 | public init(threshold: Int, url: URL? = nil) { 37 | self.threshold = threshold 38 | self.postURL = url 39 | self.enable = true 40 | } 41 | 42 | /// Initializes the setting with no threshold, indicating that the spam 43 | /// checker setting should be disabled for this particular email (assuming 44 | /// it's normally enabled on the SendGrid account). 45 | /// 46 | /// If you want to enable the spam checker setting, use the 47 | /// `init(threshold:url:)` initializer instead. 48 | public init() { 49 | self.threshold = nil 50 | self.postURL = nil 51 | self.enable = false 52 | } 53 | } 54 | 55 | public extension SpamChecker /* Encodable conformance */ { 56 | /// :nodoc: 57 | enum CodingKeys: String, CodingKey { 58 | case enable 59 | case threshold 60 | case postURL = "post_to_url" 61 | } 62 | } 63 | 64 | extension SpamChecker: Validatable { 65 | /// Validates that the threshold is within the correct range. 66 | public func validate() throws { 67 | if let level = self.threshold { 68 | guard 1...10 ~= level else { throw Exception.Mail.thresholdOutOfRange(level) } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Settings/Tracking/ClickTracking.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `ClickTracking` class is used to adjust the click tracking setting. 4 | public struct ClickTracking: Encodable { 5 | // MARK: - Properties 6 | 7 | /// A bool indicating if the setting should be toggled on or off. 8 | public let enable: Bool 9 | 10 | /// A boolean indicating if click tracking should also be applied to links 11 | /// inside a plain text email. 12 | public let enableText: Bool? 13 | 14 | // MARK: - Initialization 15 | 16 | /// Initializes the setting encoding a specified section of the email. 17 | /// 18 | /// In general, you should use the `.htmlBody` case, as only the HTML body 19 | /// of an email can have clickable links. 20 | /// 21 | /// If, for some reason, you want the URLs in the plain text body of an 22 | /// email to be tracked as well, you should use the 23 | /// `.plainTextAndHTMLBodies` case instead. **Be aware**, though, that 24 | /// having both sections tracked will result in long URLs showing up in the 25 | /// plain text body. 26 | /// 27 | /// If click tracking is normally enabled on your SendGrid account and you 28 | /// want to disable it for this particular email, use the `.off` case. 29 | /// 30 | /// - Parameter section: The section to track links in, or `.off` to 31 | /// disable the setting. 32 | public init(section: ClickTracking.Section) { 33 | switch section { 34 | case .off: 35 | self.enable = false 36 | self.enableText = nil 37 | case .htmlBody: 38 | self.enable = true 39 | self.enableText = false 40 | case .plainTextAndHTMLBodies: 41 | self.enable = true 42 | self.enableText = true 43 | } 44 | } 45 | } 46 | 47 | public extension ClickTracking /* Encodable conformance */ { 48 | /// :nodoc: 49 | enum CodingKeys: String, CodingKey { 50 | case enable 51 | case enableText = "enable_text" 52 | } 53 | } 54 | 55 | public extension ClickTracking /* Tracking sections */ { 56 | /// This enum represents the sections of an email that click tracking should 57 | /// be applied to. In general, you should use the `.htmlBody` case, as only 58 | /// the HTML body of an email can have clickable links. 59 | /// 60 | /// If, for some reason, you want the URLs in the plain text body of an 61 | /// email to be tracked as well, you should use the 62 | /// `.plainTextAndHTMLBodies` case instead. **Be aware**, though, that 63 | /// having both sections tracked will result in long URLs showing up in the 64 | /// plain text body. 65 | /// 66 | /// If click tracking is normally enabled on your SendGrid account and you 67 | /// want to disable it for this particular email, use the `.off` case. 68 | /// 69 | /// - off: If used, the setting will be disabled for 70 | /// this email. 71 | /// - htmlBody: If used, links in the HTML body of the email 72 | /// will be encoded and tracked. 73 | /// - plainTextAndHTMLBodies: If used, links in the HTML body and URLs in 74 | /// the plain text body will be encoded and 75 | /// tracked. 76 | enum Section { 77 | /// If used, the setting will be disabled for this email. 78 | case off 79 | 80 | /// If used, links in the HTML body of the email will be encoded and 81 | /// tracked. 82 | case htmlBody 83 | 84 | /// If used, links in the HTML body and URLs in the plain text body will 85 | /// be encoded and tracked. 86 | case plainTextAndHTMLBodies 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Settings/Tracking/GoogleAnalytics.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `GoogleAnalytics` class is used to toggle the Google Analytics setting, 4 | /// which adds GA parameters to links in the email. 5 | public struct GoogleAnalytics: Encodable { 6 | // MARK: - Properties 7 | 8 | /// Name of the referrer source. (e.g. Google, SomeDomain.com, or Marketing 9 | /// Email) 10 | public let source: String? 11 | 12 | /// Name of the marketing medium. (e.g. Email) 13 | public let medium: String? 14 | 15 | /// Used to identify any paid keywords. 16 | public let term: String? 17 | 18 | /// Used to differentiate your campaign from advertisements. 19 | public let content: String? 20 | 21 | /// The name of the campaign. 22 | public let campaign: String? 23 | 24 | /// A `Bool` indicating if the setting is enabled or not. 25 | public let enable: Bool 26 | 27 | // MARK: - Initializers 28 | 29 | /// Initializes the setting with the various Google Analytics parameters. 30 | /// 31 | /// If the Google Analytics setting is normally enabled by default on your 32 | /// SendGrid account and you want to disable it for this particular email, 33 | /// specify `nil` for all the parameters (the default values for each 34 | /// parameter is `nil` so you can also just initialize with 35 | /// `GoogleAnalytics()` to set everything to `nil`). 36 | /// 37 | /// - Parameters: 38 | /// - source: Name of the referrer source. (e.g. Google, 39 | /// SomeDomain.com, or Marketing Email) 40 | /// - medium: Name of the marketing medium. (e.g. Email) 41 | /// - term: Used to identify any paid keywords. 42 | /// - content: Used to differentiate your campaign from advertisements. 43 | /// - campaign: The name of the campaign. 44 | public init(source: String? = nil, medium: String? = nil, term: String? = nil, content: String? = nil, campaign: String? = nil) { 45 | self.source = source 46 | self.medium = medium 47 | self.term = term 48 | self.content = content 49 | self.campaign = campaign 50 | self.enable = [source, medium, term, content, campaign].reduce(false) { $0 || $1 != nil } 51 | } 52 | } 53 | 54 | public extension GoogleAnalytics /* Encodable conformance */ { 55 | /// :nodoc: 56 | enum CodingKeys: String, CodingKey { 57 | case enable 58 | case source = "utm_source" 59 | case medium = "utm_medium" 60 | case term = "utm_term" 61 | case content = "utm_content" 62 | case campaign = "utm_campaign" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Settings/Tracking/OpenTracking.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `OpenTracking` class is used to toggle the open tracking setting for an 4 | /// email. 5 | public struct OpenTracking: Encodable { 6 | // MARK: - Properties 7 | 8 | /// A bool indicating if the setting should be toggled on or off. 9 | public let enable: Bool 10 | 11 | /// An optional tag to specify where to place the open tracking pixel. 12 | public let substitutionTag: String? 13 | 14 | // MARK: - Initialization 15 | 16 | /// Initializes the setting with a location to put the open tracking pixel 17 | /// at. 18 | /// 19 | /// If the open tracking setting is enabled by default on your SendGrid 20 | /// account and you want to disable it for this specific email, you should 21 | /// use the `.off` case. 22 | /// 23 | /// - Parameter location: The location to put the open tracking pixel at 24 | /// in the email. If you want to turn the setting 25 | /// off, use the `.off` case. 26 | public init(location: Location) { 27 | switch location { 28 | case .off: 29 | self.enable = false 30 | self.substitutionTag = nil 31 | case .bottom: 32 | self.enable = true 33 | self.substitutionTag = nil 34 | case .at(let tag): 35 | self.enable = true 36 | self.substitutionTag = tag 37 | } 38 | } 39 | } 40 | 41 | public extension OpenTracking /* Encodable conformance */ { 42 | /// :nodoc: 43 | enum CodingKeys: String, CodingKey { 44 | case enable 45 | case substitutionTag = "substitution_tag" 46 | } 47 | } 48 | 49 | public extension OpenTracking /* Location enum */ { 50 | /// The `OpenTracking.Location` enum represents where the open tracking 51 | /// pixel should be placed in the email. 52 | /// 53 | /// If the open tracking setting is enabled by default on your SendGrid 54 | /// account and you want to disable it for this specific email, you should 55 | /// use the `.off` case. 56 | /// 57 | /// - off: Disables open tracking for the email. 58 | /// - bottom: Places the open tracking pixel at the bottom of the email 59 | /// body. 60 | /// - at: Places the open tracking pixel at a specified substitution 61 | /// tag. For instance, if you wanted to place the open tracking 62 | /// pixel at the top of your email, you can specify this case 63 | /// with a tag, such as `.at(tag: "%open_tracking%")`, and then 64 | /// in the body of your email you can place the text 65 | /// "%open_tracking%" at the top. The tag will then be replaced 66 | /// with the open tracking pixel. 67 | enum Location { 68 | /// Disables open tracking for the email. 69 | case off 70 | 71 | /// Places the open tracking pixel at the bottom of the email body. 72 | case bottom 73 | 74 | /// Places the open tracking pixel at a specified substitution tag. For 75 | /// instance, if you wanted to place the open tracking pixel at the top 76 | /// of your email, you can specify this case with a tag, such as 77 | /// `.at(tag: "%open_tracking%")`, and then in the body of your email 78 | /// you can place the text "%open_tracking%" at the top. The tag will 79 | /// then be replaced with the open tracking pixel. 80 | case at(tag: String) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Mail/Send/Settings/Tracking/TrackingSettings.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `TrackingSettings` struct houses any tracking settings an email should be 4 | /// configured with. 5 | public struct TrackingSettings: Encodable { 6 | // MARK: - Properties 7 | 8 | /// The click tracking setting. 9 | public var clickTracking: ClickTracking? 10 | 11 | /// The Google Analytics setting. 12 | public var googleAnalytics: GoogleAnalytics? 13 | 14 | /// The open tracking setting. 15 | public var openTracking: OpenTracking? 16 | 17 | /// The subscription tracking setting. 18 | public var subscriptionTracking: SubscriptionTracking? 19 | 20 | /// A `Bool` indicating if at least one of the settings have been specified. 21 | public var hasSettings: Bool { 22 | return self.clickTracking != nil || 23 | self.googleAnalytics != nil || 24 | self.openTracking != nil || 25 | self.subscriptionTracking != nil 26 | } 27 | 28 | // MARK: - Initialization 29 | 30 | /// Initializes the struct with no settings set. 31 | public init() {} 32 | } 33 | 34 | public extension TrackingSettings /* Encodable conformance */ { 35 | /// :nodoc: 36 | enum CodingKeys: String, CodingKey { 37 | case clickTracking = "click_tracking" 38 | case googleAnalytics = "ganalytics" 39 | case openTracking = "open_tracking" 40 | case subscriptionTracking = "subscription_tracking" 41 | } 42 | } 43 | 44 | extension TrackingSettings: Validatable { 45 | /// Bubbles up the `subscriptionTracking` validation. 46 | public func validate() throws { 47 | try self.subscriptionTracking?.validate() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Stats/RetrieveCategoryStatistics.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Statistic.Category` class is used to make the 4 | /// [Get Category Stats](https://sendgrid.com/docs/API_Reference/Web_API_v3/Stats/categories.html) 5 | /// API call. At minimum you need to specify a start date. 6 | /// 7 | /// ```swift 8 | /// do { 9 | /// let now = Date() 10 | /// let lastMonth = now.addingTimeInterval(-2592000) // 30 days 11 | /// let request = RetrieveCategoryStatistics( 12 | /// startDate: lastMonth, 13 | /// endDate: now, 14 | /// aggregatedBy: .week, 15 | /// categories: "Foo", "Bar" 16 | /// ) 17 | /// try Session.shared.send(modeledRequest: request) { result in 18 | /// switch result { 19 | /// case .success(_, let model): 20 | /// // The `model` variable will be an array of `Statistic` structs. 21 | /// model.forEach { _ in 22 | /// // Do something with the stats here... 23 | /// } 24 | /// case .failure(let err): 25 | /// print(err) 26 | /// } 27 | /// } 28 | /// } catch { 29 | /// print(error) 30 | /// } 31 | /// ``` 32 | public class RetrieveCategoryStatistics: RetrieveGlobalStatistics { 33 | // MARK: - Initialization 34 | 35 | /// Initializes the request with a start date and categories, as well as 36 | /// an end date and/or aggregation method. 37 | /// 38 | /// - Parameters: 39 | /// - startDate: The starting date of the statistics to retrieve. 40 | /// - endDate: The end date of the statistics to retrieve. 41 | /// - aggregatedBy: Indicates how the statistics should be grouped. 42 | /// - categories: An array of categories to retrieve stats for. 43 | public init(startDate: Date, endDate: Date? = nil, aggregatedBy: Statistic.Aggregation? = nil, categories: [String]) { 44 | let params = RetrieveStatisticsParameters( 45 | startDate: startDate, 46 | endDate: endDate, 47 | aggregatedBy: aggregatedBy, 48 | categories: categories 49 | ) 50 | super.init(path: "/v3/categories/stats", parameters: params) 51 | } 52 | 53 | /// Initializes the request with a start date and categories, as well as 54 | /// an end date and/or aggregation method. 55 | /// 56 | /// - Parameters: 57 | /// - startDate: The starting date of the statistics to retrieve. 58 | /// - endDate: The end date of the statistics to retrieve. 59 | /// - aggregatedBy: Indicates how the statistics should be grouped. 60 | /// - categories: An array of categories to retrieve stats for. 61 | public convenience init(startDate: Date, endDate: Date? = nil, aggregatedBy: Statistic.Aggregation? = nil, categories: String...) { 62 | self.init(startDate: startDate, endDate: endDate, aggregatedBy: aggregatedBy, categories: categories) 63 | } 64 | 65 | // MARK: - Methods 66 | 67 | /// Validates that there are no more than 10 categories specified. 68 | public override func validate() throws { 69 | try super.validate() 70 | let count = self.parameters?.categories?.count ?? 0 71 | guard 1...10 ~= count else { 72 | throw Exception.Statistic.invalidNumberOfCategories 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Stats/RetrieveGlobalStatistics.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Statistic.Global` class is used to make the 4 | /// [Get Global Stats](https://sendgrid.com/docs/API_Reference/Web_API_v3/Stats/global.html) 5 | /// API call. At minimum you need to specify a start date. 6 | /// 7 | /// ```swift 8 | /// do { 9 | /// let now = Date() 10 | /// let lastMonth = now.addingTimeInterval(-2592000) // 30 days 11 | /// let request = RetrieveGlobalStatistics( 12 | /// startDate: lastMonth, 13 | /// endDate: now, 14 | /// aggregatedBy: .week 15 | /// ) 16 | /// try Session.shared.send(modeledRequest: request) { result in 17 | /// switch result { 18 | /// case .success(_, let model): 19 | /// // The `model` property will be an array of `Statistic` structs. 20 | /// model.forEach { _ in 21 | /// // Do something with the stats here... 22 | /// } 23 | /// case .failure(let err): 24 | /// print(err) 25 | /// } 26 | /// } 27 | /// } catch { 28 | /// print(error) 29 | /// } 30 | /// ``` 31 | public class RetrieveGlobalStatistics: ModeledRequest<[Statistic], RetrieveStatisticsParameters> { 32 | // MARK: - Initialization 33 | 34 | /// Initializes the request with a path and set of parameters. 35 | /// 36 | /// - Parameters: 37 | /// - path: The path of the endpoint. 38 | /// - parameters: The parameters used in the API call. 39 | internal init(path: String, parameters: RetrieveStatisticsParameters) { 40 | let format = "yyyy-MM-dd" 41 | let formatter = DateFormatter() 42 | formatter.dateFormat = format 43 | 44 | super.init( 45 | method: .GET, 46 | path: path, 47 | parameters: parameters, 48 | encodingStrategy: EncodingStrategy(dates: .formatted(formatter), data: .base64), 49 | decodingStrategy: DecodingStrategy(dates: .formatted(formatter), data: .base64) 50 | ) 51 | } 52 | 53 | /// Initializes the request with a start date, as well as an end date and/or 54 | /// aggregation method. 55 | /// 56 | /// - Parameters: 57 | /// - startDate: The starting date of the statistics to retrieve. 58 | /// - endDate: The end date of the statistics to retrieve. 59 | /// - aggregatedBy: Indicates how the statistics should be grouped. 60 | public convenience init(startDate: Date, endDate: Date? = nil, aggregatedBy: Statistic.Aggregation? = nil) { 61 | let params = RetrieveStatisticsParameters( 62 | startDate: startDate, 63 | endDate: endDate, 64 | aggregatedBy: aggregatedBy 65 | ) 66 | self.init(path: "/v3/stats", parameters: params) 67 | } 68 | 69 | // MARK: - Methods 70 | 71 | /// Validates that the end date (if present) is not earlier than the start 72 | /// date. 73 | public override func validate() throws { 74 | try super.validate() 75 | if let e = self.parameters?.endDate, let s = self.parameters?.startDate { 76 | guard s.timeIntervalSince(e) < 0 else { 77 | throw Exception.Statistic.invalidEndDate 78 | } 79 | } 80 | } 81 | } 82 | 83 | /// The `RetrieveStatisticsParameters` class represents the 84 | public class RetrieveStatisticsParameters: Codable { 85 | /// Indicates how the statistics should be grouped. 86 | public let aggregatedBy: Statistic.Aggregation? 87 | 88 | /// The starting date of the statistics to retrieve. 89 | public let startDate: Date 90 | 91 | /// The end date of the statistics to retrieve. 92 | public let endDate: Date? 93 | 94 | /// The categories to retrieve stats for. 95 | public let categories: [String]? 96 | 97 | /// The subusers to retrieve stats for. 98 | public let subusers: [String]? 99 | 100 | /// Initializes the request with a start date, as well as an end date and/or 101 | /// aggregation method. 102 | /// 103 | /// - Parameters: 104 | /// - startDate: The starting date of the statistics to retrieve. 105 | /// - endDate: The end date of the statistics to retrieve. 106 | /// - aggregatedBy: Indicates how the statistics should be grouped. 107 | public init(startDate: Date, endDate: Date? = nil, aggregatedBy: Statistic.Aggregation? = nil, categories: [String]? = nil, subusers: [String]? = nil) { 108 | self.startDate = startDate 109 | self.endDate = endDate 110 | self.aggregatedBy = aggregatedBy 111 | self.categories = categories 112 | self.subusers = subusers 113 | } 114 | 115 | /// :nodoc: 116 | public enum CodingKeys: String, CodingKey { 117 | case startDate = "start_date" 118 | case endDate = "end_date" 119 | case aggregatedBy = "aggregated_by" 120 | case categories 121 | case subusers 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Stats/Statistic.Subuser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Statistic.Subuser` class is used to make the 4 | /// [Get Subuser Stats](https://sendgrid.com/docs/API_Reference/Web_API_v3/Stats/subusers.html) 5 | /// API call. At minimum you need to specify a start date. 6 | /// 7 | /// ```swift 8 | /// do { 9 | /// let now = Date() 10 | /// let lastMonth = now.addingTimeInterval(-2592000) // 30 days 11 | /// let request = RetrieveSubuserStatistics( 12 | /// startDate: lastMonth, 13 | /// endDate: now, 14 | /// aggregatedBy: .week, 15 | /// subusers: "Foo", "Bar" 16 | /// ) 17 | /// try Session.shared.send(modeledRequest: request) { result in 18 | /// switch result { 19 | /// case .success(_, let model): 20 | /// // The `model` property will be an array of `Statistic` structs. 21 | /// model.forEach { _ in 22 | /// // Do something with the stats here... 23 | /// } 24 | /// case .failure(let err): 25 | /// print(err) 26 | /// } 27 | /// } 28 | /// } catch { 29 | /// print(error) 30 | /// } 31 | /// ``` 32 | public class RetrieveSubuserStatistics: RetrieveGlobalStatistics { 33 | // MARK: - Initialization 34 | 35 | /// Initializes the request with a start date and subusers, as well as 36 | /// an end date and/or aggregation method. 37 | /// 38 | /// - Parameters: 39 | /// - startDate: The starting date of the statistics to retrieve. 40 | /// - endDate: The end date of the statistics to retrieve. 41 | /// - aggregatedBy: Indicates how the statistics should be grouped. 42 | /// - subusers: An array of subuser usernames to retrieve stats 43 | /// for (max 10). 44 | public init(startDate: Date, endDate: Date? = nil, aggregatedBy: Statistic.Aggregation? = nil, subusers: [String]) { 45 | let params = RetrieveStatisticsParameters( 46 | startDate: startDate, 47 | endDate: endDate, 48 | aggregatedBy: aggregatedBy, 49 | subusers: subusers 50 | ) 51 | super.init(path: "/v3/subusers/stats", parameters: params) 52 | } 53 | 54 | /// Initializes the request with a start date and subusers, as well as 55 | /// an end date and/or aggregation method. 56 | /// 57 | /// - Parameters: 58 | /// - startDate: The starting date of the statistics to retrieve. 59 | /// - endDate: The end date of the statistics to retrieve. 60 | /// - aggregatedBy: Indicates how the statistics should be grouped. 61 | /// - subusers: An array of subuser usernames to retrieve stats 62 | /// for (max 10). 63 | public convenience init(startDate: Date, endDate: Date? = nil, aggregatedBy: Statistic.Aggregation? = nil, subusers: String...) { 64 | self.init(startDate: startDate, endDate: endDate, aggregatedBy: aggregatedBy, subusers: subusers) 65 | } 66 | 67 | /// Initializes the request with a start date and subusers, as well as 68 | /// an end date and/or aggregation method. 69 | /// 70 | /// - Parameters: 71 | /// - startDate: The starting date of the statistics to retrieve. 72 | /// - endDate: The end date of the statistics to retrieve. 73 | /// - aggregatedBy: Indicates how the statistics should be grouped. 74 | /// - subusers: An array of `Subuser` instances to retrieve 75 | /// stats for (max 10). 76 | public convenience init(startDate: Date, endDate: Date? = nil, aggregatedBy: Statistic.Aggregation? = nil, subusers: [SendGrid.Subuser]) { 77 | let names = subusers.map { $0.username } 78 | self.init(startDate: startDate, endDate: endDate, aggregatedBy: aggregatedBy, subusers: names) 79 | } 80 | 81 | /// Initializes the request with a start date and subusers, as well as 82 | /// an end date and/or aggregation method. 83 | /// 84 | /// - Parameters: 85 | /// - startDate: The starting date of the statistics to retrieve. 86 | /// - endDate: The end date of the statistics to retrieve. 87 | /// - aggregatedBy: Indicates how the statistics should be grouped. 88 | /// - subusers: An array of `Subuser` instances to retrieve 89 | /// stats for (max 10). 90 | public convenience init(startDate: Date, endDate: Date? = nil, aggregatedBy: Statistic.Aggregation? = nil, subusers: SendGrid.Subuser...) { 91 | self.init(startDate: startDate, endDate: endDate, aggregatedBy: aggregatedBy, subusers: subusers) 92 | } 93 | 94 | // MARK: - Methods 95 | 96 | /// Validates that there are no more than 10 subusers specified. 97 | public override func validate() throws { 98 | try super.validate() 99 | let count = self.parameters?.subusers?.count ?? 0 100 | guard 1...10 ~= count else { 101 | throw Exception.Statistic.invalidNumberOfSubusers 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Subuser/RetrieveSubusers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This class is used to make the 4 | /// [get subusers](https://sendgrid.com/docs/API_Reference/Web_API_v3/subusers.html#List-all-Subusers-for-a-parent-GET) 5 | /// API call. 6 | /// 7 | /// You can provide pagination information, and also search by username. If 8 | /// you partial searches are allowed, so for instance if you had a subuser 9 | /// with username `foobar`, searching for `foo` would return it. 10 | /// 11 | /// ```swift 12 | /// do { 13 | /// let search = RetrieveSubusers(username: "foo") 14 | /// try Session.shared.send(modeledRequest: search) { result in 15 | /// switch result { 16 | /// case .success(_, let list): 17 | /// // The `list` variable will be an array of 18 | /// // `Subuser` instances. 19 | /// list.forEach { print($0.username) } 20 | /// case .failure(let err): 21 | /// print(err) 22 | /// } 23 | /// } 24 | /// } catch { 25 | /// print(error) 26 | /// } 27 | /// ``` 28 | public class RetrieveSubusers: ModeledRequest<[Subuser], RetrieveSubusers.Parameters> { 29 | // MARK: - Initialization 30 | 31 | /// Initializes the request with pagination info and a username search. 32 | /// If you don't specify a `username` value, then all subusers will be 33 | /// returned. 34 | /// 35 | /// - Parameters: 36 | /// - page: Provides `limit` and `offset` information to 37 | /// paginate the results. 38 | /// - username: A basic search applied against the subusers' 39 | /// usernames. You can provide a partial username. For 40 | /// instance, if you have a subuser with the username 41 | /// `foobar`, searching for `foo` will return it. 42 | public init(page: Page? = nil, username: String? = nil) { 43 | super.init( 44 | method: .GET, 45 | path: "/v3/subusers", 46 | parameters: Parameters(page: page, username: username) 47 | ) 48 | } 49 | 50 | // MARK: - Methods 51 | 52 | /// Validates that the `limit` value isn't over 500. 53 | public override func validate() throws { 54 | try super.validate() 55 | if let limit = self.parameters?.page?.limit { 56 | let range = 1...500 57 | guard range ~= limit else { throw Exception.Global.limitOutOfRange(limit, range) } 58 | } 59 | } 60 | } 61 | 62 | public extension RetrieveSubusers /* Parameters Struct */ { 63 | /// The `RetrieveSubusers.Parameters` struct holds all the parameters that 64 | /// can be used in the `RetrieveSubusers` call. 65 | struct Parameters: Codable { 66 | // MARK: - Properties 67 | 68 | /// The page range to retrieve. 69 | public var page: Page? 70 | 71 | /// A specific username to search for. 72 | public var username: String? 73 | 74 | // MARK: - Initialization 75 | 76 | /// Initializes the struct. 77 | /// 78 | /// - Parameters: 79 | /// - page: The page range to retrieve. 80 | /// - username: A specific username to search for. 81 | public init(page: Page? = nil, username: String? = nil) { 82 | self.page = page 83 | self.username = username 84 | } 85 | 86 | /// :nodoc: 87 | public init(from decoder: Decoder) throws { 88 | let container = try decoder.container(keyedBy: RetrieveSubusers.Parameters.CodingKeys.self) 89 | if let limit = try container.decodeIfPresent(Int.self, forKey: .limit), 90 | let offset = try container.decodeIfPresent(Int.self, forKey: .offset) { 91 | self.page = Page(limit: limit, offset: offset) 92 | } 93 | self.username = try container.decodeIfPresent(String.self, forKey: .username) 94 | } 95 | 96 | /// :nodoc: 97 | public func encode(to encoder: Encoder) throws { 98 | var container = encoder.container(keyedBy: RetrieveSubusers.Parameters.CodingKeys.self) 99 | try container.encodeIfPresent(self.page?.limit, forKey: .limit) 100 | try container.encodeIfPresent(self.page?.offset, forKey: .offset) 101 | try container.encodeIfPresent(self.username, forKey: .username) 102 | } 103 | 104 | /// :nodoc: 105 | public enum CodingKeys: String, CodingKey { 106 | case limit 107 | case offset 108 | case username 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Suppression/Blocks/DeleteBlocks.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `DeleteBlocks` class represents the API call to [delete from the 4 | /// block list](https://sendgrid.com/docs/API_Reference/Web_API_v3/blocks.html#Delete-blocks-DELETE). 5 | /// You can use it to delete the entire list, or specific entries in the 6 | /// list. 7 | /// 8 | /// ## Delete All Blocks 9 | /// 10 | /// To delete all blocks, use the request returned from `DeleteBlocks.all`. 11 | /// This request will delete all blocks on your block list. 12 | /// 13 | /// ```swift 14 | /// do { 15 | /// let request = DeleteBlocks.all 16 | /// try Session.shared.send(request: request) { result in 17 | /// switch result { 18 | /// case .success(let response): 19 | /// print(response.statusCode) 20 | /// case .failure(let err): 21 | /// print(err) 22 | /// } 23 | /// } 24 | /// } catch { 25 | /// print(error) 26 | /// } 27 | /// ``` 28 | /// 29 | /// ## Delete Specific Blocks 30 | /// 31 | /// To delete specific entries from your block list, use the `DeleteBlocks` 32 | /// class. You can either specify email addresses (as strings), or you can 33 | /// use `Block` instances (useful for if you just retrieved some from the 34 | /// `RetrieveBlocks` class). 35 | /// 36 | /// ```swift 37 | /// do { 38 | /// let request = DeleteBlocks(emails: "foo@example.none", "bar@example.none") 39 | /// try Session.shared.send(request: request) { result in 40 | /// switch result { 41 | /// case .success(let response): 42 | /// print(response.statusCode) 43 | /// case .failure(let err): 44 | /// print(err) 45 | /// } 46 | /// } 47 | /// } catch { 48 | /// print(error) 49 | /// } 50 | /// ``` 51 | public class DeleteBlocks: SuppressionListDeleter { 52 | // MARK: - Properties 53 | 54 | /// Returns a request that will delete *all* the entries on your block 55 | /// list. 56 | public static var all: DeleteBlocks { 57 | DeleteBlocks(deleteAll: true, emails: nil) 58 | } 59 | 60 | // MARK: - Initialization 61 | 62 | /// Private initializer to set all the required properties. 63 | /// 64 | /// - Parameters: 65 | /// - path: The path for the request's API endpoint. 66 | /// - deleteAll: A `Bool` indicating if all the events on the suppression 67 | /// list should be deleted. 68 | /// - emails: An array of emails to delete from the suppression list. 69 | internal override init(path: String? = nil, deleteAll: Bool?, emails: [String]?) { 70 | super.init(path: "/v3/suppression/blocks", deleteAll: deleteAll, emails: emails) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Suppression/Blocks/RetrieveBlocks.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `RetrieveBlocks` class represents the API call to [retrieve the block 4 | /// list](https://sendgrid.com/docs/API_Reference/Web_API_v3/blocks.html#List-all-blocks-GET). 5 | /// You can use it to retrieve the entire list, or specific entries in the 6 | /// list. 7 | /// 8 | /// ## Get All Blocks 9 | /// 10 | /// To retrieve the list of all blocks, use the `RetrieveBlocks` class with the 11 | /// `init(start:end:page:)` initializer. The library will automatically map 12 | /// the response to the `Block` struct model, accessible via the `model` 13 | /// property on the response instance you get back. 14 | /// 15 | /// ```swift 16 | /// do { 17 | /// // If you don't specify any parameters, then the first page of your 18 | /// // entire block list will be fetched: 19 | /// let request = RetrieveBlocks() 20 | /// try Session.shared.send(modeledRequest: request) { result in 21 | /// switch result { 22 | /// case .success(let response, let model): 23 | /// // The `model` property will be an array of `Block` structs. 24 | /// model.forEach { print($0.email) } 25 | /// 26 | /// // The response object has a `Pagination` instance on it as well. 27 | /// // You can use this to get the next page, if you wish. 28 | /// if let nextPage = response.pages?.next { 29 | /// let nextRequest = RetrieveBlocks(page: nextPage) 30 | /// } 31 | /// case .failure(let err): 32 | /// print(err) 33 | /// } 34 | /// } 35 | /// } catch { 36 | /// print(error) 37 | /// } 38 | /// ``` 39 | /// 40 | /// You can also specify any or all of the init parameters to filter your 41 | /// search down: 42 | /// 43 | /// ```swift 44 | /// do { 45 | /// // Retrieve page 2 46 | /// let page = Page(limit: 500, offset: 500) 47 | /// // Blocks starting from yesterday 48 | /// let now = Date() 49 | /// let start = now.addingTimeInterval(-86400) // 24 hours 50 | /// 51 | /// let request = RetrieveBlocks(start: start, end: now, page: page) 52 | /// try Session.shared.send(modeledRequest: request) { result in 53 | /// switch result { 54 | /// case .success(_, let model): 55 | /// // The `model` variable will be an array of `Block` structs. 56 | /// model.forEach { print($0.email) } 57 | /// case .failure(let err): 58 | /// print(err) 59 | /// } 60 | /// } 61 | /// } catch { 62 | /// print(error) 63 | /// } 64 | /// ``` 65 | /// 66 | /// ## Get Specific Block 67 | /// 68 | /// If you're looking for a specific email address in the block list, you 69 | /// can use the `init(email:)` initializer on `RetrieveBlocks`: 70 | /// 71 | /// ```swift 72 | /// do { 73 | /// let request = RetrieveBlocks(email: "foo@example.none") 74 | /// try Session.shared.send(modeledRequest: request) { result in 75 | /// switch result { 76 | /// case .success(_, let model): 77 | /// // The `model` property will be an array of `Block` structs. 78 | /// model.forEach { item in 79 | /// print("\(item.email) was blocked with reason \"\(item.reason)\"") 80 | /// } 81 | /// case .failure(let err): 82 | /// print(err) 83 | /// } 84 | /// } 85 | /// } catch { 86 | /// print(error) 87 | /// } 88 | /// ``` 89 | public class RetrieveBlocks: SuppressionListReader { 90 | /// :nodoc: 91 | internal override init(path: String?, email: String?, start: Date?, end: Date?, page: Page?) { 92 | super.init( 93 | path: "/v3/suppression/blocks", 94 | email: email, 95 | start: start, 96 | end: end, 97 | page: page 98 | ) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Suppression/Bounces/DeleteBounces.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `DeleteBounces` class represents the API call to [delete from the 4 | /// bounce list](https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html#Delete-bounces-DELETE). 5 | /// You can use it to delete the entire list, or specific entries from the 6 | /// list. 7 | /// 8 | /// ## Delete All Bounces 9 | /// 10 | /// To delete all bounces, use the request returned from 11 | /// `DeleteBounces.all`. This request will delete all bounces on your 12 | /// bounce list. 13 | /// 14 | /// ```swift 15 | /// do { 16 | /// let request = DeleteBounces.all 17 | /// try Session.shared.send(request: request) { result in 18 | /// switch result { 19 | /// case .success(let response): 20 | /// print(response.statusCode) 21 | /// case .failure(let err): 22 | /// print(err) 23 | /// } 24 | /// } 25 | /// } catch { 26 | /// print(error) 27 | /// } 28 | /// ``` 29 | /// 30 | /// ## Delete Specific Bounces 31 | /// 32 | /// To delete specific entries from your bounce list, use the 33 | /// `DeleteBounces` class. You can either specify email addresses (as 34 | /// strings), or you can use `Bounce` instances (useful for if you just retrieved 35 | /// some from the `RetrieveBounces` class). 36 | /// 37 | /// ```swift 38 | /// do { 39 | /// let request = DeleteBounces(emails: "foo@example.none", "bar@example.none") 40 | /// try Session.shared.send(request: request) { result in 41 | /// switch result { 42 | /// case .success(let response): 43 | /// print(response.statusCode) 44 | /// case .failure(let err): 45 | /// print(err) 46 | /// } 47 | /// } 48 | /// } catch { 49 | /// print(error) 50 | /// } 51 | /// ``` 52 | public class DeleteBounces: SuppressionListDeleter { 53 | // MARK: - Properties 54 | 55 | /// Returns a request that will delete *all* the entries on your bounce 56 | /// list. 57 | public static var all: DeleteBounces { 58 | DeleteBounces(deleteAll: true, emails: nil) 59 | } 60 | 61 | // MARK: - Initialization 62 | 63 | /// Private initializer to set all the required properties. 64 | /// 65 | /// - Parameters: 66 | /// - path: The path for the request's API endpoint. 67 | /// - deleteAll: A `Bool` indicating if all the events on the suppression 68 | /// list should be deleted. 69 | /// - emails: An array of emails to delete from the suppression list. 70 | internal override init(path: String? = nil, deleteAll: Bool?, emails: [String]?) { 71 | super.init(path: "/v3/suppression/bounces", deleteAll: deleteAll, emails: emails) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Suppression/Bounces/RetrieveBounces.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `RetrieveBounces` class represents the API call to [retrieve the bounce 4 | /// list](https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html#List-all-bounces-GET). 5 | /// You can use it to retrieve the entire list, or specific entries in the 6 | /// list. 7 | /// 8 | /// ## Get All Bounces 9 | /// 10 | /// To retrieve the list of all bounces, use the `RetrieveBounces` class with the 11 | /// `init(start:end:page:)` initializer. The library will automatically map 12 | /// the response to the `Bounce` struct model, accessible via the `model` 13 | /// property on the response instance you get back. 14 | /// 15 | /// ```swift 16 | /// do { 17 | /// // If you don't specify any parameters, then the first page of your entire 18 | /// // bounce list will be fetched: 19 | /// let request = RetrieveBounces() 20 | /// try Session.shared.send(modeledRequest: request) { result in 21 | /// switch result { 22 | /// case .success(let response, let model): 23 | /// // The `model` property will be an array of `Bounce` structs. 24 | /// model.forEach { print($0.email) } 25 | /// 26 | /// // The response object has a `Pagination` instance on it as well. 27 | /// // You can use this to get the next page, if you wish. 28 | /// if let nextPage = response.pages?.next { 29 | /// let nextRequest = RetrieveBounces(page: nextPage) 30 | /// } 31 | /// case .failure(let err): 32 | /// print(err) 33 | /// } 34 | /// } 35 | /// } catch { 36 | /// print(error) 37 | /// } 38 | /// ``` 39 | /// 40 | /// You can also specify any or all of the init parameters to filter your 41 | /// search down: 42 | /// 43 | /// ```swift 44 | /// do { 45 | /// // Retrieve page 2 46 | /// let page = Page(limit: 500, offset: 500) 47 | /// // Bounces starting from yesterday 48 | /// let now = Date() 49 | /// let start = now.addingTimeInterval(-86400) // 24 hours 50 | /// 51 | /// let request = RetrieveBounces(start: start, end: now, page: page) 52 | /// try Session.shared.send(modeledRequest: request) { result in 53 | /// switch result { 54 | /// case .success(_, let model): 55 | /// // The `model` property will be an array of `Bounce` structs. 56 | /// model.forEach { print($0.email) } 57 | /// case .failure(let err): 58 | /// print(err) 59 | /// } 60 | /// } 61 | /// } catch { 62 | /// print(error) 63 | /// } 64 | /// ``` 65 | /// 66 | /// ## Get Specific Bounce 67 | /// 68 | /// If you're looking for a specific email address in the bounce list, you 69 | /// can use the `init(email:)` initializer on `RetrieveBounces`: 70 | /// 71 | /// ```swift 72 | /// do { 73 | /// let request = RetrieveBounces(email: "foo@example.none") 74 | /// try Session.shared.send(modeledRequest: request) { result in 75 | /// switch result { 76 | /// case .success(_, let model): 77 | /// // The `model` property will be an array of `Bounce` structs. 78 | /// if let match = model.first { 79 | /// print("\(match.email) bounced with reason \"\(match.reason)\"") 80 | /// } 81 | /// case .failure(let err): 82 | /// print(err) 83 | /// } 84 | /// } 85 | /// } catch { 86 | /// print(error) 87 | /// } 88 | /// ``` 89 | public class RetrieveBounces: SuppressionListReader { 90 | /// :nodoc: 91 | internal override init(path: String?, email: String?, start: Date?, end: Date?, page: Page?) { 92 | super.init( 93 | path: "/v3/suppression/bounces", 94 | email: email, 95 | start: start, 96 | end: end, 97 | page: page 98 | ) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Suppression/Global Unsubscribes/AddGlobalUnsubscribes.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `AddGlobalUnsubscribes` class represents the API call to add email 4 | /// addresses to the global unsubscribe list. 5 | /// 6 | /// You can specify email addresses (as strings), or you can use `Address` 7 | /// instances. 8 | /// 9 | /// ```swift 10 | /// do { 11 | /// let request = AddGlobalUnsubscribes(emails: "foo@example.none", "bar@example.none") 12 | /// try Session.shared.send(modeledRequest: request) { result in 13 | /// switch result { 14 | /// case .success(let response, _): 15 | /// print(response.statusCode) 16 | /// case .failure(let err): 17 | /// print(err) 18 | /// } 19 | /// } 20 | /// } catch { 21 | /// print(error) 22 | /// } 23 | /// ``` 24 | public class AddGlobalUnsubscribes: ModeledRequest { 25 | // MARK: - Initialization 26 | 27 | /// Initializes the request with a list of email addresses to add to the 28 | /// global unsubscribe list. 29 | /// 30 | /// - Parameter emails: An array of email addresses to add to the global 31 | /// unsubscribe list. 32 | public init(emails: [String]) { 33 | let params = AddGlobalUnsubscribes.Parameters(emails: emails) 34 | super.init(method: .POST, path: "/v3/asm/suppressions/global", parameters: params) 35 | } 36 | 37 | /// Initializes the request with a list of email addresses to add to the 38 | /// global unsubscribe list. 39 | /// 40 | /// - Parameter emails: An array of email addresses to add to the global 41 | /// unsubscribe list. 42 | public convenience init(emails: String...) { 43 | self.init(emails: emails) 44 | } 45 | 46 | /// Initializes the request with a list of addresses to add to the 47 | /// global unsubscribe list. 48 | /// 49 | /// - Parameter emails: An array of addresses to add to the global 50 | /// unsubscribe list. 51 | public convenience init(addresses: [Address]) { 52 | let emails = addresses.map { $0.email } 53 | self.init(emails: emails) 54 | } 55 | 56 | /// Initializes the request with a list of addresses to add to the 57 | /// global unsubscribe list. 58 | /// 59 | /// - Parameter emails: An array of addresses to add to the global 60 | /// unsubscribe list. 61 | public convenience init(addresses: Address...) { 62 | self.init(addresses: addresses) 63 | } 64 | } 65 | 66 | public extension AddGlobalUnsubscribes /* Parameters Struct */ { 67 | /// The `AddGlobalUnsubscribes.Parameters` struct houses the parameters used 68 | /// to add email addresses to the global unsubscribe list. 69 | struct Parameters: Codable { 70 | /// The email addresses to add to the global unsubscribe list. 71 | public let emails: [String] 72 | 73 | /// Initializes the struct with a list of email addresses. 74 | /// 75 | /// - Parameter emails: An array of email addresses. 76 | public init(emails: [String]) { 77 | self.emails = emails 78 | } 79 | 80 | /// Initializes the request with a list of email addresses. 81 | /// 82 | /// - Parameter emails: An array of email addresses. 83 | public init(emails: String...) { 84 | self.init(emails: emails) 85 | } 86 | 87 | /// :nodoc: 88 | public enum CodingKeys: String, CodingKey { 89 | case emails = "recipient_emails" 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Suppression/Global Unsubscribes/DeleteGlobalUnsubscribe.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `DeleteGlobalUnsubscribe` class represents the API call to [delete 4 | /// from the invalid email list](https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/global_suppressions.html#Remove-an-email-address-from-the-Global-Unsubscribes-collection-DELETE). 5 | /// 6 | /// You can specify an email address (as a 7 | /// string), or you can use a `GlobalUnsubscribe` instance (useful for if 8 | /// you just retrieved some from the `RetrieveGlobalUnsubscribes` class). 9 | /// 10 | /// ```swift 11 | /// do { 12 | /// let request = DeleteGlobalUnsubscribe(email: "foo@example.none") 13 | /// try Session.shared.send(request: request) { result in 14 | /// switch result { 15 | /// case .success(let response): 16 | /// print(response.statusCode) 17 | /// case .failure(let err): 18 | /// print(err) 19 | /// } 20 | /// } 21 | /// } catch { 22 | /// print(error) 23 | /// } 24 | /// ``` 25 | public class DeleteGlobalUnsubscribe: Request<[String:String]> { 26 | // MARK: - Initializer 27 | 28 | /// Initializes the request with an email address to delete from the 29 | /// global unsubscribe list. 30 | /// 31 | /// - Parameters: 32 | /// - email: An email address to delete from the global unsubscribe 33 | /// list. 34 | public init(email: String) { 35 | super.init(method: .DELETE, path: "/v3/asm/suppressions/global/\(email)", parameters: nil) 36 | } 37 | 38 | /// Initializes the request with a global unsubscribe event that should 39 | /// be removed from the global unsubscribe list. 40 | /// 41 | /// - Parameter event: A global unsubscribe event containing the email 42 | /// addresses to remove from the global unsubscribe \ 43 | /// list. 44 | public convenience init(event: GlobalUnsubscribe) { 45 | self.init(email: event.email) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Suppression/Global Unsubscribes/RetrieveGlobalUnsubscribes.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `RetrieveGlobalUnsubscribes` class represents the API call to [retrieve 4 | /// the global unsubscribe list](https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/global_suppressions.html#List-all-globally-unsubscribed-email-addresses-GET). 5 | /// You can use it to retrieve the entire list, or specific entries on the 6 | /// list. 7 | /// 8 | /// ## Get All Global Unsubscribes 9 | /// 10 | /// To retrieve the list of all global unsubscribes, use the 11 | /// `RetrieveGlobalUnsubscribes` class with the `init(start:end:page:)` 12 | /// initializer. The library will automatically map the response to the 13 | /// `GlobalUnsubscribe` struct model, accessible via the `model` property on 14 | /// the response instance you get back. 15 | /// 16 | /// ```swift 17 | /// do { 18 | /// // If you don't specify any parameters, then the first page of your 19 | /// // entire global unsubscribe list will be fetched: 20 | /// let request = RetrieveGlobalUnsubscribes() 21 | /// try Session.shared.send(modeledRequest: request) { result in 22 | /// switch result { 23 | /// case .success(let response, let model): 24 | /// // The `model` property will be an array of `GlobalUnsubscribe` structs. 25 | /// model.forEach { print($0.email) } 26 | /// 27 | /// // The response object has a `Pagination` instance on it as well. 28 | /// // You can use this to get the next page, if you wish. 29 | /// if let nextPage = response.pages?.next { 30 | /// let nextRequest = RetrieveGlobalUnsubscribes(page: nextPage) 31 | /// } 32 | /// case .failure(let err): 33 | /// print(err) 34 | /// } 35 | /// } 36 | /// } catch { 37 | /// print(error) 38 | /// } 39 | /// ``` 40 | /// 41 | /// You can also specify any or all of the init parameters to filter your 42 | /// search down: 43 | /// 44 | /// ```swift 45 | /// do { 46 | /// // Retrieve page 2 47 | /// let page = Page(limit: 500, offset: 500) 48 | /// // Global unsubscribes starting from yesterday 49 | /// let now = Date() 50 | /// let start = now.addingTimeInterval(-86400) // 24 hours 51 | /// 52 | /// let request = RetrieveGlobalUnsubscribes(start: start, end: now, page: page) 53 | /// try Session.shared.send(modeledRequest: request) { result in 54 | /// switch result { 55 | /// case .success(_, let model): 56 | /// model.forEach { print($0.email) } 57 | /// case .failure(let err): 58 | /// print(err) 59 | /// } 60 | /// } 61 | /// } catch { 62 | /// print(error) 63 | /// } 64 | /// ``` 65 | /// 66 | /// ## Get Specific Global Unsubscribe 67 | /// 68 | /// If you're looking for a specific email address in the global unsubscribe 69 | /// list, you can use the `init(email:)` initializer on 70 | /// `RetrieveGlobalUnsubscribes`: 71 | /// 72 | /// ```swift 73 | /// do { 74 | /// let request = RetrieveGlobalUnsubscribes(email: "foo@example") 75 | /// try Session.shared.send(modeledRequest: request) { result in 76 | /// switch result { 77 | /// case .success(_, let model): 78 | /// if let unsub = model.first { 79 | /// print(unsub) 80 | /// } 81 | /// case .failure(let err): 82 | /// print(err) 83 | /// } 84 | /// } 85 | /// } catch { 86 | /// print(error) 87 | /// } 88 | /// ``` 89 | public class RetrieveGlobalUnsubscribes: SuppressionListReader { 90 | /// :nodoc: 91 | internal override init(path: String?, email: String?, start: Date?, end: Date?, page: Page?) { 92 | super.init( 93 | path: "/v3/suppression/unsubscribes", 94 | email: email, 95 | start: start, 96 | end: end, 97 | page: page 98 | ) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Suppression/Invalid Emails/DeleteInvalidEmails.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `DeleteInvalidEmails.Delete` class represents the API call to [delete from 4 | /// the invalid email list](https://sendgrid.com/docs/API_Reference/Web_API_v3/invalid_emails.html#Delete-invalid-emails-DELETE). 5 | /// You can use it to delete the entire list, or specific entries on the 6 | /// list. 7 | /// 8 | /// ## Delete All Invalid Emails 9 | /// 10 | /// To delete all invalid emails, use the request returned from 11 | /// `DeleteInvalidEmails.all`. This request will delete all addresses on 12 | /// your invalid email list. 13 | /// 14 | /// ```swift 15 | /// do { 16 | /// let request = DeleteInvalidEmails.all 17 | /// try Session.shared.send(request: request) { result in 18 | /// switch result { 19 | /// case .success(let response): 20 | /// print(response.statusCode) 21 | /// case .failure(let err): 22 | /// print(err) 23 | /// } 24 | /// } 25 | /// } catch { 26 | /// print(error) 27 | /// } 28 | /// ``` 29 | /// 30 | /// ## Delete Specific Invalid Emails 31 | /// 32 | /// To delete specific entries from your invalid email list, use the 33 | /// `DeleteInvalidEmails` class. You can either specify email addresses (as 34 | /// strings), or you can use `InvalidEmail` instances (useful for if you 35 | /// just retrieved some from the `RetrieveInvalidEmails` class). 36 | /// 37 | /// ```swift 38 | /// do { 39 | /// let request = DeleteInvalidEmails(emails: "foo@example", "bar@example") 40 | /// try Session.shared.send(request: request) { result in 41 | /// switch result { 42 | /// case .success(let response): 43 | /// print(response.statusCode) 44 | /// case .failure(let err): 45 | /// print(err) 46 | /// } 47 | /// } 48 | /// } catch { 49 | /// print(error) 50 | /// } 51 | /// ``` 52 | public class DeleteInvalidEmails: SuppressionListDeleter { 53 | // MARK: - Properties 54 | 55 | /// Returns a request that will delete *all* the entries on your spam 56 | /// report list. 57 | public static var all: DeleteInvalidEmails { 58 | DeleteInvalidEmails(deleteAll: true, emails: nil) 59 | } 60 | 61 | // MARK: - Initialization 62 | 63 | /// Private initializer to set all the required properties. 64 | /// 65 | /// - Parameters: 66 | /// - path: The path for the request's API endpoint. 67 | /// - deleteAll: A `Bool` indicating if all the events on the suppression 68 | /// list should be deleted. 69 | /// - emails: An array of emails to delete from the suppression list. 70 | internal override init(path: String? = nil, deleteAll: Bool?, emails: [String]?) { 71 | super.init(path: "/v3/suppression/invalid_emails", deleteAll: deleteAll, emails: emails) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Suppression/Invalid Emails/RetrieveInvalidEmails.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `RetrieveInvalidEmails` class represents the API call to [retrieve the 4 | /// invalid email list](https://sendgrid.com/docs/API_Reference/Web_API_v3/invalid_emails.html#List-all-invalid-emails-GET). 5 | /// You can use it to retrieve the entire list, or specific entries from the 6 | /// list. 7 | /// 8 | /// ## Get All Invalid Emails 9 | /// 10 | /// To retrieve the list of all invalid emails, use the `RetrieveInvalidEmails` 11 | /// class with the `init(start:end:page:)` initializer. The library will 12 | /// automatically map the response to the `InvalidEmail` struct model, 13 | /// accessible via the `model` property on the response instance you get 14 | /// back. 15 | /// 16 | /// ```swift 17 | /// do { 18 | /// // If you don't specify any parameters, then the first page of your 19 | /// // entire invalid email list will be fetched: 20 | /// let request = RetrieveInvalidEmails() 21 | /// try Session.shared.send(modeledRequest: request) { result in 22 | /// switch result { 23 | /// case .success(let response, let model): 24 | /// // The `model` property will be an array of `InvalidEmail` 25 | /// // structs. 26 | /// model.forEach { print($0.email) } 27 | /// 28 | /// // The response object has a `Pagination` instance on it as 29 | /// // well. You can use this to get the next page, if you wish. 30 | /// if let nextPage = response.pages?.next { 31 | /// let nextRequest = RetrieveInvalidEmails(page: nextPage) 32 | /// } 33 | /// case .failure(let err): 34 | /// print(err) 35 | /// } 36 | /// } 37 | /// } catch { 38 | /// print(error) 39 | /// } 40 | /// ``` 41 | /// 42 | /// You can also specify any or all of the init parameters to filter your search 43 | /// down: 44 | /// 45 | /// ```swift 46 | /// do { 47 | /// // Retrieve page 2 48 | /// let page = Page(limit: 500, offset: 500) 49 | /// // Invalid emails starting from yesterday 50 | /// let now = Date() 51 | /// let start = now.addingTimeInterval(-86400) // 24 hours 52 | /// 53 | /// let request = RetrieveInvalidEmails(start: start, end: now, page: page) 54 | /// try Session.shared.send(modeledRequest: request) { result in 55 | /// switch result { 56 | /// case .success(_, let model): 57 | /// model.forEach { print($0.email) } 58 | /// case .failure(let err): 59 | /// print(err) 60 | /// } 61 | /// } 62 | /// } catch { 63 | /// print(error) 64 | /// } 65 | /// ``` 66 | /// 67 | /// ## Get Specific Invalid Email 68 | /// 69 | /// If you're looking for a specific email address in the invalid email list, 70 | /// you can use the `init(email:)` initializer on `RetrieveInvalidEmails`: 71 | /// 72 | /// ```swift 73 | /// do { 74 | /// let request = RetrieveInvalidEmails(email: "foo@example") 75 | /// try Session.shared.send(modeledRequest: request) { result in 76 | /// switch result { 77 | /// case .success(_, let model): 78 | /// model.forEach { print($0.email) } 79 | /// case .failure(let err): 80 | /// print(err) 81 | /// } 82 | /// } 83 | /// } catch { 84 | /// print(error) 85 | /// } 86 | /// ``` 87 | public class RetrieveInvalidEmails: SuppressionListReader { 88 | /// :nodoc: 89 | internal override init(path: String?, email: String?, start: Date?, end: Date?, page: Page?) { 90 | super.init( 91 | path: "/v3/suppression/invalid_emails", 92 | email: email, 93 | start: start, 94 | end: end, 95 | page: page 96 | ) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Suppression/Spam Reports/DeleteSpamReports.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `DeleteSpamReports` class represents the API call to [delete from 4 | /// the spam report list](https://sendgrid.com/docs/API_Reference/Web_API_v3/spam_reports.html#Delete-a-specific-spam-report-DELETE). 5 | /// You can use it to delete the entire list, or specific entries from the 6 | /// list. 7 | /// 8 | /// ## Delete All Spam Reports 9 | /// 10 | /// To delete all spam reports, use the request returned from 11 | /// `DeleteSpamReports.all`. This request will delete all spam reports on 12 | /// your spam report list. 13 | /// 14 | /// ```swift 15 | /// do { 16 | /// let request = DeleteSpamReports.all 17 | /// try Session.shared.send(request: request) { result in 18 | /// switch result { 19 | /// case .success(let response): 20 | /// print(response.statusCode) 21 | /// case .failure(let err): 22 | /// print(err) 23 | /// } 24 | /// } 25 | /// } catch { 26 | /// print(error) 27 | /// } 28 | /// ``` 29 | /// 30 | /// ## Delete Specific Spam Reports 31 | /// 32 | /// To delete specific entries from your spam report list, use the 33 | /// `DeleteSpamReports` class. You can either specify email addresses (as 34 | /// strings), or you can use `SpamReport` instances (useful for if you just 35 | /// retrieved some from the `RetrieveSpamReports` class). 36 | /// 37 | /// ```swift 38 | /// do { 39 | /// let request = DeleteSpamReports(emails: "foo@example.none", "bar@example.none") 40 | /// try Session.shared.send(request: request) { result in 41 | /// switch result { 42 | /// case .success(let response): 43 | /// print(response.statusCode) 44 | /// case .failure(let err): 45 | /// print(err) 46 | /// } 47 | /// } 48 | /// } catch { 49 | /// print(error) 50 | /// } 51 | /// ``` 52 | public class DeleteSpamReports: SuppressionListDeleter { 53 | // MARK: - Properties 54 | 55 | /// Returns a request that will delete *all* the entries on your spam 56 | /// report list. 57 | public static var all: DeleteSpamReports { 58 | DeleteSpamReports(deleteAll: true, emails: nil) 59 | } 60 | 61 | // MARK: - Initialization 62 | 63 | /// Private initializer to set all the required properties. 64 | /// 65 | /// - Parameters: 66 | /// - path: The path for the request's API endpoint. 67 | /// - deleteAll: A `Bool` indicating if all the events on the suppression 68 | /// list should be deleted. 69 | /// - emails: An array of emails to delete from the suppression list. 70 | internal override init(path: String? = nil, deleteAll: Bool?, emails: [String]?) { 71 | super.init(path: "/v3/suppression/spam_reports", deleteAll: deleteAll, emails: emails) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Suppression/Spam Reports/RetrieveSpamReports.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `RetrieveSpamReports` class represents the API call to [retrieve the 4 | /// spam reports list](https://sendgrid.com/docs/API_Reference/Web_API_v3/spam_reports.html#List-all-spam-reports-GET). 5 | /// You can use it to retrieve the entire list, or specific entries on the 6 | /// list. 7 | /// 8 | /// ## Get All Spam Reports 9 | /// 10 | /// To retrieve the list of all spam reports, use the `RetrieveSpamReports` class 11 | /// with the `init(start:end:page:)` initializer. The library will 12 | /// automatically map the response to the `SpamReport` struct model, 13 | /// accessible via the `model` property on the response instance you get 14 | /// back. 15 | /// 16 | /// ```swift 17 | /// do { 18 | /// // If you don't specify any parameters, then the first page of your 19 | /// // entire spam report list will be fetched: 20 | /// let request = RetrieveSpamReports() 21 | /// try Session.shared.send(modeledRequest: request) { result in 22 | /// switch result { 23 | /// case .success(let response, let model): 24 | /// // The `model` property will be an array of `SpamReport` structs. 25 | /// model.forEach { print($0.email) } 26 | /// 27 | /// // The response object has a `Pagination` instance on it as well. 28 | /// // You can use this to get the next page, if you wish. 29 | /// if let nextPage = response.pages?.next { 30 | /// let nextRequest = RetrieveSpamReports(page: nextPage) 31 | /// } 32 | /// case .failure(let err): 33 | /// print(err) 34 | /// } 35 | /// } 36 | /// } catch { 37 | /// print(error) 38 | /// } 39 | /// ``` 40 | /// 41 | /// You can also specify any or all of the init parameters to filter your 42 | /// search down: 43 | /// 44 | /// ```swift 45 | /// do { 46 | /// // Retrieve page 2 47 | /// let page = Page(limit: 500, offset: 500) 48 | /// // Spam Reports starting from yesterday 49 | /// let now = Date() 50 | /// let start = now.addingTimeInterval(-86400) // 24 hours 51 | /// 52 | /// let request = RetrieveSpamReports(start: start, end: now, page: page) 53 | /// try Session.shared.send(modeledRequest: request) { result in 54 | /// switch result { 55 | /// case .success(_, let model): 56 | /// // The `model` property will be an array of `SpamReport` 57 | /// // structs. 58 | /// model.forEach { print($0.email) } 59 | /// case .failure(let err): 60 | /// print(err) 61 | /// } 62 | /// } 63 | /// } catch { 64 | /// print(error) 65 | /// } 66 | /// ``` 67 | /// 68 | /// ## Get Specific Spam Report 69 | /// 70 | /// If you're looking for a specific email address in the spam report list, 71 | /// you can use the `init(email:)` initializer on `RetrieveSpamReports`: 72 | /// 73 | /// ```swift 74 | /// do { 75 | /// let request = RetrieveSpamReports(email: "foo@example.none") 76 | /// try Session.shared.send(modeledRequest: request) { result in 77 | /// switch result { 78 | /// case .success(_, let model): 79 | /// // The `model` value will be an array of `SpamReport` 80 | /// // structs. 81 | /// model.forEach { print($0.email) } 82 | /// case .failure(let err): 83 | /// print(err) 84 | /// } 85 | /// } 86 | /// } catch { 87 | /// print(error) 88 | /// } 89 | /// ``` 90 | public class RetrieveSpamReports: SuppressionListReader { 91 | /// :nodoc: 92 | internal override init(path: String?, email: String?, start: Date?, end: Date?, page: Page?) { 93 | super.init( 94 | path: "/v3/suppression/spam_reports", 95 | email: email, 96 | start: start, 97 | end: end, 98 | page: page 99 | ) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/SendGrid/API/V3/Suppression/SuppressionListDeleter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `SuppressionListDeleter` class is base class inherited by requests that 4 | /// delete entries from a supression list. You should not use this class 5 | /// directly. 6 | public class SuppressionListDeleter: Request { 7 | // MARK: - Initializer 8 | 9 | /// Private initializer to set all the required properties. 10 | /// 11 | /// - Parameters: 12 | /// - path: The path for the request's API endpoint. 13 | /// - deleteAll: A `Bool` indicating if all the events on the suppression 14 | /// list should be deleted. 15 | /// - emails: An array of emails to delete from the suppression list. 16 | internal init(path: String? = nil, deleteAll: Bool?, emails: [String]?) { 17 | let params = SuppressionListDeleterParameters(deleteAll: deleteAll, emails: emails) 18 | super.init(method: .DELETE, path: path ?? "/", parameters: params) 19 | } 20 | 21 | /// Initializes the request with an array of email addresses to delete 22 | /// from the suppression list. 23 | /// 24 | /// - Parameter emails: An array of emails to delete from the suppression 25 | /// list. 26 | public convenience init(emails: [String]) { 27 | self.init(deleteAll: nil, emails: emails) 28 | } 29 | 30 | /// Initializes the request with an array of email addresses to delete 31 | /// from the suppression list. 32 | /// 33 | /// - Parameter emails: An array of emails to delete from the suppression 34 | /// list. 35 | public convenience init(emails: String...) { 36 | self.init(emails: emails) 37 | } 38 | 39 | /// Initializes the request with an array of events that should 40 | /// be removed from the suppression list. 41 | /// 42 | /// - Parameter events: An array of events containing email addresses to 43 | /// remove from the suppression list. 44 | public convenience init(events: [T]) { 45 | let emails = events.map { $0.email } 46 | self.init(emails: emails) 47 | } 48 | 49 | /// Initializes the request with an array of events that should be removed 50 | /// from the suppression list. 51 | /// 52 | /// - Parameter events: An array of events containing email addresses to 53 | /// remove from the suppression list. 54 | public convenience init(events: T...) { 55 | self.init(events: events) 56 | } 57 | } 58 | 59 | /// The `SuppressionListDeleterParameters` struct houses all the parameters that 60 | /// can be made for the suppression delete calls. 61 | public struct SuppressionListDeleterParameters: Codable { 62 | /// A `Bool` indicating if all the events on the suppression list should be 63 | /// deleted. 64 | public let deleteAll: Bool? 65 | 66 | /// An array of emails to delete from the suppression list. 67 | public let emails: [String]? 68 | 69 | /// Initializes the struct. 70 | /// 71 | /// - Parameters: 72 | /// - deleteAll: A `Bool` indicating if all the events on the suppression 73 | /// list should be deleted. 74 | /// - emails: An array of emails to delete from the suppression list. 75 | public init(deleteAll: Bool?, emails: [String]?) { 76 | self.deleteAll = deleteAll 77 | self.emails = emails 78 | } 79 | 80 | /// :nodoc: 81 | public enum CodingKeys: String, CodingKey { 82 | case deleteAll = "delete_all" 83 | case emails 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/SendGrid/Deprecations/2.0.0/Deprecations.AutoEncodable.2.0.0.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// :nodoc: 4 | @available(*, deprecated, message: "all requests should now contain a `parameters` property that is `Encodable`.") 5 | public protocol AutoEncodable: Encodable { 6 | /// The date and date encoding strategy. 7 | var encodingStrategy: EncodingStrategy { get } 8 | 9 | /// The encoded data representation. 10 | func encode(formatting: JSONEncoder.OutputFormatting) -> Data? 11 | 12 | /// The encoded string representation. 13 | func encodedString(formatting: JSONEncoder.OutputFormatting) -> String? 14 | } 15 | -------------------------------------------------------------------------------- /Sources/SendGrid/Deprecations/2.0.0/Deprecations.Email.2.0.0.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Email { 4 | /// :nodoc: 5 | @available(*, deprecated, renamed: "parameters.personalizations") 6 | var personalizations: [Personalization] { 7 | get { self.parameters!.personalizations } 8 | set { self.parameters?.personalizations = newValue } 9 | } 10 | 11 | /// :nodoc: 12 | @available(*, deprecated, renamed: "parameters.content") 13 | var content: [Content] { 14 | get { self.parameters!.content! } 15 | set { self.parameters?.content = newValue } 16 | } 17 | 18 | /// :nodoc: 19 | @available(*, deprecated, renamed: "parameters.subject") 20 | var subject: String? { 21 | get { self.parameters!.subject } 22 | set { self.parameters?.subject = newValue } 23 | } 24 | 25 | /// :nodoc: 26 | @available(*, deprecated, renamed: "parameters.from") 27 | var from: Address { 28 | get { self.parameters!.from } 29 | set { self.parameters?.from = newValue } 30 | } 31 | 32 | /// :nodoc: 33 | @available(*, deprecated, renamed: "parameters.replyTo") 34 | var replyTo: Address? { 35 | get { self.parameters!.replyTo } 36 | set { self.parameters?.replyTo = newValue } 37 | } 38 | 39 | /// :nodoc: 40 | @available(*, deprecated, renamed: "parameters.attachments") 41 | var attachments: [Attachment]? { 42 | get { self.parameters!.attachments } 43 | set { self.parameters?.attachments = newValue } 44 | } 45 | 46 | /// :nodoc: 47 | @available(*, deprecated, renamed: "parameters.templateID") 48 | var templateID: String? { 49 | get { self.parameters!.templateID } 50 | set { self.parameters?.templateID = newValue } 51 | } 52 | 53 | /// :nodoc: 54 | @available(*, deprecated, renamed: "parameters.categories") 55 | var categories: [String]? { 56 | get { self.parameters!.categories } 57 | set { self.parameters?.categories = newValue } 58 | } 59 | 60 | /// :nodoc: 61 | @available(*, deprecated, renamed: "parameters.sections") 62 | var sections: [String: String]? { 63 | get { self.parameters!.sections } 64 | set { self.parameters?.sections = newValue } 65 | } 66 | 67 | /// :nodoc: 68 | @available(*, deprecated, renamed: "parameters.customArguments") 69 | var customArguments: [String: String]? { 70 | get { self.parameters!.customArguments } 71 | set { self.parameters?.customArguments = newValue } 72 | } 73 | 74 | /// :nodoc: 75 | @available(*, deprecated, renamed: "parameters.asm") 76 | var asm: ASM? { 77 | get { self.parameters!.asm } 78 | set { self.parameters?.asm = newValue } 79 | } 80 | 81 | /// :nodoc: 82 | @available(*, deprecated, renamed: "parameters.sendAt") 83 | var sendAt: Date? { 84 | get { self.parameters!.sendAt } 85 | set { self.parameters?.sendAt = newValue } 86 | } 87 | 88 | /// :nodoc: 89 | @available(*, deprecated, renamed: "parameters.batchID") 90 | var batchID: String? { 91 | get { self.parameters!.batchID } 92 | set { self.parameters?.batchID = newValue } 93 | } 94 | 95 | /// :nodoc: 96 | @available(*, deprecated, renamed: "parameters.ipPoolName") 97 | var ipPoolName: String? { 98 | get { self.parameters!.ipPoolName } 99 | set { self.parameters?.ipPoolName = newValue } 100 | } 101 | 102 | /// :nodoc: 103 | @available(*, deprecated, renamed: "parameters.mailSettings") 104 | var mailSettings: MailSettings { 105 | get { self.parameters!.mailSettings } 106 | set { self.parameters?.mailSettings = newValue } 107 | } 108 | 109 | /// :nodoc: 110 | @available(*, deprecated, renamed: "parameters.trackingSettings") 111 | var trackingSettings: TrackingSettings { 112 | get { self.parameters!.trackingSettings } 113 | set { self.parameters?.trackingSettings = newValue } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Sources/SendGrid/Deprecations/2.0.0/Deprecations.JSONValue.2.0.0.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// :nodoc: 4 | @available(*, deprecated, message: "create a `Decodable` model for your response instead") 5 | public struct JSONValue: Codable {} 6 | -------------------------------------------------------------------------------- /Sources/SendGrid/Deprecations/2.0.0/Deprecations.Pagination.2.0.0.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(Linux) 3 | import FoundationNetworking 4 | #endif 5 | 6 | public extension Pagination { 7 | /// :nodoc: 8 | @available(*, deprecated, renamed: "init(response:)") 9 | static func from(response: URLResponse?) -> Pagination? { nil } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/SendGrid/Deprecations/2.0.0/Deprecations.RateLimit.2.0.0.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(Linux) 3 | import FoundationNetworking 4 | #endif 5 | 6 | public extension RateLimit { 7 | /// :nodoc: 8 | @available(*, deprecated, renamed: "init(response:)") 9 | static func from(response: URLResponse?) -> RateLimit? { nil } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/SendGrid/Deprecations/2.0.0/Deprecations.Request.2.0.0.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(Linux) 3 | import FoundationNetworking 4 | #endif 5 | 6 | public extension ModeledRequest { 7 | /// :nodoc: 8 | @available(*, deprecated, renamed: "endpointPath") 9 | var endpoint: URLComponents? { nil } 10 | 11 | /// :nodoc: 12 | @available(*, deprecated, message: "use the new methods in `Session` to faciliate building the URL request.") 13 | func generateUrlRequest() throws -> URLRequest { 14 | throw Exception.Request.couldNotConstructUrlRequest 15 | } 16 | 17 | /// :nodoc: 18 | @available(*, deprecated, message: "use the `headers` property to set any headers on the request.") 19 | var contentType: ContentType { .json } 20 | 21 | /// :nodoc: 22 | @available(*, deprecated, message: "use the `headers` property to set any headers on the request.") 23 | var acceptType: ContentType { .json } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/SendGrid/Deprecations/2.0.0/Deprecations.Response.2.0.0.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @available(*, deprecated, message: "HTTPURLResponse is now returned and a model is sent separately.") 4 | /// :nodoc: 5 | public struct Response {} 6 | -------------------------------------------------------------------------------- /Sources/SendGrid/Deprecations/2.0.0/Deprecations.Statistics.2.0.0.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Statistic { 4 | /// :nodoc: 5 | @available(*, deprecated, renamed: "RetrieveGlobalStatistics") 6 | class Global {} 7 | 8 | /// :nodoc: 9 | @available(*, deprecated, renamed: "RetrieveCategoryStatistics") 10 | class Category {} 11 | 12 | /// :nodoc: 13 | @available(*, deprecated, renamed: "RetrieveSubuserStatistics") 14 | class Subuser {} 15 | } 16 | -------------------------------------------------------------------------------- /Sources/SendGrid/Deprecations/2.0.0/Deprecations.Subuser.Get.2.0.0.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Subuser { 4 | /// :nodoc: 5 | @available(*, deprecated, renamed: "RetrieveSubusers") 6 | class Get {} 7 | } 8 | -------------------------------------------------------------------------------- /Sources/SendGrid/Deprecations/2.0.0/Deprecations.Suppressions.2.0.0.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Block { 4 | /// :nodoc: 5 | @available(*, deprecated, renamed: "RetrieveBlocks") 6 | class Get {} 7 | 8 | /// :nodoc: 9 | @available(*, deprecated, renamed: "DeleteBlocks") 10 | class Delete {} 11 | } 12 | 13 | public extension Bounce { 14 | /// :nodoc: 15 | @available(*, deprecated, renamed: "RetrieveBounces") 16 | class Get {} 17 | 18 | /// :nodoc: 19 | @available(*, deprecated, renamed: "DeleteBounces") 20 | class Delete {} 21 | } 22 | 23 | public extension GlobalUnsubscribe { 24 | /// :nodoc: 25 | @available(*, deprecated, renamed: "RetrieveGlobalUnsubscribes") 26 | class Get {} 27 | 28 | /// :nodoc: 29 | @available(*, deprecated, renamed: "DeleteGlobalUnsubscribe") 30 | class Delete {} 31 | } 32 | 33 | public extension InvalidEmail { 34 | /// :nodoc: 35 | @available(*, deprecated, renamed: "RetrieveInvalidEmails") 36 | class Get {} 37 | 38 | /// :nodoc: 39 | @available(*, deprecated, renamed: "DeleteInvalidEmails") 40 | class Delete {} 41 | } 42 | 43 | public extension SpamReport { 44 | /// :nodoc: 45 | @available(*, deprecated, renamed: "RetrieveSpamReports") 46 | class Get {} 47 | 48 | /// :nodoc: 49 | @available(*, deprecated, renamed: "DeleteSpamReports") 50 | class Delete {} 51 | } 52 | -------------------------------------------------------------------------------- /Sources/SendGrid/Enums/ContentDisposition.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `ContentDisposition` represents the various content-dispositions an 4 | /// attachment can have. 5 | /// 6 | /// - inline: Shows the attachment inline with text. 7 | /// - attachment: Shows the attachment below the text. 8 | public enum ContentDisposition: String, Codable { 9 | // MARK: - Cases 10 | 11 | /// The "inline" disposition, which shows the attachment inline with text. 12 | case inline 13 | 14 | /// The "attachment" disposition, which shows the attachment below the text. 15 | case attachment 16 | } 17 | -------------------------------------------------------------------------------- /Sources/SendGrid/Enums/HTTPMethod.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `HTTPMethod` enum represents the various verbs used in an HTTP request. 4 | public enum HTTPMethod: String, CustomStringConvertible { 5 | // MARK: - Cases 6 | 7 | /// Represents a "GET" call. 8 | case GET 9 | 10 | /// Represents a "POST" call. 11 | case POST 12 | 13 | /// Represents a "PUT" call. 14 | case PUT 15 | 16 | /// Represents a "PATCH" call. 17 | case PATCH 18 | 19 | /// Represents a "DELETE" call. 20 | case DELETE 21 | 22 | // MARK: - Properties 23 | 24 | /// The String representation of the HTTP method. 25 | public var description: String { self.rawValue } 26 | 27 | /// A bool indicating if the HTTP method contains an HTTP body in its 28 | /// request. 29 | public var hasBody: Bool { 30 | switch self { 31 | case .GET: return false 32 | default: return true 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/SendGrid/Enums/Statistic.Aggregation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Statistic { 4 | /// Represents the various aggregation methods a stats call can have. 5 | /// 6 | /// - day: Statistic aggregated by day. 7 | /// - week: Statistics aggregated by week. 8 | /// - month: Statistics aggregated by month. 9 | enum Aggregation: String, Codable { 10 | /// Statistics aggregated by day. 11 | case day 12 | 13 | /// Statistics aggregated by week. 14 | case week 15 | 16 | /// Statistics aggregated by month. 17 | case month 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/SendGrid/Enums/Statistic.Dimension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Statistic { 4 | /// The `Statistic.Dimension` enum represents the different ways the 5 | /// statistics can be sliced. 6 | /// 7 | /// - category: Represents statistics grouped by category. 8 | /// - subuser: Represents statistics grouped by subuser. 9 | enum Dimension: String, Codable { 10 | /// Represents statistics grouped by category. 11 | case category 12 | 13 | /// Represents statistics grouped by subuser. 14 | case subuser 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/SendGrid/Errors/Exception+API.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(Linux) 3 | import FoundationNetworking 4 | #endif 5 | 6 | public extension Exception { 7 | /// The `Exception.APIResponse` struct represents the body returned from an 8 | /// API call that is indicating an error occurred. 9 | struct APIResponse: Error, Codable { 10 | /// The list of error messages. 11 | let errors: [APIMessage] 12 | 13 | /// The original `HTTPURLResponse` from the HTTP request. 14 | var httpResponse: HTTPURLResponse! { self._httpResponse } 15 | 16 | /// :nodoc: 17 | internal var _httpResponse: HTTPURLResponse! 18 | 19 | /// :nodoc: 20 | enum CodingKeys: String, CodingKey { 21 | case errors 22 | } 23 | } 24 | 25 | /// The `Exception.APIMessage` struct represents a single error that can be 26 | /// returned from the API. 27 | struct APIMessage: Codable { 28 | /// The description of the error. 29 | let message: String 30 | 31 | /// The field in the request that the error is referencing. 32 | let field: String? 33 | 34 | /// A description on how to solve the error. 35 | let help: String? 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/SendGrid/Errors/Exception+Authentication.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Exception { 4 | /// The `Exception.Authentication` enum contains all the errors thrown by 5 | /// `Authentication`. 6 | enum Authentication: Error, CustomStringConvertible { 7 | // MARK: - Cases 8 | 9 | /// Thrown when there was a problem encoding the username and password. 10 | case unableToEncodeCredentials 11 | 12 | // MARK: - Properties 13 | 14 | /// A description for the error. 15 | public var description: String { 16 | switch self { 17 | case .unableToEncodeCredentials: 18 | return "There was a problem encoding the username and password for use in the Authorization header. Please double check them." 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/SendGrid/Errors/Exception+ContentType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Exception { 4 | /// The `Exception.ContentType` enum contains all the errors thrown by 5 | /// `ContentType`. 6 | enum ContentType: Error, CustomStringConvertible { 7 | // MARK: - Cases 8 | 9 | /// Thrown when there was an invalid Content-Type used. 10 | case invalidContentType(String) 11 | 12 | // MARK: - Properties 13 | 14 | /// A description for the error. 15 | public var description: String { 16 | switch self { 17 | case .invalidContentType(let type): 18 | return "Invalid content type '\(type)': Content types cannot contain ';', ',', spaces, or CLRF characters and must be at least 3 characters long." 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/SendGrid/Errors/Exception+Global.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Exception { 4 | /// The `Exception.Global` struct contains global errors that can be thrown. 5 | enum Global: Error, CustomStringConvertible { 6 | // MARK: - Cases 7 | 8 | /// Thrown in the event an old, deprecated method is called. 9 | case methodUnavailable(AnyClass, String) 10 | 11 | /// Thrown if a `limit` property has an out-of-range value. 12 | case limitOutOfRange(Int, CountableClosedRange) 13 | 14 | // MARK: - Properties 15 | 16 | /// A description for the error. 17 | public var description: String { 18 | switch self { 19 | case .methodUnavailable(let klass, let methodName): 20 | return "The `\(methodName)` method on \(klass) is no longer available." 21 | case .limitOutOfRange(let value, let range): 22 | return "The `limit` value must be between \(range.lowerBound) and \(range.upperBound) (inclusive). You specified \(value)." 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/SendGrid/Errors/Exception+Request.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Exception { 4 | /// The `Exception.Request` enum contains all the errors thrown by 5 | /// `Request`. 6 | enum Request: Error, CustomStringConvertible { 7 | // MARK: - Cases 8 | 9 | /// Thrown when there a request was made to encode the parameters to an 10 | /// unsupported content type. 11 | case unsupportedContentType(String) 12 | 13 | /// Thrown if there was a problem constructing the URL for the request 14 | /// call. 15 | case couldNotConstructUrlRequest 16 | 17 | // MARK: - Properties 18 | 19 | /// A description for the error. 20 | public var description: String { 21 | switch self { 22 | case .unsupportedContentType(let type): 23 | return "Unsupported content type '\(type)': Unable to encode the request's parameters to type '\(type)'" 24 | case .couldNotConstructUrlRequest: 25 | return "There was a problem constructing the API call's URL. Please double check the `path` property for the request." 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/SendGrid/Errors/Exception+Session.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(Linux) 3 | import FoundationNetworking 4 | #endif 5 | 6 | public extension Exception { 7 | /// The `Exception.Session` enum contains all the errors thrown when 8 | /// attempting to build an HTTP request. 9 | enum Session: Error, CustomStringConvertible { 10 | // MARK: - Cases 11 | 12 | /// Represents an error where no authentication method was provided. 13 | case authenticationMissing 14 | 15 | /// Represents an error where an unsupported authentication method was 16 | /// used. 17 | case authenticationTypeNotAllowed(AnyClass, Authentication) 18 | 19 | /// Thrown if an "onBehalfOf" subuser was specified on a request that 20 | /// doesn't support impersonation. 21 | case impersonationNotAllowed 22 | 23 | /// Thrown if an unsupported authentication method was used. 24 | case unsupportedAuthetication(String) 25 | 26 | /// Thrown when no response comes back from an HTTP request. 27 | case noResponseReceived 28 | 29 | /// Thrown when the API response couldn't be parsed into an expected 30 | /// model. 31 | case unableToParseResponse(HTTPURLResponse) 32 | 33 | // MARK: - Properties 34 | 35 | /// A description for the error. 36 | public var description: String { 37 | switch self { 38 | case .authenticationMissing: 39 | return "Could not make an HTTP request as there was no `Authentication` configured on `Session`. Please set the `authentication` property before calling `send` on `Session`." 40 | case .authenticationTypeNotAllowed(let object, let authType): 41 | return "The `\(object)` class does not allow authentication with \(authType)s. Please try using another Authentication type." 42 | case .impersonationNotAllowed: 43 | return "The specified request does not support impersonation via the 'On-behalf-of' header." 44 | case .unsupportedAuthetication(let description): 45 | return "Authentication with \(description) is not supported on this API call." 46 | case .noResponseReceived: 47 | return "No response was returned from the API call." 48 | case .unableToParseResponse: 49 | return "The received response couldn't be parsed into the expected model type. Inspect the associated `HTTPURLResponse` for more information." 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/SendGrid/Errors/Exception+Statistic.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Exception { 4 | /// The `Exception.Statistic` enum represents all the errors that can be thrown 5 | /// on the statistics calls. 6 | enum Statistic: Error, CustomStringConvertible { 7 | /// Thrown if the end date is before the start date. 8 | case invalidEndDate 9 | 10 | /// Thrown if more than 10 categories are specified in the category 11 | /// stats call. 12 | case invalidNumberOfCategories 13 | 14 | /// Thrown if more than 10 subusers are specified in the subuser stats 15 | /// call. 16 | case invalidNumberOfSubusers 17 | 18 | /// A description of the error. 19 | public var description: String { 20 | switch self { 21 | case .invalidEndDate: 22 | return "The end date cannot be any earlier in time than the start date." 23 | case .invalidNumberOfCategories: 24 | return "Invalid number of categories specified. You must specify at least 1 category, and no more than 10." 25 | case .invalidNumberOfSubusers: 26 | return "Invalid number of subusers specified. You must specify at least 1 subuser, and no more than 10." 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/SendGrid/Errors/Exception.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Exception` struct contains all the errors that the SendGrid-Swift 4 | /// library can throw. 5 | public struct Exception {} 6 | -------------------------------------------------------------------------------- /Sources/SendGrid/Extensions/HTTPURLResponse+APIHeaders.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(Linux) 3 | import FoundationNetworking 4 | #endif 5 | 6 | public extension HTTPURLResponse /* Rate Limit Info */ { 7 | /// The rate limit information extracted from the response. 8 | var rateLimit: RateLimit? { RateLimit(response: self) } 9 | } 10 | 11 | public extension HTTPURLResponse /* Pagination Info */ { 12 | /// The pagination info from the response, if applicable. 13 | var pages: Pagination? { Pagination(response: self) } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/SendGrid/Protocols/EmailEventRepresentable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `EmailEventRepresentable` protocol is used by a struct or class that 4 | /// represents an email event. 5 | public protocol EmailEventRepresentable { 6 | /// The email address on the event 7 | var email: String { get } 8 | 9 | /// The date and time the event occurred on. 10 | var created: Date { get } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/SendGrid/Protocols/EmailHeaderRepresentable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `EmailHeaderRepresentable` protocol provides a method for ensuring 4 | /// custom headers are valid. 5 | public protocol EmailHeaderRepresentable { 6 | /// A dictionary representing the headers that should be added to the email. 7 | var headers: [String: String]? { get set } 8 | 9 | /// Validates the `headers` property to ensure they are not using any 10 | /// reserved headers. If there is a problem, an error is thrown. If 11 | /// everything is fine, then this method returns nothing. 12 | /// 13 | /// - Throws: If there is an invalid header, an error will be 14 | /// thrown. 15 | func validateHeaders() throws 16 | } 17 | 18 | public extension EmailHeaderRepresentable { 19 | /// The default implementation validates against the following headers: 20 | /// 21 | /// - X-SG-ID 22 | /// - X-SG-EID 23 | /// - Received 24 | /// - DKIM-Signature 25 | /// - Content-Type 26 | /// - Content-Transfer-Encoding 27 | /// - To 28 | /// - From 29 | /// - Subject 30 | /// - Reply-To 31 | /// - CC 32 | /// - BCC 33 | func validateHeaders() throws { 34 | guard let head = self.headers else { return } 35 | let reserved: [String] = [ 36 | "x-sg-id", 37 | "x-sg-eid", 38 | "received", 39 | "dkim-signature", 40 | "content-type", 41 | "content-transfer-encoding", 42 | "to", 43 | "from", 44 | "subject", 45 | "reply-to", 46 | "cc", 47 | "bcc" 48 | ] 49 | for (key, _) in head { 50 | guard reserved.firstIndex(of: key.lowercased()) == nil else { throw Exception.Mail.headerNotAllowed(key) } 51 | let regex = try NSRegularExpression(pattern: #"(\s)"#, options: [.caseInsensitive, .anchorsMatchLines]) 52 | guard regex.numberOfMatches(in: key, options: [], range: NSMakeRange(0, key.count)) == 0 else { 53 | throw Exception.Mail.malformedHeader(key) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/SendGrid/Protocols/Scheduling.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Scheduling` protocol contains the properties and methods needed for a 4 | /// class to support scheduled sends. 5 | public protocol Scheduling { 6 | /// An optional time to send the email at. The date cannot be further than 72 hours in the future. 7 | var sendAt: Date? { get set } 8 | 9 | /// Validates the `sendAt` date. 10 | func validateSendAt() throws 11 | } 12 | 13 | public extension Scheduling { 14 | /// The default implementation validates that the date is less than 72 hours 15 | /// in the future. 16 | func validateSendAt() throws { 17 | if let date = self.sendAt { 18 | guard date.timeIntervalSinceNow <= Constants.ScheduleLimit else { throw Exception.Mail.invalidScheduleDate } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/SendGrid/Protocols/Validatable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Validatable` protocol defines the functions that a class or struct 4 | /// needs to implement in order to validate their own values. These are often 5 | /// adopted by structs or classes that are used to configure a request to ensure 6 | /// all the required information is correct and present. 7 | public protocol Validatable { 8 | /// This method is implemented by all conforming classes to validate their 9 | /// own values. If everything is valid, the method does not need to return 10 | /// or do anything. If one or more values are invalid, an error should be 11 | /// thrown. 12 | func validate() throws 13 | } 14 | -------------------------------------------------------------------------------- /Sources/SendGrid/Structs/Authentication.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Authentication` struct is used to authenticate all the requests made by 4 | /// `Session`. 5 | public struct Authentication: CustomStringConvertible { 6 | // MARK: - Properties 7 | 8 | /// The prefix used in the authorization header. 9 | public let prefix: String 10 | 11 | /// The value of the authorization header. 12 | public let value: String 13 | 14 | /// A description for the type of the authorization. 15 | public let description: String 16 | 17 | /// Returns that `Authorization` header value for the authentication type. 18 | /// This can be used on any web API V3 call. 19 | public var authorizationHeader: String { "\(self.prefix) \(self.value)" } 20 | 21 | // MARK: - Initialization 22 | 23 | /// Initializes the struct. 24 | /// 25 | /// - parameter prefix: The prefix used in the authorization header. 26 | /// - parameter value: The value of the authorization header. 27 | /// - parameter description: A description of the authentication type. 28 | public init(prefix: String, value: String, description: String) { 29 | self.prefix = prefix 30 | self.value = value 31 | self.description = description 32 | } 33 | } 34 | 35 | public extension Authentication { 36 | /// Creates an `Authentication` instance representing an API key. 37 | /// 38 | /// - parameter key: The SendGrid API key to use. 39 | /// 40 | /// - returns: An `Authentication` instance. 41 | static func apiKey(_ key: String) -> Authentication { 42 | Authentication(prefix: "Bearer", value: key, description: "API Key") 43 | } 44 | 45 | /// Creates an `Authentication` instance representing a username and 46 | /// password. 47 | /// 48 | /// - parameter username: The SendGrid username key to use. 49 | /// - parameter password: The SendGrid password key to use. 50 | /// 51 | /// - returns: An `Authentication` instance. 52 | static func credential(username: String, password: String) throws -> Authentication { 53 | let str = "\(username):\(password)" 54 | guard let data = str.data(using: .utf8) else { 55 | throw Exception.Authentication.unableToEncodeCredentials 56 | } 57 | return Authentication(prefix: "Basic", value: data.base64EncodedString(), description: "credential") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/SendGrid/Structs/Constants/Constants.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Constants` struct houses all the constant values used throughout the 4 | /// library. 5 | public struct Constants { 6 | // MARK: - Global Constants 7 | 8 | /// The host to use for the API calls. 9 | public static var ApiHost: String = "https://api.sendgrid.com/" 10 | 11 | /// The version number of the library. 12 | public static let Version: String = "2.2.1" 13 | 14 | /// The upper limit to the number of personalizations allowed in an email. 15 | public static let PersonalizationLimit: Int = 1000 16 | 17 | /// The upper limit to the number of recipients that can be in an email. 18 | public static let RecipientLimit: Int = 1000 19 | 20 | /// The upper limit on how many substitutions are allowed. 21 | public static let SubstitutionLimit: Int = 10000 22 | 23 | /// The maximum amount of seconds in the future an email can be scheduled 24 | /// for. 25 | public static let ScheduleLimit: TimeInterval = (72 * 60 * 60) 26 | 27 | /// Constants for categories 28 | public struct Categories { 29 | /// The max number of categories allowed in an email. 30 | public static let TotalLimit: Int = 10 31 | 32 | /// The max number of characters allowed in a category name. 33 | public static let CharacterLimit: Int = 255 34 | } 35 | 36 | /// Constants for the subscription tracking setting. 37 | public struct SubscriptionTracking { 38 | /// The default verbiage for the plain text unsubscribe footer. 39 | public static let DefaultPlainText = "If you would like to unsubscribe and stop receiving these emails click here: <% %>." 40 | 41 | /// The default verbiage for the HTML text unsubscribe footer. 42 | public static let DefaultHTMLText = "

If you would like to unsubscribe and stop receiving these emails <% click here %>.

" 43 | } 44 | 45 | /// Constants for custom arguments. 46 | public struct CustomArguments { 47 | /// The maximum number of bytes allowed for custom arguments in an email. 48 | public static let MaximumBytes: Int = 10000 49 | } 50 | 51 | /// Constants for ASM. 52 | public struct UnsubscribeGroups { 53 | /// The maximum number of unsubscribe groups you can specify in the 54 | /// `groupsToDisplay` property. 55 | public static let MaximumNumberOfDisplayGroups: Int = 25 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/SendGrid/Structs/DecodingStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This struct houses both the date and data decoding strategies for a 4 | /// response. 5 | public struct DecodingStrategy { 6 | // MARK: - Properties 7 | 8 | /// The encoding strategy for dates. 9 | public let dates: JSONDecoder.DateDecodingStrategy 10 | 11 | /// The encoding strategy for data. 12 | public let data: JSONDecoder.DataDecodingStrategy 13 | 14 | // MARK: - Initialization 15 | 16 | /// Initializes the struct with a date and data strategy. 17 | /// 18 | /// - Parameters: 19 | /// - dates: The date encoding strategy. 20 | /// - data: The data encoding strategy. 21 | public init(dates: JSONDecoder.DateDecodingStrategy = .secondsSince1970, data: JSONDecoder.DataDecodingStrategy = .base64) { 22 | self.dates = dates 23 | self.data = data 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/SendGrid/Structs/EncodingStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This struct houses both the date and data encoding strategies for a request. 4 | public struct EncodingStrategy { 5 | // MARK: - Properties 6 | 7 | /// The encoding strategy for dates. 8 | public let dates: JSONEncoder.DateEncodingStrategy 9 | 10 | /// The encoding strategy for data. 11 | public let data: JSONEncoder.DataEncodingStrategy 12 | 13 | // MARK: - Initialization 14 | 15 | /// Initializes the struct with a date and data strategy. 16 | /// 17 | /// - Parameters: 18 | /// - dates: The date encoding strategy. 19 | /// - data: The data encoding strategy. 20 | public init(dates: JSONEncoder.DateEncodingStrategy = .secondsSince1970, data: JSONEncoder.DataEncodingStrategy = .base64) { 21 | self.dates = dates 22 | self.data = data 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/SendGrid/Structs/Page.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// This struct is used to represent a page via the `limit` and `offset` 4 | /// parameters found in various API calls. 5 | public struct Page { 6 | // MARK: - Properties 7 | 8 | /// The limit value for each page of results. 9 | public let limit: Int 10 | 11 | /// The offset value for the page. 12 | public let offset: Int 13 | 14 | // MARK: - Initialization 15 | 16 | /// Initializes the struct with a limit and offset. 17 | /// 18 | /// - Parameters: 19 | /// - limit: The number of results per page. 20 | /// - offset: The index to start the page on. 21 | public init(limit: Int, offset: Int) { 22 | self.limit = limit 23 | self.offset = offset 24 | } 25 | } 26 | 27 | extension Page: Equatable { 28 | /// :nodoc: 29 | /// Equatable conformance. 30 | public static func ==(lhs: Page, rhs: Page) -> Bool { 31 | lhs.limit == rhs.limit && lhs.offset == rhs.offset 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/SendGrid/Structs/Pagination.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(Linux) 3 | import FoundationNetworking 4 | #endif 5 | 6 | /// The `Pagination` struct represents all the pagination info that can be 7 | /// returned via an API call, often containing a `first`, `previous`, `next`, 8 | /// and `last` page. This struct represents all those pages as `Page` instances. 9 | public struct Pagination { 10 | // MARK: - Properties 11 | 12 | /// The first page of results. 13 | public let first: Page? 14 | 15 | /// The previous page of results. 16 | public let previous: Page? 17 | 18 | /// The next page of results. 19 | public let next: Page? 20 | 21 | /// The last page of results. 22 | public let last: Page? 23 | 24 | // MARK: - Initialization 25 | 26 | /// Initializes the struct with a first, previous, next, and last page. 27 | /// 28 | /// - Parameters: 29 | /// - first: The first page of results. 30 | /// - previous: The previous page of results. 31 | /// - next: The next page of results. 32 | /// - last: The last page of results. 33 | public init(first: Page? = nil, previous: Page? = nil, next: Page? = nil, last: Page? = nil) { 34 | self.first = first 35 | self.previous = previous 36 | self.next = next 37 | self.last = last 38 | } 39 | 40 | /// Initializes a new instance from the headers of an API response. 41 | /// 42 | /// - Parameter headers: The headers of the API response. 43 | public init?(headers: [AnyHashable: Any]) { 44 | guard let link = headers["Link"] as? String else { return nil } 45 | func first(match pattern: String, in str: String) -> String? { 46 | let range = str.startIndex.. (String, Page)? in 54 | let partial = String(item) 55 | guard let name = first(match: #"(?<=rel=\")\S+(?=\")"#, in: partial), 56 | let limitStr = first(match: #"(?<=limit=)\d+"#, in: partial), 57 | let limit = Int(limitStr), 58 | let offsetStr = first(match: #"(?<=offset=)\d+"#, in: partial), 59 | let offset = Int(offsetStr) 60 | else { return nil } 61 | let info = Page(limit: limit, offset: offset) 62 | return (name, info) 63 | } 64 | func page(_ rel: String) -> Page? { 65 | let filtered = rawPages.filter { $0.0 == rel } 66 | return filtered.first?.1 67 | } 68 | self.init( 69 | first: page("first"), 70 | previous: page("prev"), 71 | next: page("next"), 72 | last: page("last") 73 | ) 74 | } 75 | 76 | /// Initializes a new instance from a URLResponse, extracting the 77 | /// information out of the "Link" header (if present). 78 | /// 79 | /// - Parameter response: An instance of `URLResponse`. 80 | public init?(response: URLResponse?) { 81 | guard let http = response as? HTTPURLResponse else { return nil } 82 | self.init(headers: http.allHeaderFields) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/SendGrid/Structs/RateLimit.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(Linux) 3 | import FoundationNetworking 4 | #endif 5 | 6 | /// The `RateLimit` struct abstracts any rate-limit information returned from an 7 | /// `Response`. 8 | public struct RateLimit { 9 | // MARK: - Properties 10 | 11 | /// The number of calls allowed for this resource during the refresh period. 12 | public let limit: Int 13 | 14 | /// The number of calls remaining for this resource during the current refresh period. 15 | public let remaining: Int 16 | 17 | /// The date and time at which the refresh period will reset. 18 | public let resetDate: Date 19 | 20 | // MARK: - Initialization 21 | 22 | /// Initializes the struct. 23 | /// 24 | /// - Parameters: 25 | /// - limit: The total number of calls allowed in the rate limit 26 | /// period for the endpoint. 27 | /// - remaining: The number of calls remaining in the rate limit period. 28 | /// - resetDate: The time at which the rate limit will reset. 29 | public init(limit: Int, remaining: Int, resetDate: Date) { 30 | self.limit = limit 31 | self.remaining = remaining 32 | self.resetDate = resetDate 33 | } 34 | 35 | /// Initializes a new instance from the headers of an API response. 36 | /// 37 | /// - Parameter headers: The headers of the API response. 38 | public init?(headers: [AnyHashable: Any]) { 39 | guard let limitStr = headers["X-RateLimit-Limit"] as? String, 40 | let li = Int(limitStr), 41 | let remainStr = headers["X-RateLimit-Remaining"] as? String, 42 | let re = Int(remainStr), 43 | let dateStr = headers["X-RateLimit-Reset"] as? String, 44 | let date = Double(dateStr) 45 | else { return nil } 46 | self.init(limit: li, remaining: re, resetDate: Date(timeIntervalSince1970: date)) 47 | } 48 | 49 | /// Abstracts out the rate-limiting headers from an `URLResponse` and 50 | /// stores their value in a new instance of `RateLimit`. 51 | /// 52 | /// - Parameter response: An instance of `URLResponse`. 53 | public init?(response: URLResponse?) { 54 | guard let http = response as? HTTPURLResponse else { return nil } 55 | self.init(headers: http.allHeaderFields) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/SendGrid/Structs/Validate.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Validate` struct provides static methods for validating common types of 4 | /// data. 5 | struct Validate { 6 | /// Takes a given input and regex pattern and returns a `Bool` indicating if 7 | /// there were any matches. 8 | /// 9 | /// - parameter input: The `String` to search. 10 | /// - parameter pattern: The regular expression pattern, expressed as a 11 | /// `String` to use. 12 | /// 13 | /// - returns: A `Bool` indicating if there were any matches. 14 | static func input(_ input: String, against pattern: String) -> Bool { 15 | guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) 16 | else { return false } 17 | let range = input.startIndex.. 0 19 | } 20 | } 21 | 22 | extension Validate { 23 | /// Validates if a provided `String` is an email address. 24 | static func email(_ email: String) -> Bool { 25 | self.input(email, against: #"\S+@\S+(\.\S+)+"#) 26 | } 27 | 28 | /// Validates text to be used in the subscription tracking setting. 29 | static func subscriptionTracking(body: String) -> Bool { 30 | self.input(body, against: "<% .*%>") 31 | } 32 | 33 | /// Validates that a string does contain CLRF characters. 34 | static func noCLRF(in input: String) -> Bool { 35 | self.input(input, against: #"^([^;,\s]+)$"#) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SendGridTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SendGridTests.__allTests() 7 | 8 | XCTMain(tests) 9 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/Models/Events/BlockTests.swift: -------------------------------------------------------------------------------- 1 | import SendGrid 2 | import XCTest 3 | 4 | class BlockTests: XCTestCase { 5 | func testInitialization() { 6 | let now = Date() 7 | let event = Block(email: "foo@bar.com", created: now, reason: "Because", status: "4.8.15") 8 | XCTAssertEqual(event.email, "foo@bar.com") 9 | XCTAssertEqual(event.created, now) 10 | XCTAssertEqual(event.reason, "Because") 11 | XCTAssertEqual(event.status, "4.8.15") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/Models/Events/BounceTests.swift: -------------------------------------------------------------------------------- 1 | import SendGrid 2 | import XCTest 3 | 4 | class BounceTests: XCTestCase { 5 | func testInitialization() { 6 | let now = Date() 7 | let event = Bounce(email: "foo@bar.com", created: now, reason: "Because", status: "4.8.15") 8 | XCTAssertEqual(event.email, "foo@bar.com") 9 | XCTAssertEqual(event.created, now) 10 | XCTAssertEqual(event.reason, "Because") 11 | XCTAssertEqual(event.status, "4.8.15") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/Models/Events/GlobalUnsubscribeTests.swift: -------------------------------------------------------------------------------- 1 | import SendGrid 2 | import XCTest 3 | 4 | class GlobalUnsubscribeTests: XCTestCase { 5 | func testInitialization() { 6 | let now = Date() 7 | let event = GlobalUnsubscribe(email: "foo@bar.com", created: now) 8 | XCTAssertEqual(event.email, "foo@bar.com") 9 | XCTAssertEqual(event.created, now) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/Models/Events/InvalidEmailTests.swift: -------------------------------------------------------------------------------- 1 | import SendGrid 2 | import XCTest 3 | 4 | class InvalidEmailTests: XCTestCase { 5 | func testInitialization() { 6 | let now = Date() 7 | let event = InvalidEmail(email: "foo@bar", created: now, reason: "Missing TLD") 8 | XCTAssertEqual(event.email, "foo@bar") 9 | XCTAssertEqual(event.created, now) 10 | XCTAssertEqual(event.reason, "Missing TLD") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/Models/Events/SpamReportTests.swift: -------------------------------------------------------------------------------- 1 | import SendGrid 2 | import XCTest 3 | 4 | class SpamReportTests: XCTestCase { 5 | func testInitialization() { 6 | let now = Date() 7 | let event = SpamReport(email: "foo@bar.com", created: now, ip: "123.45.67.89") 8 | XCTAssertEqual(event.email, "foo@bar.com") 9 | XCTAssertEqual(event.created, now) 10 | XCTAssertEqual(event.ip, "123.45.67.89") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/ASMTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class ASMTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = ASM 6 | 7 | func testEncoding() { 8 | let min = ASM(groupID: 4) 9 | XCTAssertEncodedObject(min, equals: ["group_id": 4]) 10 | 11 | let max = ASM(groupID: 4, groupsToDisplay: [8, 15, 16, 23, 42]) 12 | XCTAssertEncodedObject(max, equals: ["group_id": 4, "groups_to_display": [8, 15, 16, 23, 42]]) 13 | } 14 | 15 | func testInitialization() { 16 | let basic = ASM(groupID: 4) 17 | XCTAssertEqual(basic.id, 4) 18 | XCTAssertNil(basic.groupsToDisplay) 19 | 20 | let advance = ASM(groupID: 4, groupsToDisplay: [8, 15, 16, 23, 42]) 21 | XCTAssertEqual(advance.id, 4) 22 | XCTAssertEqual(advance.groupsToDisplay!, [8, 15, 16, 23, 42]) 23 | } 24 | 25 | func testValidation() { 26 | let over = ASM(groupID: 815, groupsToDisplay: Array(1...3)) 27 | XCTAssertNoThrow(try over.validate()) 28 | 29 | do { 30 | let over = ASM(groupID: 815, groupsToDisplay: Array(1...30)) 31 | try over.validate() 32 | XCTFail("Expected an error to be thrown when `ASM` is provided more than \(Constants.UnsubscribeGroups.MaximumNumberOfDisplayGroups) groups to display, but nothing was thrown.") 33 | } catch { 34 | XCTAssertEqual("\(error)", SendGrid.Exception.Mail.tooManyUnsubscribeGroups.description) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/AddressTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class AddressTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = Address 6 | 7 | func testEncoding() { 8 | let withoutName = Address(email: "foo@example.none") 9 | let withName = Address(email: "foo@example.none", name: "Foo Bar") 10 | 11 | XCTAssertEncodedObject(withoutName, equals: ["email": "foo@example.none"]) 12 | XCTAssertEncodedObject(withName, equals: ["name": "Foo Bar", "email": "foo@example.none"]) 13 | } 14 | 15 | func testInitialization() { 16 | let good = Address(email: "test@example.none", name: "Good Email") 17 | XCTAssertEqual("test@example.none", good.email) 18 | XCTAssertEqual("Good Email", good.name) 19 | } 20 | 21 | func testValidation() { 22 | do { 23 | let bad = Address(email: "testexample") 24 | try bad.validate() 25 | XCTFail("Initialization should have failed with a bad address, but no error was thrown.") 26 | } catch { 27 | XCTAssertEqual("\(error)", SendGrid.Exception.Mail.malformedEmailAddress("testexample").description) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/ContentTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class ContentTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = Content 6 | 7 | func testEncoding() { 8 | let plain = Content.plainText(body: "Hello World") 9 | XCTAssertEncodedObject(plain, equals: ["type": "text/plain", "value": "Hello World"]) 10 | } 11 | 12 | func testInitialization() { 13 | let c = Content(contentType: .plainText, value: "Hello World") 14 | XCTAssertEqual(c.type.description, "text/plain") 15 | XCTAssertEqual(c.value, "Hello World") 16 | } 17 | 18 | func testClassInitializers() { 19 | let plain = Content.plainText(body: "plain") 20 | XCTAssertEqual(plain.type.description, ContentType.plainText.description) 21 | XCTAssertEqual(plain.value, "plain") 22 | 23 | let html = Content.html(body: "html") 24 | XCTAssertEqual(html.type.description, ContentType.htmlText.description) 25 | XCTAssertEqual(html.value, "html") 26 | 27 | let both = Content.emailBody(plain: "plain", html: "html") 28 | XCTAssertEqual(both.count, 2) 29 | XCTAssertEqual(both[0].value, "plain") 30 | XCTAssertEqual(both[0].type.description, ContentType.plainText.description) 31 | XCTAssertEqual(both[1].value, "html") 32 | XCTAssertEqual(both[1].type.description, ContentType.htmlText.description) 33 | } 34 | 35 | func testValidation() { 36 | do { 37 | let html = Content(contentType: .jpeg, value: "test") 38 | try html.validate() 39 | XCTAssertTrue(true) 40 | } catch { 41 | XCTFailUnknownError(error) 42 | } 43 | 44 | do { 45 | let semicolon = Content(contentType: ContentType(type: "application;", subtype: "json"), value: "{}") 46 | try semicolon.validate() 47 | XCTFail("Expected error to be thrown when a content type has a semicolon, but nothing was thrown.") 48 | } catch let SendGrid.Exception.ContentType.invalidContentType(errorType) { 49 | XCTAssertEqual(errorType, "application;/json") 50 | } catch { 51 | XCTFailUnknownError(error) 52 | } 53 | 54 | do { 55 | let nl = Content(contentType: ContentType(type: "application\n\r", subtype: "json"), value: "{}") 56 | try nl.validate() 57 | XCTFail("Expected error to be thrown when a content type has a newline, but nothing was thrown.") 58 | } catch let SendGrid.Exception.ContentType.invalidContentType(errorType) { 59 | XCTAssertEqual(errorType, "application\n\r/json") 60 | } catch { 61 | XCTFailUnknownError(error) 62 | } 63 | 64 | do { 65 | let nl = Content(contentType: ContentType(type: "", subtype: ""), value: "{}") 66 | try nl.validate() 67 | XCTFail("Expected error to be thrown when a content type is an empty string, but nothing was thrown.") 68 | } catch let SendGrid.Exception.ContentType.invalidContentType(errorType) { 69 | XCTAssertEqual(errorType, "/") 70 | } catch { 71 | XCTFailUnknownError(error) 72 | } 73 | 74 | do { 75 | let empty = Content(contentType: .plainText, value: "") 76 | try empty.validate() 77 | XCTFail("Expected an empty content value to throw an error, but no error was thrown.") 78 | } catch SendGrid.Exception.Mail.contentHasEmptyString { 79 | XCTAssertTrue(true) 80 | } catch { 81 | XCTFailUnknownError(error) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/Settings/Mail/BCCSettingTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class BCCSettingTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = BCCSetting 6 | 7 | func testEncoding() { 8 | let setting = BCCSetting(email: "foo@example.none") 9 | XCTAssertEncodedObject(setting, equals: ["enable": true, "email": "foo@example.none"]) 10 | 11 | let offSetting = BCCSetting() 12 | XCTAssertEncodedObject(offSetting, equals: ["enable": false]) 13 | 14 | let address = Address(email: "foo@example.none") 15 | let withAddress = BCCSetting(address: address) 16 | XCTAssertEncodedObject(withAddress, equals: ["enable": true, "email": "foo@example.none"]) 17 | } 18 | 19 | func testValidation() { 20 | let good = BCCSetting(email: "test@example.com") 21 | XCTAssertNoThrow(try good.validate()) 22 | 23 | do { 24 | let bad = BCCSetting(email: "test") 25 | try bad.validate() 26 | XCTFail("Expected a failure when initializing the BCC setting with a malformed email, but no error was thrown.") 27 | } catch let SendGrid.Exception.Mail.malformedEmailAddress(em) { 28 | XCTAssertEqual(em, "test") 29 | } catch { 30 | XCTFailUnknownError(error) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/Settings/Mail/BypassListManagementTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class BypassListManagementTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = BypassListManagement 6 | 7 | func testEncoding() { 8 | let on = BypassListManagement(enable: true) 9 | XCTAssertEncodedObject(on, equals: ["enable": true]) 10 | 11 | let off = BypassListManagement(enable: false) 12 | XCTAssertEncodedObject(off, equals: ["enable": false]) 13 | 14 | let unspecified = BypassListManagement() 15 | XCTAssertEncodedObject(unspecified, equals: ["enable": true]) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/Settings/Mail/FooterTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class FooterTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = Footer 6 | 7 | func testEncoding() { 8 | let onSetting = Footer(text: "Hello World", html: "

Hello World

") 9 | let onExpectations: [String: Any] = [ 10 | "enable": true, 11 | "text": "Hello World", 12 | "html": "

Hello World

" 13 | ] 14 | XCTAssertEncodedObject(onSetting, equals: onExpectations) 15 | 16 | let offSetting = Footer() 17 | XCTAssertEncodedObject(offSetting, equals: ["enable": false]) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/Settings/Mail/MailSettingsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class MailSettingsTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = MailSettings 6 | 7 | func testEncoding() { 8 | var settings = MailSettings() 9 | settings.bcc = BCCSetting(email: "foo@example.none") 10 | settings.bypassListManagement = BypassListManagement() 11 | settings.footer = Footer(text: "Hello World", html: "

Hello World

") 12 | settings.sandboxMode = SandboxMode() 13 | settings.spamCheck = SpamChecker(threshold: 8) 14 | let expected: [String: Any] = [ 15 | "bcc": [ 16 | "enable": true, 17 | "email": "foo@example.none" 18 | ], 19 | "bypass_list_management": ["enable": true], 20 | "footer": [ 21 | "enable": true, 22 | "text": "Hello World", 23 | "html": "

Hello World

" 24 | ], 25 | "sandbox_mode": ["enable": true], 26 | "spam_check": [ 27 | "enable": true, 28 | "threshold": 8 29 | ] 30 | ] 31 | XCTAssertEncodedObject(settings, equals: expected) 32 | } 33 | 34 | func testValidation() { 35 | var noErrors = MailSettings() 36 | XCTAssertNoThrow(try noErrors.validate()) 37 | 38 | noErrors.bcc = BCCSetting(email: "foo@example.none") 39 | XCTAssertNoThrow(try noErrors.validate()) 40 | 41 | noErrors.spamCheck = SpamChecker(threshold: 8) 42 | XCTAssertNoThrow(try noErrors.validate()) 43 | 44 | do { 45 | var bccTest = MailSettings() 46 | bccTest.bcc = BCCSetting(email: "foo") 47 | try bccTest.validate() 48 | } catch let SendGrid.Exception.Mail.malformedEmailAddress(em) { 49 | XCTAssertEqual(em, "foo") 50 | } catch { 51 | XCTFailUnknownError(error) 52 | } 53 | 54 | do { 55 | var spamTest = MailSettings() 56 | spamTest.spamCheck = SpamChecker(threshold: 815) 57 | try spamTest.validate() 58 | } catch let SendGrid.Exception.Mail.thresholdOutOfRange(i) { 59 | XCTAssertEqual(i, 815) 60 | } catch { 61 | XCTFailUnknownError(error) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/Settings/Mail/SandboxModeTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class SandboxModeTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = SandboxMode 6 | 7 | func testEncoding() { 8 | let on = SandboxMode(enable: true) 9 | XCTAssertEncodedObject(on, equals: ["enable": true]) 10 | 11 | let off = SandboxMode(enable: false) 12 | XCTAssertEncodedObject(off, equals: ["enable": false]) 13 | 14 | let unspecified = SandboxMode() 15 | XCTAssertEncodedObject(unspecified, equals: ["enable": true]) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/Settings/Mail/SpamCheckerTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class SpamCheckerTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = SpamChecker 6 | 7 | func testEncoding() { 8 | let onSettingMin = SpamChecker(threshold: 4) 9 | XCTAssertEncodedObject(onSettingMin, equals: ["enable": true, "threshold": 4]) 10 | 11 | let onSettingMax = SpamChecker(threshold: 8, url: URL(string: "http://example.none")) 12 | let onSettingMaxExpectations: [String: Any] = [ 13 | "enable": true, 14 | "threshold": 8, 15 | "post_to_url": "http://example.none" 16 | ] 17 | XCTAssertEncodedObject(onSettingMax, equals: onSettingMaxExpectations) 18 | 19 | let offSetting = SpamChecker() 20 | XCTAssertEncodedObject(offSetting, equals: ["enable": false]) 21 | } 22 | 23 | func testInitialization() { 24 | let basic = SpamChecker(threshold: 4) 25 | XCTAssertTrue(basic.enable) 26 | XCTAssertEqual(basic.threshold, 4) 27 | XCTAssertNil(basic.postURL) 28 | 29 | let url = URL(string: "http://localhost") 30 | let advance = SpamChecker(threshold: 10, url: url) 31 | XCTAssertTrue(advance.enable) 32 | XCTAssertEqual(advance.threshold, 10) 33 | XCTAssertEqual(advance.postURL?.absoluteString, "http://localhost") 34 | 35 | let off = SpamChecker() 36 | XCTAssertFalse(off.enable) 37 | XCTAssertNil(off.threshold) 38 | XCTAssertNil(off.postURL) 39 | } 40 | 41 | func testValidation() { 42 | let good = SpamChecker(threshold: 5) 43 | XCTAssertNoThrow(try good.validate()) 44 | 45 | do { 46 | let over = SpamChecker(threshold: 42) 47 | try over.validate() 48 | XCTFail("Expected an error to be thrown with a threshold over 10, but no errors were raised.") 49 | } catch let SendGrid.Exception.Mail.thresholdOutOfRange(i) { 50 | XCTAssertEqual(i, 42) 51 | } catch { 52 | XCTFailUnknownError(error) 53 | } 54 | 55 | do { 56 | let under = SpamChecker(threshold: 0) 57 | try under.validate() 58 | XCTFail("Expected an error to be thrown with a threshold under 1, but no errors were raised.") 59 | } catch let SendGrid.Exception.Mail.thresholdOutOfRange(i) { 60 | XCTAssertEqual(i, 0) 61 | } catch { 62 | XCTFailUnknownError(error) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/Settings/Tracking/ClickTrackingTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class ClickTrackingTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = ClickTracking 6 | 7 | func testExample() { 8 | let onSettingMin = ClickTracking(section: .htmlBody) 9 | XCTAssertEncodedObject(onSettingMin, equals: ["enable": true, "enable_text": false]) 10 | 11 | let onSettingMax = ClickTracking(section: .plainTextAndHTMLBodies) 12 | XCTAssertEncodedObject(onSettingMax, equals: ["enable": true, "enable_text": true]) 13 | 14 | let offSetting = ClickTracking(section: .off) 15 | XCTAssertEncodedObject(offSetting, equals: ["enable": false]) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/Settings/Tracking/GoogleAnalyticsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class GoogleAnalyticsTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = GoogleAnalytics 6 | 7 | func testEncoding() { 8 | let onSettingMin = GoogleAnalytics(source: "test") 9 | XCTAssertEncodedObject(onSettingMin, equals: ["enable": true, "utm_source": "test"]) 10 | 11 | let onSettingMax = GoogleAnalytics( 12 | source: "test_source", 13 | medium: "test_medium", 14 | term: "test_term", 15 | content: "test_content", 16 | campaign: "test_campaign" 17 | ) 18 | let expectation: [String: Any] = [ 19 | "enable": true, 20 | "utm_source": "test_source", 21 | "utm_medium": "test_medium", 22 | "utm_term": "test_term", 23 | "utm_content": "test_content", 24 | "utm_campaign": "test_campaign" 25 | ] 26 | XCTAssertEncodedObject(onSettingMax, equals: expectation) 27 | 28 | let offSetting = GoogleAnalytics() 29 | XCTAssertEncodedObject(offSetting, equals: ["enable": false]) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/Settings/Tracking/OpenTrackingTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class OpenTrackingTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = OpenTracking 6 | 7 | func testEncoding() { 8 | let onSettingMin = OpenTracking(location: .bottom) 9 | XCTAssertEncodedObject(onSettingMin, equals: ["enable": true]) 10 | 11 | let onSettingMax = OpenTracking(location: .at(tag: "%open%")) 12 | XCTAssertEncodedObject(onSettingMax, equals: ["enable": true, "substitution_tag": "%open%"]) 13 | 14 | let offSetting = OpenTracking(location: .off) 15 | XCTAssertEncodedObject(offSetting, equals: ["enable": false]) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/Settings/Tracking/SubscriptionTrackingTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class SubscriptionTrackingTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = SubscriptionTracking 6 | 7 | func testEncoding() { 8 | let templateSetting = SubscriptionTracking(plainText: "Unsubscribe: <% %>", html: "<% Unsubscribe %>") 9 | let templateExpectations: [String: Any] = [ 10 | "enable": true, 11 | "text": "Unsubscribe: <% %>", 12 | "html": "<% Unsubscribe %>" 13 | ] 14 | XCTAssertEncodedObject(templateSetting, equals: templateExpectations) 15 | 16 | let subSetting = SubscriptionTracking(substitutionTag: "%unsub%") 17 | XCTAssertEncodedObject(subSetting, equals: ["enable": true, "substitution_tag": "%unsub%"]) 18 | 19 | let offSetting = SubscriptionTracking() 20 | XCTAssertEncodedObject(offSetting, equals: ["enable": false]) 21 | } 22 | 23 | func testValidation() { 24 | let good1 = SubscriptionTracking( 25 | plainText: "Click here to unsubscribe: <% %>.", 26 | html: "

<% Click here %> to unsubscribe.

" 27 | ) 28 | XCTAssertNoThrow(try good1.validate()) 29 | 30 | let good2 = SubscriptionTracking() 31 | XCTAssertNoThrow(try good2.validate()) 32 | 33 | do { 34 | let missingPlain = SubscriptionTracking( 35 | plainText: "Click here to unsubscribe", 36 | html: "

<% Click here %> to unsubscribe.

" 37 | ) 38 | try missingPlain.validate() 39 | } catch SendGrid.Exception.Mail.missingSubscriptionTrackingTag { 40 | XCTAssertTrue(true) 41 | } catch { 42 | XCTFailUnknownError(error) 43 | } 44 | 45 | do { 46 | let missingHTML = SubscriptionTracking( 47 | plainText: "Click here to unsubscribe: <% %>.", 48 | html: "

Click here to unsubscribe.

" 49 | ) 50 | try missingHTML.validate() 51 | } catch SendGrid.Exception.Mail.missingSubscriptionTrackingTag { 52 | XCTAssertTrue(true) 53 | } catch { 54 | XCTFailUnknownError(error) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Mail/Send/Settings/Tracking/TrackingSettingsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class TrackingSettingsTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = TrackingSettings 6 | 7 | func testEncoding() { 8 | var settings = TrackingSettings() 9 | settings.clickTracking = ClickTracking(section: .htmlBody) 10 | settings.googleAnalytics = GoogleAnalytics(source: "test") 11 | settings.openTracking = OpenTracking(location: .bottom) 12 | settings.subscriptionTracking = SubscriptionTracking(substitutionTag: "%unsub%") 13 | let expected: [String: Any] = [ 14 | "click_tracking": [ 15 | "enable": true, 16 | "enable_text": false 17 | ], 18 | "ganalytics": [ 19 | "enable": true, 20 | "utm_source": "test" 21 | ], 22 | "open_tracking": [ 23 | "enable": true 24 | ], 25 | "subscription_tracking": [ 26 | "enable": true, 27 | "substitution_tag": "%unsub%" 28 | ] 29 | ] 30 | XCTAssertEncodedObject(settings, equals: expected) 31 | } 32 | 33 | func testValidation() { 34 | var plain = TrackingSettings() 35 | XCTAssertNoThrow(try plain.validate()) 36 | 37 | plain.subscriptionTracking = SubscriptionTracking( 38 | plainText: "Click here to unsubscribe: <% %>.", 39 | html: "

<% Click here %> to unsubscribe.

" 40 | ) 41 | 42 | XCTAssertNoThrow(try plain.validate()) 43 | 44 | do { 45 | var subTest = TrackingSettings() 46 | subTest.subscriptionTracking = SubscriptionTracking( 47 | plainText: "Click here to unsubscribe.", 48 | html: "

<% Click here %> to unsubscribe.

" 49 | ) 50 | try subTest.validate() 51 | } catch SendGrid.Exception.Mail.missingSubscriptionTrackingTag { 52 | XCTAssertTrue(true) 53 | } catch { 54 | XCTFailUnknownError(error) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Stats/RetrieveCategoryStatisticsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class RetrieveCategoryStatisticsTests: XCTestCase { 5 | func date(day: Int) -> Date { 6 | let formatter = DateFormatter() 7 | formatter.dateFormat = "yyyy-MM-dd" 8 | return formatter.date(from: "2017-09-\(day)")! 9 | } 10 | 11 | func testMinimalInitialization() { 12 | let request = RetrieveCategoryStatistics(startDate: date(day: 20), categories: "Foo") 13 | XCTAssertEqual(request.description, """ 14 | # GET /v3/categories/stats?categories%5B%5D=Foo&start_date=2017-09-20 15 | 16 | + Request (application/json) 17 | 18 | + Headers 19 | 20 | Accept: application/json 21 | Content-Type: application/json 22 | 23 | """) 24 | } 25 | 26 | func testMaxInitialization() { 27 | let request = RetrieveCategoryStatistics(startDate: date(day: 20), endDate: date(day: 27), aggregatedBy: .week, categories: "Foo", "Bar") 28 | XCTAssertEqual(request.description, """ 29 | # GET /v3/categories/stats?aggregated_by=week&categories%5B%5D=Foo&categories%5B%5D=Bar&end_date=2017-09-27&start_date=2017-09-20 30 | 31 | + Request (application/json) 32 | 33 | + Headers 34 | 35 | Accept: application/json 36 | Content-Type: application/json 37 | 38 | """) 39 | } 40 | 41 | func testValidation() { 42 | let good = RetrieveCategoryStatistics(startDate: date(day: 20), endDate: date(day: 27), aggregatedBy: .week, categories: "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten") 43 | XCTAssertNoThrow(try good.validate()) 44 | 45 | do { 46 | let under = RetrieveCategoryStatistics(startDate: date(day: 20), categories: []) 47 | try under.validate() 48 | } catch SendGrid.Exception.Statistic.invalidNumberOfCategories { 49 | XCTAssertTrue(true) 50 | } catch { 51 | XCTFailUnknownError(error) 52 | } 53 | 54 | do { 55 | let over = RetrieveCategoryStatistics(startDate: date(day: 20), categories: "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven") 56 | try over.validate() 57 | } catch SendGrid.Exception.Statistic.invalidNumberOfCategories { 58 | XCTAssertTrue(true) 59 | } catch { 60 | XCTFailUnknownError(error) 61 | } 62 | 63 | do { 64 | let request = RetrieveCategoryStatistics(startDate: date(day: 20), endDate: date(day: 19)) 65 | try request.validate() 66 | XCTFail("Expected a failure to be thrown when the end date is before the start date, but nothing was thrown.") 67 | } catch SendGrid.Exception.Statistic.invalidEndDate { 68 | XCTAssertTrue(true) 69 | } catch { 70 | XCTFailUnknownError(error) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Stats/RetrieveGlobalStatisticsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class RetrieveGlobalStatisticsTests: XCTestCase { 5 | func date(day: Int) -> Date { 6 | let formatter = DateFormatter() 7 | formatter.dateFormat = "yyyy-MM-dd" 8 | return formatter.date(from: "2017-09-\(day)")! 9 | } 10 | 11 | func testMinimalInitialization() { 12 | let request = RetrieveGlobalStatistics(startDate: date(day: 20)) 13 | XCTAssertEqual(request.description, """ 14 | # GET /v3/stats?start_date=2017-09-20 15 | 16 | + Request (application/json) 17 | 18 | + Headers 19 | 20 | Accept: application/json 21 | Content-Type: application/json 22 | 23 | """) 24 | } 25 | 26 | func testMaxInitialization() { 27 | let request = RetrieveGlobalStatistics(startDate: date(day: 20), endDate: date(day: 27), aggregatedBy: .week) 28 | XCTAssertEqual(request.description, """ 29 | # GET /v3/stats?aggregated_by=week&end_date=2017-09-27&start_date=2017-09-20 30 | 31 | + Request (application/json) 32 | 33 | + Headers 34 | 35 | Accept: application/json 36 | Content-Type: application/json 37 | 38 | """) 39 | } 40 | 41 | func testValidation() { 42 | let good = RetrieveGlobalStatistics(startDate: date(day: 20), endDate: date(day: 27), aggregatedBy: .week) 43 | XCTAssertNoThrow(try good.validate()) 44 | 45 | do { 46 | let request = RetrieveGlobalStatistics(startDate: date(day: 20), endDate: date(day: 19)) 47 | try request.validate() 48 | XCTFail("Expected a failure to be thrown when the end date is before the start date, but nothing was thrown.") 49 | } catch SendGrid.Exception.Statistic.invalidEndDate { 50 | XCTAssertTrue(true) 51 | } catch { 52 | XCTFailUnknownError(error) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Stats/RetrieveSubuserStatisticsTests.swift: -------------------------------------------------------------------------------- 1 | import SendGrid 2 | import XCTest 3 | 4 | class RetrieveSubuserStatisticsTests: XCTestCase { 5 | func date(day: Int) -> Date { 6 | let formatter = DateFormatter() 7 | formatter.dateFormat = "yyyy-MM-dd" 8 | return formatter.date(from: "2017-09-\(day)")! 9 | } 10 | 11 | func testMinimalInitialization() { 12 | let request = RetrieveSubuserStatistics(startDate: date(day: 20), subusers: "foo") 13 | XCTAssertEqual(request.description, """ 14 | # GET /v3/subusers/stats?start_date=2017-09-20&subusers%5B%5D=foo 15 | 16 | + Request (application/json) 17 | 18 | + Headers 19 | 20 | Accept: application/json 21 | Content-Type: application/json 22 | 23 | """) 24 | 25 | let testSub = Subuser(id: 1, username: "foo", email: "foobar@example.nonet", disabled: false) 26 | let subRequest = RetrieveSubuserStatistics(startDate: date(day: 20), subusers: testSub) 27 | XCTAssertEqual(subRequest.description, """ 28 | # GET /v3/subusers/stats?start_date=2017-09-20&subusers%5B%5D=foo 29 | 30 | + Request (application/json) 31 | 32 | + Headers 33 | 34 | Accept: application/json 35 | Content-Type: application/json 36 | 37 | """) 38 | } 39 | 40 | func testMaxInitialization() { 41 | let request = RetrieveSubuserStatistics(startDate: date(day: 20), endDate: date(day: 27), aggregatedBy: .week, subusers: "Foo", "Bar") 42 | XCTAssertEqual(request.description, """ 43 | # GET /v3/subusers/stats?aggregated_by=week&end_date=2017-09-27&start_date=2017-09-20&subusers%5B%5D=Foo&subusers%5B%5D=Bar 44 | 45 | + Request (application/json) 46 | 47 | + Headers 48 | 49 | Accept: application/json 50 | Content-Type: application/json 51 | 52 | """) 53 | } 54 | 55 | func testValidation() { 56 | let good = RetrieveSubuserStatistics(startDate: date(day: 20), endDate: date(day: 27), aggregatedBy: .week, subusers: "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten") 57 | XCTAssertNoThrow(try good.validate()) 58 | 59 | do { 60 | let under = RetrieveSubuserStatistics(startDate: date(day: 20), subusers: [String]()) 61 | try under.validate() 62 | } catch SendGrid.Exception.Statistic.invalidNumberOfSubusers { 63 | XCTAssertTrue(true) 64 | } catch { 65 | XCTFailUnknownError(error) 66 | } 67 | 68 | do { 69 | let over = RetrieveSubuserStatistics(startDate: date(day: 20), subusers: "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven") 70 | try over.validate() 71 | } catch SendGrid.Exception.Statistic.invalidNumberOfSubusers { 72 | XCTAssertTrue(true) 73 | } catch { 74 | XCTFailUnknownError(error) 75 | } 76 | 77 | do { 78 | let request = RetrieveSubuserStatistics(startDate: date(day: 20), endDate: date(day: 19), subusers: "one", "two", "three") 79 | try request.validate() 80 | XCTFail("Expected a failure to be thrown when the end date is before the start date, but nothing was thrown.") 81 | } catch SendGrid.Exception.Statistic.invalidEndDate { 82 | XCTAssertTrue(true) 83 | } catch { 84 | XCTFailUnknownError(error) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Subuser/RetrieveSubusersTests.swift: -------------------------------------------------------------------------------- 1 | import SendGrid 2 | import XCTest 3 | 4 | class RetrieveSubusersTests: XCTestCase { 5 | func testInitialization() { 6 | let min = RetrieveSubusers() 7 | XCTAssertEqual(min.description, """ 8 | # GET /v3/subusers 9 | 10 | + Request (application/json) 11 | 12 | + Headers 13 | 14 | Accept: application/json 15 | Content-Type: application/json 16 | 17 | """) 18 | 19 | let max = RetrieveSubusers(page: Page(limit: 1, offset: 2), username: "foo") 20 | XCTAssertEqual(max.description, """ 21 | # GET /v3/subusers?limit=1&offset=2&username=foo 22 | 23 | + Request (application/json) 24 | 25 | + Headers 26 | 27 | Accept: application/json 28 | Content-Type: application/json 29 | 30 | """) 31 | } 32 | 33 | func testValidation() { 34 | let goodMin = RetrieveSubusers() 35 | XCTAssertNoThrow(try goodMin.validate()) 36 | 37 | let goodMax = RetrieveSubusers(page: Page(limit: 500, offset: 0), username: "foo") 38 | XCTAssertNoThrow(try goodMax.validate()) 39 | 40 | do { 41 | let under = RetrieveSubusers(page: Page(limit: 0, offset: 0)) 42 | try under.validate() 43 | } catch SendGrid.Exception.Global.limitOutOfRange(let i, _) { 44 | XCTAssertEqual(i, 0) 45 | } catch { 46 | XCTFailUnknownError(error) 47 | } 48 | 49 | do { 50 | let over = RetrieveSubusers(page: Page(limit: 501, offset: 0)) 51 | try over.validate() 52 | } catch SendGrid.Exception.Global.limitOutOfRange(let i, _) { 53 | XCTAssertEqual(i, 501) 54 | } catch { 55 | XCTFailUnknownError(error) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/Blocks/DeleteBlocksTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class DeleteBlocksTests: XCTestCase { 5 | func testInitializer() { 6 | let request = DeleteBlocks(emails: "foo@example.none", "bar@example.none") 7 | XCTAssertEqual(request.description, """ 8 | # DELETE /v3/suppression/blocks 9 | 10 | + Request (application/json) 11 | 12 | + Headers 13 | 14 | Accept: application/json 15 | Content-Type: application/json 16 | 17 | + Body 18 | 19 | {"emails":["foo@example.none","bar@example.none"]} 20 | 21 | """) 22 | } 23 | 24 | func testDeleteAll() { 25 | let request = DeleteBlocks.all 26 | XCTAssertEqual(request.description, """ 27 | # DELETE /v3/suppression/blocks 28 | 29 | + Request (application/json) 30 | 31 | + Headers 32 | 33 | Accept: application/json 34 | Content-Type: application/json 35 | 36 | + Body 37 | 38 | {"delete_all":true} 39 | 40 | """) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/Blocks/RetrieveBlocks.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class RetrieveBlocksTests: XCTestCase { 5 | func testGetAllInitialization() { 6 | let minRequest = RetrieveBlocks() 7 | XCTAssertEqual(minRequest.description, """ 8 | # GET /v3/suppression/blocks 9 | 10 | + Request (application/json) 11 | 12 | + Headers 13 | 14 | Accept: application/json 15 | Content-Type: application/json 16 | 17 | """) 18 | 19 | let start = Date(timeIntervalSince1970: 15) 20 | let end = Date(timeIntervalSince1970: 16) 21 | let maxRequest = RetrieveBlocks(start: start, end: end, page: Page(limit: 4, offset: 8)) 22 | XCTAssertEqual(maxRequest.description, """ 23 | # GET /v3/suppression/blocks?end_time=16&limit=4&offset=8&start_time=15 24 | 25 | + Request (application/json) 26 | 27 | + Headers 28 | 29 | Accept: application/json 30 | Content-Type: application/json 31 | 32 | """) 33 | } 34 | 35 | func testEmailSpecificInitializer() { 36 | let request = RetrieveBlocks(email: "foo@example.none") 37 | XCTAssertEqual(request.description, """ 38 | # GET /v3/suppression/blocks/foo@example.none 39 | 40 | + Request (application/json) 41 | 42 | + Headers 43 | 44 | Accept: application/json 45 | Content-Type: application/json 46 | 47 | """) 48 | } 49 | 50 | func testValidation() { 51 | do { 52 | let request = RetrieveBlocks(page: Page(limit: 501, offset: 0)) 53 | try request.validate() 54 | XCTFail("Expected an error to be thrown when the limit is above 500, but no error was thrown.") 55 | } catch let SendGrid.Exception.Global.limitOutOfRange(i, range) { 56 | XCTAssertEqual(i, 501) 57 | XCTAssertEqual(range, 1...500) 58 | } catch { 59 | XCTFailUnknownError(error) 60 | } 61 | 62 | do { 63 | let request = RetrieveBlocks(page: Page(limit: 0, offset: 0)) 64 | try request.validate() 65 | XCTFail("Expected an error to be thrown when the limit is below 1, but no error was thrown.") 66 | } catch let SendGrid.Exception.Global.limitOutOfRange(i, range) { 67 | XCTAssertEqual(i, 0) 68 | XCTAssertEqual(range, 1...500) 69 | } catch { 70 | XCTFailUnknownError(error) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/Bounces/DeleteBouncesTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class DeleteBouncesTests: XCTestCase { 5 | func testInitializer() { 6 | func assert(request: DeleteBounces) { 7 | XCTAssertEqual(request.description, """ 8 | # DELETE /v3/suppression/bounces 9 | 10 | + Request (application/json) 11 | 12 | + Headers 13 | 14 | Accept: application/json 15 | Content-Type: application/json 16 | 17 | + Body 18 | 19 | {"emails":["foo@example.none","bar@example.none"]} 20 | 21 | """) 22 | } 23 | 24 | let emails = DeleteBounces(emails: "foo@example.none", "bar@example.none") 25 | assert(request: emails) 26 | 27 | let fooBounce = Bounce(email: "foo@example.none", created: Date(), reason: "Because", status: "4.8.15") 28 | let barBounce = Bounce(email: "bar@example.none", created: Date(), reason: "Because", status: "16.23.42") 29 | let events = DeleteBounces(events: fooBounce, barBounce) 30 | assert(request: events) 31 | } 32 | 33 | func testDeleteAll() { 34 | let request = DeleteBounces.all 35 | XCTAssertEqual(request.description, """ 36 | # DELETE /v3/suppression/bounces 37 | 38 | + Request (application/json) 39 | 40 | + Headers 41 | 42 | Accept: application/json 43 | Content-Type: application/json 44 | 45 | + Body 46 | 47 | {"delete_all":true} 48 | 49 | """) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/Bounces/RetrieveBouncesTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class RetrieveBouncesTests: XCTestCase { 5 | func testGetAllInitialization() { 6 | let minRequest = RetrieveBounces() 7 | XCTAssertEqual(minRequest.description, """ 8 | # GET /v3/suppression/bounces 9 | 10 | + Request (application/json) 11 | 12 | + Headers 13 | 14 | Accept: application/json 15 | Content-Type: application/json 16 | 17 | """) 18 | 19 | let start = Date(timeIntervalSince1970: 15) 20 | let end = Date(timeIntervalSince1970: 16) 21 | let maxRequest = RetrieveBounces(start: start, end: end, page: Page(limit: 4, offset: 8)) 22 | XCTAssertEqual(maxRequest.description, """ 23 | # GET /v3/suppression/bounces?end_time=16&limit=4&offset=8&start_time=15 24 | 25 | + Request (application/json) 26 | 27 | + Headers 28 | 29 | Accept: application/json 30 | Content-Type: application/json 31 | 32 | """) 33 | } 34 | 35 | func testEmailSpecificInitializer() { 36 | let request = RetrieveBounces(email: "foo@example.none") 37 | XCTAssertEqual(request.description, """ 38 | # GET /v3/suppression/bounces/foo@example.none 39 | 40 | + Request (application/json) 41 | 42 | + Headers 43 | 44 | Accept: application/json 45 | Content-Type: application/json 46 | 47 | """) 48 | } 49 | 50 | func testValidation() { 51 | let good = RetrieveBounces(page: Page(limit: 1, offset: 1)) 52 | XCTAssertNoThrow(try good.validate()) 53 | 54 | do { 55 | let request = RetrieveBounces(page: Page(limit: 0, offset: 0)) 56 | try request.validate() 57 | XCTFail("Expected an error to be thrown when the limit is below 1, but no error was thrown.") 58 | } catch let SendGrid.Exception.Global.limitOutOfRange(i, range) { 59 | XCTAssertEqual(i, 0) 60 | XCTAssertEqual(range, 1...500) 61 | } catch { 62 | XCTFailUnknownError(error) 63 | } 64 | 65 | do { 66 | let request = RetrieveBounces(page: Page(limit: 501, offset: 0)) 67 | try request.validate() 68 | XCTFail("Expected an error to be thrown when the limit is above 500, but no error was thrown.") 69 | } catch let SendGrid.Exception.Global.limitOutOfRange(i, range) { 70 | XCTAssertEqual(i, 501) 71 | XCTAssertEqual(range, 1...500) 72 | } catch { 73 | XCTFailUnknownError(error) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/Global Unsubscribes/AddGlobalUnsubscribesTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class AddGlobalUnsubscribesTests: XCTestCase { 5 | func testInitialization() { 6 | func assert(request: AddGlobalUnsubscribes) { 7 | XCTAssertEqual(request.description, """ 8 | # POST /v3/asm/suppressions/global 9 | 10 | + Request (application/json) 11 | 12 | + Headers 13 | 14 | Accept: application/json 15 | Content-Type: application/json 16 | 17 | + Body 18 | 19 | {"recipient_emails":["foo@example.none","bar@example.none"]} 20 | 21 | """) 22 | } 23 | 24 | let emails = AddGlobalUnsubscribes(emails: "foo@example.none", "bar@example.none") 25 | assert(request: emails) 26 | 27 | let addresses = AddGlobalUnsubscribes(addresses: Address(email: "foo@example.none"), Address(email: "bar@example.none")) 28 | assert(request: addresses) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/Global Unsubscribes/DeleteGlobalUnsubscribeTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class DeleteGlobalUnsubscribeTests: XCTestCase { 5 | func testInitializer() { 6 | func assert(request: DeleteGlobalUnsubscribe) { 7 | XCTAssertEqual(request.description, """ 8 | # DELETE /v3/asm/suppressions/global/foo@example.none 9 | 10 | + Request (application/json) 11 | 12 | + Headers 13 | 14 | Accept: application/json 15 | Content-Type: application/json 16 | 17 | """) 18 | } 19 | 20 | let email = DeleteGlobalUnsubscribe(email: "foo@example.none") 21 | assert(request: email) 22 | 23 | let event = GlobalUnsubscribe(email: "foo@example.none", created: Date()) 24 | let request = DeleteGlobalUnsubscribe(event: event) 25 | assert(request: request) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/Global Unsubscribes/RetrieveGlobalUnsubscribesTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class RetrieveGlobalUnsubscribesTests: XCTestCase { 5 | func testGetAllInitialization() { 6 | let minRequest = RetrieveGlobalUnsubscribes() 7 | XCTAssertEqual(minRequest.description, """ 8 | # GET /v3/suppression/unsubscribes 9 | 10 | + Request (application/json) 11 | 12 | + Headers 13 | 14 | Accept: application/json 15 | Content-Type: application/json 16 | 17 | """) 18 | 19 | let start = Date(timeIntervalSince1970: 15) 20 | let end = Date(timeIntervalSince1970: 16) 21 | let maxRequest = RetrieveGlobalUnsubscribes(start: start, end: end, page: Page(limit: 4, offset: 8)) 22 | XCTAssertEqual(maxRequest.description, """ 23 | # GET /v3/suppression/unsubscribes?end_time=16&limit=4&offset=8&start_time=15 24 | 25 | + Request (application/json) 26 | 27 | + Headers 28 | 29 | Accept: application/json 30 | Content-Type: application/json 31 | 32 | """) 33 | } 34 | 35 | func testEmailSpecificInitializer() { 36 | let request = RetrieveGlobalUnsubscribes(email: "foo@example.none") 37 | XCTAssertEqual(request.description, """ 38 | # GET /v3/suppression/unsubscribes/foo@example.none 39 | 40 | + Request (application/json) 41 | 42 | + Headers 43 | 44 | Accept: application/json 45 | Content-Type: application/json 46 | 47 | """) 48 | } 49 | 50 | func testValidation() { 51 | do { 52 | let request = RetrieveGlobalUnsubscribes(page: Page(limit: 0, offset: 0)) 53 | try request.validate() 54 | XCTFail("Expected an error to be thrown when the limit is below 1, but no error was thrown.") 55 | } catch let SendGrid.Exception.Global.limitOutOfRange(i, range) { 56 | XCTAssertEqual(i, 0) 57 | XCTAssertEqual(range, 1...500) 58 | } catch { 59 | XCTFailUnknownError(error) 60 | } 61 | 62 | do { 63 | let request = RetrieveGlobalUnsubscribes(page: Page(limit: 501, offset: 0)) 64 | try request.validate() 65 | XCTFail("Expected an error to be thrown when the limit is above 500, but no error was thrown.") 66 | } catch let SendGrid.Exception.Global.limitOutOfRange(i, range) { 67 | XCTAssertEqual(i, 501) 68 | XCTAssertEqual(range, 1...500) 69 | } catch { 70 | XCTFailUnknownError(error) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/Invalid Emails/DeleteInvalidEmailsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class DeleteInvalidEmailsTests: XCTestCase { 5 | func testInitializer() { 6 | let request = DeleteInvalidEmails(emails: "foo@example.none", "bar@example.none") 7 | XCTAssertEqual(request.description, """ 8 | # DELETE /v3/suppression/invalid_emails 9 | 10 | + Request (application/json) 11 | 12 | + Headers 13 | 14 | Accept: application/json 15 | Content-Type: application/json 16 | 17 | + Body 18 | 19 | {"emails":["foo@example.none","bar@example.none"]} 20 | 21 | """) 22 | } 23 | 24 | func testDeleteAll() { 25 | let request = DeleteInvalidEmails.all 26 | XCTAssertEqual(request.description, """ 27 | # DELETE /v3/suppression/invalid_emails 28 | 29 | + Request (application/json) 30 | 31 | + Headers 32 | 33 | Accept: application/json 34 | Content-Type: application/json 35 | 36 | + Body 37 | 38 | {"delete_all":true} 39 | 40 | """) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/Invalid Emails/RetrieveInvalidEmailsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class RetrieveInvalidEmailsTests: XCTestCase { 5 | func testGetAllInitialization() { 6 | let minRequest = RetrieveInvalidEmails() 7 | XCTAssertEqual(minRequest.description, """ 8 | # GET /v3/suppression/invalid_emails 9 | 10 | + Request (application/json) 11 | 12 | + Headers 13 | 14 | Accept: application/json 15 | Content-Type: application/json 16 | 17 | """) 18 | 19 | let start = Date(timeIntervalSince1970: 15) 20 | let end = Date(timeIntervalSince1970: 16) 21 | let maxRequest = RetrieveInvalidEmails(start: start, end: end, page: Page(limit: 4, offset: 8)) 22 | XCTAssertEqual(maxRequest.description, """ 23 | # GET /v3/suppression/invalid_emails?end_time=16&limit=4&offset=8&start_time=15 24 | 25 | + Request (application/json) 26 | 27 | + Headers 28 | 29 | Accept: application/json 30 | Content-Type: application/json 31 | 32 | """) 33 | } 34 | 35 | func testEmailSpecificInitializer() { 36 | let request = RetrieveInvalidEmails(email: "foo@example.none") 37 | XCTAssertEqual(request.description, """ 38 | # GET /v3/suppression/invalid_emails/foo@example.none 39 | 40 | + Request (application/json) 41 | 42 | + Headers 43 | 44 | Accept: application/json 45 | Content-Type: application/json 46 | 47 | """) 48 | } 49 | 50 | func testValidation() { 51 | do { 52 | let request = RetrieveInvalidEmails(page: Page(limit: 0, offset: 0)) 53 | try request.validate() 54 | XCTFail("Expected an error to be thrown when the limit is below 1, but no error was thrown.") 55 | } catch let SendGrid.Exception.Global.limitOutOfRange(i, range) { 56 | XCTAssertEqual(i, 0) 57 | XCTAssertEqual(range, 1...500) 58 | } catch { 59 | XCTFailUnknownError(error) 60 | } 61 | 62 | do { 63 | let request = RetrieveInvalidEmails(page: Page(limit: 501, offset: 0)) 64 | try request.validate() 65 | XCTFail("Expected an error to be thrown when the limit is above 500, but no error was thrown.") 66 | } catch let SendGrid.Exception.Global.limitOutOfRange(i, range) { 67 | XCTAssertEqual(i, 501) 68 | XCTAssertEqual(range, 1...500) 69 | } catch { 70 | XCTFailUnknownError(error) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/Spam Reports/DeleteSpamReportsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class DeleteSpamReportsTests: XCTestCase { 5 | func testInitializer() { 6 | let request = DeleteSpamReports(emails: "foo@example.none", "bar@example.none") 7 | XCTAssertEqual(request.description, """ 8 | # DELETE /v3/suppression/spam_reports 9 | 10 | + Request (application/json) 11 | 12 | + Headers 13 | 14 | Accept: application/json 15 | Content-Type: application/json 16 | 17 | + Body 18 | 19 | {"emails":["foo@example.none","bar@example.none"]} 20 | 21 | """) 22 | } 23 | 24 | func testDeleteAll() { 25 | let request = DeleteSpamReports.all 26 | XCTAssertEqual(request.description, """ 27 | # DELETE /v3/suppression/spam_reports 28 | 29 | + Request (application/json) 30 | 31 | + Headers 32 | 33 | Accept: application/json 34 | Content-Type: application/json 35 | 36 | + Body 37 | 38 | {"delete_all":true} 39 | 40 | """) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/Spam Reports/RetrieveSpamReportsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class RetrieveSpamReportsTests: XCTestCase { 5 | func testGetAllInitialization() { 6 | let minRequest = RetrieveSpamReports() 7 | XCTAssertEqual(minRequest.description, """ 8 | # GET /v3/suppression/spam_reports 9 | 10 | + Request (application/json) 11 | 12 | + Headers 13 | 14 | Accept: application/json 15 | Content-Type: application/json 16 | 17 | """) 18 | 19 | let start = Date(timeIntervalSince1970: 15) 20 | let end = Date(timeIntervalSince1970: 16) 21 | let maxRequest = RetrieveSpamReports(start: start, end: end, page: Page(limit: 4, offset: 8)) 22 | XCTAssertEqual(maxRequest.description, """ 23 | # GET /v3/suppression/spam_reports?end_time=16&limit=4&offset=8&start_time=15 24 | 25 | + Request (application/json) 26 | 27 | + Headers 28 | 29 | Accept: application/json 30 | Content-Type: application/json 31 | 32 | """) 33 | } 34 | 35 | func testEmailSpecificInitializer() { 36 | let request = RetrieveSpamReports(email: "foo@example.none") 37 | XCTAssertEqual(request.description, """ 38 | # GET /v3/suppression/spam_reports/foo@example.none 39 | 40 | + Request (application/json) 41 | 42 | + Headers 43 | 44 | Accept: application/json 45 | Content-Type: application/json 46 | 47 | """) 48 | } 49 | 50 | func testValidation() { 51 | do { 52 | let request = RetrieveSpamReports(page: Page(limit: 0, offset: 0)) 53 | try request.validate() 54 | XCTFail("Expected an error to be thrown when the limit is below 1, but no error was thrown.") 55 | } catch let SendGrid.Exception.Global.limitOutOfRange(i, range) { 56 | XCTAssertEqual(i, 0) 57 | XCTAssertEqual(range, 1...500) 58 | } catch { 59 | XCTFailUnknownError(error) 60 | } 61 | 62 | do { 63 | let request = RetrieveSpamReports(page: Page(limit: 501, offset: 0)) 64 | try request.validate() 65 | XCTFail("Expected an error to be thrown when the limit is above 500, but no error was thrown.") 66 | } catch let SendGrid.Exception.Global.limitOutOfRange(i, range) { 67 | XCTAssertEqual(i, 501) 68 | XCTAssertEqual(range, 1...500) 69 | } catch { 70 | XCTFailUnknownError(error) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/SuppressionListDeleterTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class SuppressionListDeleterTests: XCTestCase { 5 | func testInitialization() { 6 | let deleteAllOn = SuppressionListDeleter(deleteAll: true, emails: nil) 7 | XCTAssertTrue(deleteAllOn.parameters!.deleteAll!) 8 | XCTAssertNil(deleteAllOn.parameters!.emails) 9 | 10 | let deleteAllOff = SuppressionListDeleter(deleteAll: false, emails: nil) 11 | XCTAssertFalse(deleteAllOff.parameters!.deleteAll!) 12 | XCTAssertNil(deleteAllOff.parameters!.emails) 13 | 14 | let emails = SuppressionListDeleter(deleteAll: nil, emails: ["foo@bar.com"]) 15 | XCTAssertNil(emails.parameters!.deleteAll) 16 | XCTAssertEqual(emails.parameters!.emails?.joined(), "foo@bar.com") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/SendGridTests/API/V3/Suppression/SuppressionListReaderTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class SuppressionListReaderTests: XCTestCase { 5 | func testInitialization() { 6 | let email = SuppressionListReader(email: "foo@bar.com") 7 | XCTAssertNil(email.parameters!.startDate) 8 | XCTAssertNil(email.parameters!.endDate) 9 | XCTAssertNil(email.parameters!.page) 10 | XCTAssertEqual(email.path, "//foo@bar.com") 11 | 12 | let all = SuppressionListReader(start: Date(), end: Date(), page: Page(limit: 1, offset: 1)) 13 | XCTAssertNotNil(all.parameters!.startDate) 14 | XCTAssertNotNil(all.parameters!.endDate) 15 | XCTAssertEqual(all.parameters!.page, Page(limit: 1, offset: 1)) 16 | XCTAssertEqual(all.path, "/") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/SendGridTests/Classes/SessionTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class SessionTests: XCTestCase { 5 | func testSendWithoutAuth() { 6 | let session = Session() 7 | let personalization = [Personalization(recipients: "test@example.com")] 8 | let email = Email(personalizations: personalization, from: Address(email: "foo@bar.com"), content: [Content.plainText(body: "plain")], subject: "Hello World") 9 | do { 10 | try session.send(request: email) 11 | XCTFail("Expected failure when sending a request without authentication, but nothing was thrown.") 12 | } catch SendGrid.Exception.Session.authenticationMissing { 13 | XCTAssertTrue(true) 14 | } catch { 15 | XCTFailUnknownError(error) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/SendGridTests/Enums/HTTPMethodTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class HTTPMethodTests: XCTestCase { 5 | func testDescription() { 6 | let map: [String: HTTPMethod] = [ 7 | "GET": .GET, 8 | "POST": .POST, 9 | "PUT": .PUT, 10 | "PATCH": .PATCH, 11 | "DELETE": .DELETE 12 | ] 13 | map.forEach { method in 14 | XCTAssertEqual(method.key, method.value.description) 15 | } 16 | } 17 | 18 | func testHasBody() { 19 | XCTAssertFalse(HTTPMethod.GET.hasBody) 20 | 21 | XCTAssertTrue(HTTPMethod.POST.hasBody) 22 | XCTAssertTrue(HTTPMethod.PUT.hasBody) 23 | XCTAssertTrue(HTTPMethod.PATCH.hasBody) 24 | XCTAssertTrue(HTTPMethod.DELETE.hasBody) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/SendGridTests/Helpers/EncodingTester.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | protocol EncodingTester: class { 5 | associatedtype EncodableObject: Encodable 6 | 7 | func encode(_ obj: EncodableObject, strategy: EncodingStrategy) throws -> Data 8 | 9 | func XCTAssertEncodedObject(_ encodableObject: EncodableObject, equals dictionary: [String: Any]) 10 | 11 | func XCTAssertDeepEquals(_ lhs: Any?, _ rhs: Any?) 12 | } 13 | 14 | extension EncodingTester { 15 | func encode(_ obj: EncodableObject, strategy: EncodingStrategy = EncodingStrategy()) throws -> Data { 16 | let encoder = JSONEncoder() 17 | encoder.dataEncodingStrategy = strategy.data 18 | encoder.dateEncodingStrategy = strategy.dates 19 | return try encoder.encode(obj) 20 | } 21 | 22 | func XCTAssertEncodedObject(_ encodableObject: EncodableObject, equals dictionary: [String: Any]) { 23 | do { 24 | let json = try encode(encodableObject) 25 | guard let parsed = (try JSONSerialization.jsonObject(with: json)) as? [String: Any] else { 26 | XCTFail("Expected encoded object to be a dictionary, but received something else.") 27 | return 28 | } 29 | XCTAssertDeepEquals(parsed, dictionary) 30 | } catch { 31 | XCTFail("\(error)") 32 | } 33 | } 34 | 35 | func XCTAssertDeepEquals(_ lhs: Any?, _ rhs: Any?) { 36 | if let lDict = lhs as? [AnyHashable: Any], let rDict = rhs as? [AnyHashable: Any] { 37 | XCTAssertEqual(lDict.count, rDict.count) 38 | for (key, value) in lDict { 39 | XCTAssertDeepEquals(value, rDict[key]) 40 | } 41 | for (key, value) in rDict { 42 | XCTAssertDeepEquals(value, lDict[key]) 43 | } 44 | } else if let lArray = lhs as? [Any], let rArray = rhs as? [Any] { 45 | XCTAssertEqual(lArray.count, rArray.count) 46 | for item in lArray.enumerated() { 47 | XCTAssertDeepEquals(item.element, rArray[item.offset]) 48 | } 49 | } else if let lBool = lhs as? Bool, let rBool = rhs as? Bool { 50 | XCTAssertEqual(lBool, rBool) 51 | } else if let left = lhs, let right = rhs { 52 | XCTAssertEqual("\(left)", "\(right)") 53 | } else { 54 | XCTAssertNotNil(lhs) 55 | XCTAssertNotNil(rhs) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/SendGridTests/Helpers/Exception.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Exception: Error, CustomStringConvertible { 4 | case encodedDataIsNilString 5 | var description: String { 6 | switch self { 7 | case .encodedDataIsNilString: 8 | return "The encoded object could not be represented as a `String`." 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/SendGridTests/Helpers/XCTFailUnknownError.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | func XCTFailUnknownError(_ error: Error) { 4 | XCTFail("An unexpected error was thrown: \(error)") 5 | } 6 | -------------------------------------------------------------------------------- /Tests/SendGridTests/Structs/AuthenticationTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class AuthenticationTests: XCTestCase { 5 | func testInitializer() { 6 | let auth = Authentication(prefix: "Bearer", value: "SG.123.ABC", description: "Test") 7 | XCTAssertEqual(auth.prefix, "Bearer") 8 | XCTAssertEqual(auth.value, "SG.123.ABC") 9 | XCTAssertEqual(auth.description, "Test") 10 | } 11 | 12 | func testAuthorizationHeader() { 13 | let auth = Authentication(prefix: "Bearer", value: "SG.123.ABC", description: "Test") 14 | XCTAssertEqual(auth.authorizationHeader, "Bearer SG.123.ABC") 15 | } 16 | 17 | func testApiKey() { 18 | let auth: Authentication = .apiKey("SG.123.ABC") 19 | XCTAssertEqual(auth.authorizationHeader, "Bearer SG.123.ABC") 20 | } 21 | 22 | func testCredential() { 23 | let auth: Authentication? = try? .credential(username: "foo", password: "bar") 24 | XCTAssertEqual(auth?.authorizationHeader, "Basic Zm9vOmJhcg==") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/SendGridTests/Structs/ContentTypeTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class ContentTypeTests: XCTestCase, EncodingTester { 5 | typealias EncodableObject = TestObject 6 | struct TestObject: Encodable { 7 | let type: ContentType 8 | } 9 | 10 | func testEncoding() { 11 | let test = TestObject(type: .json) 12 | XCTAssertEncodedObject(test, equals: ["type": "application/json"]) 13 | } 14 | 15 | func testInitializerAndDescription() { 16 | let type = ContentType(type: "foo", subtype: "bar") 17 | XCTAssertEqual(type.type, "foo") 18 | XCTAssertEqual(type.subtype, "bar") 19 | XCTAssertEqual(type.description, "foo/bar") 20 | } 21 | 22 | func testRawInitializer() { 23 | let good = ContentType(rawValue: "foo/bar") 24 | XCTAssertNotNil(good) 25 | XCTAssertEqual(good?.type, "foo") 26 | XCTAssertEqual(good?.subtype, "bar") 27 | XCTAssertEqual(good?.description, "foo/bar") 28 | 29 | let badCases = [ 30 | "foo/bar/baz": "if there is more than 1 slash", 31 | "foobar": "if there is no slash" 32 | ] 33 | for (type, description) in badCases { 34 | XCTAssertNil(ContentType(rawValue: type), "Returns `nil` \(description)") 35 | } 36 | } 37 | 38 | func testIndex() { 39 | let plain = ContentType(type: "text", subtype: "plain") 40 | let html = ContentType(type: "text", subtype: "html") 41 | let other = ContentType(type: "foo", subtype: "bar") 42 | XCTAssertEqual(plain.index, 0) 43 | XCTAssertEqual(html.index, 1) 44 | XCTAssertEqual(other.index, 2) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/SendGridTests/Structs/PaginationTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | #if os(Linux) 4 | import FoundationNetworking 5 | #endif 6 | 7 | class PaginationTests: XCTestCase { 8 | func testUrlResponseInit() { 9 | let url = URL(fileURLWithPath: "/foo") 10 | let sample = """ 11 | ; rel="next"; title="2", ; rel="prev"; title="1", ; rel="last"; title="117", ; rel="first"; title="1" 12 | """ 13 | let headers: [String: String] = [ 14 | "Link": sample 15 | ] 16 | let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: headers) 17 | 18 | guard let pages = Pagination(response: response) else { 19 | XCTFail("Received `nil` from Pagination initializer.") 20 | return 21 | } 22 | 23 | XCTAssertEqual(pages.first, Page(limit: 500, offset: 0)) 24 | XCTAssertEqual(pages.previous, Page(limit: 500, offset: 500)) 25 | XCTAssertEqual(pages.next, Page(limit: 500, offset: 1500)) 26 | XCTAssertEqual(pages.last, Page(limit: 500, offset: 58000)) 27 | } 28 | 29 | func testNoHeader() { 30 | let url = URL(fileURLWithPath: "/foo") 31 | let headers: [String: String] = [ 32 | "Foo": "Bar" 33 | ] 34 | let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: headers) 35 | let pages = Pagination(response: response) 36 | XCTAssertNil(pages) 37 | } 38 | 39 | func testBadHeaders() { 40 | let url = URL(fileURLWithPath: "/foo") 41 | let sample = """ 42 | ; rel="next"; title="2", foo, bar, baz 43 | """ 44 | let headers: [String: String] = [ 45 | "Link": sample 46 | ] 47 | let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: headers) 48 | let pages = Pagination(response: response) 49 | XCTAssertNil(pages?.first) 50 | XCTAssertNil(pages?.previous) 51 | XCTAssertNil(pages?.next) 52 | XCTAssertNil(pages?.last) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/SendGridTests/Structs/RateLimitTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | #if os(Linux) 4 | import FoundationNetworking 5 | #endif 6 | 7 | class RateLimitTests: XCTestCase { 8 | func testUrlResponseInitializer() { 9 | let url = URL(fileURLWithPath: "/foo") 10 | let headers: [String: String] = [ 11 | "X-RateLimit-Limit": "500", 12 | "X-RateLimit-Remaining": "499", 13 | "X-RateLimit-Reset": "1466435354" 14 | ] 15 | let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: headers) 16 | 17 | guard let info = RateLimit(response: response) else { 18 | XCTFail("Received `nil` from Rate limit initializer.") 19 | return 20 | } 21 | 22 | XCTAssertEqual(info.limit, 500) 23 | XCTAssertEqual(info.remaining, 499) 24 | XCTAssertEqual(info.resetDate.timeIntervalSince1970, 1466435354) 25 | } 26 | 27 | func testBadHeaders() { 28 | let url = URL(fileURLWithPath: "/foo") 29 | let headers: [String: String] = [ 30 | "X-RateLimit-Limit": "foo", 31 | "X-RateLimit-Remaining": "bar", 32 | "X-RateLimit-Reset": "baz" 33 | ] 34 | let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: headers) 35 | 36 | let info = RateLimit(response: response) 37 | XCTAssertNil(info) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/SendGridTests/Structs/ValidateTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SendGrid 2 | import XCTest 3 | 4 | class ValidateTests: XCTestCase { 5 | func testInput() { 6 | let pass = Validate.input("Hello World", against: "World") 7 | let fail = Validate.input("Hello World", against: "Foo") 8 | XCTAssertTrue(pass) 9 | XCTAssertFalse(fail) 10 | 11 | let badPattern = Validate.input("(", against: "(") 12 | XCTAssertFalse(badPattern) 13 | } 14 | 15 | func testEmail() { 16 | for goodEmail in ["foo@example.none", "foo@example.co.none", "अजय@डाटा.भारत", "用户@例子.广告"] { 17 | XCTAssertTrue(Validate.email(goodEmail), "'\(goodEmail)' validates successfully") 18 | } 19 | for badEmail in ["example.none", "foo@example"] { 20 | XCTAssertFalse(Validate.email(badEmail), "'\(badEmail)' fails validation") 21 | } 22 | } 23 | 24 | func testSubscriptionTracking() { 25 | let valid = [ 26 | "To unsubscribe, <% click here %>.", 27 | "To unsubscribe, <% click here%>." 28 | ] 29 | let invalid = [ 30 | "To unsubscribe, click here.", 31 | "To unsubscribe, <%click here %>." 32 | ] 33 | for body in valid { 34 | XCTAssertTrue(Validate.subscriptionTracking(body: body), body) 35 | } 36 | for body in invalid { 37 | XCTAssertFalse(Validate.subscriptionTracking(body: body), body) 38 | } 39 | } 40 | 41 | func testNoCLRF() { 42 | for input in ["foo", "foo/bar"] { 43 | XCTAssertTrue(Validate.noCLRF(in: input), "'\(input)' validates successfully") 44 | } 45 | for input in ["foo bar", "foo;bar", "foo,bar", ""] { 46 | XCTAssertFalse(Validate.noCLRF(in: input), "'\(input)' fails validation") 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | command: swift build 8 | volumes: 9 | - ./:/app --------------------------------------------------------------------------------