├── .swift-version
├── logo.png
├── Tests
├── LinuxMain.swift
└── WrapTests
│ ├── WrapTests+Linux.swift
│ └── WrapTests.swift
├── Wrap.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcshareddata
│ └── xcschemes
│ │ ├── Wrap-watchOS.xcscheme
│ │ ├── Wrap-iOS.xcscheme
│ │ ├── Wrap-tvOS.xcscheme
│ │ └── Wrap-macOS.xcscheme
└── project.pbxproj
├── .travis.yml
├── buddybuild_postbuild.sh
├── Package.swift
├── Configs
├── WrapTests.plist
└── Wrap.plist
├── Wrap.podspec
├── LICENSE
├── .gitignore
├── CODE_OF_CONDUCT.md
├── README.md
└── Sources
└── Wrap.swift
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.0
2 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohnSundell/Wrap/HEAD/logo.png
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import WrapTests
3 |
4 | XCTMain([
5 | testCase(WrapTests.allTests),
6 | ])
7 |
--------------------------------------------------------------------------------
/Wrap.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os: linux
2 | language: generic
3 | sudo: required
4 | dist: trusty
5 | install:
6 | - eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)"
7 | script:
8 | - swift test
9 |
--------------------------------------------------------------------------------
/buddybuild_postbuild.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Run tests using SPM for macOS
4 | swift test
5 |
6 | # Run tests for tvOS
7 | xcodebuild clean test -quiet -project Wrap.xcodeproj -scheme Wrap-tvOS -destination "platform=tvOS Simulator,name=Apple TV 1080p" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO
8 |
9 | # Build for watchOS
10 | xcodebuild clean build -project Wrap.xcodeproj -scheme Wrap-watchOS CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO
11 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.0
2 |
3 | /**
4 | * Wrap
5 | * Copyright (c) John Sundell 2017
6 | * Licensed under the MIT license. See LICENSE file.
7 | */
8 |
9 | import PackageDescription
10 |
11 | let package = Package(
12 | name: "Wrap",
13 | products: [
14 | .library(name: "Wrap", targets: ["Wrap"])
15 | ],
16 | targets: [
17 | .target(
18 | name: "Wrap",
19 | path: "Sources"
20 | ),
21 | .testTarget(
22 | name: "WrapTests",
23 | dependencies: ["Wrap"]
24 | )
25 | ]
26 | )
27 |
--------------------------------------------------------------------------------
/Configs/WrapTests.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Configs/Wrap.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSHumanReadableCopyright
24 | Copyright © 2015 - 2017 John Sundell. All rights reserved.
25 | NSPrincipalClass
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Wrap.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "Wrap"
3 | s.version = "3.0.0"
4 | s.summary = "The easy to use Swift JSON encoder"
5 | s.description = <<-DESC
6 | Wrap is an easy to use Swift JSON encoder. Don't spend hours writing JSON encoding code - just wrap it instead!
7 |
8 | Using Wrap is as easy as calling Wrap() on any instance of a class or struct that you wish to encode. It automatically encodes all of your type’s properties, including nested objects, collections, enums and more!
9 | DESC
10 | s.homepage = "https://github.com/JohnSundell/Wrap"
11 | s.license = { :type => "MIT", :file => "LICENSE" }
12 | s.author = { "John Sundell" => "john@sundell.co" }
13 | s.social_media_url = "https://twitter.com/johnsundell"
14 | s.ios.deployment_target = "8.0"
15 | s.osx.deployment_target = "10.9"
16 | s.watchos.deployment_target = "2.0"
17 | s.tvos.deployment_target = "9.0"
18 | s.source = { :git => "https://github.com/JohnSundell/Wrap.git", :tag => s.version.to_s }
19 | s.source_files = "Sources/Wrap.swift"
20 | s.framework = "Foundation"
21 | end
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 - 2017 John Sundell
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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | .build/
41 |
42 | # CocoaPods
43 | #
44 | # We recommend against adding the Pods directory to your .gitignore. However
45 | # you should judge for yourself, the pros and cons are mentioned at:
46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
47 | #
48 | # Pods/
49 |
50 | # Carthage
51 | #
52 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
53 | # Carthage/Checkouts
54 |
55 | Carthage/Build
56 |
57 | # fastlane
58 | #
59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
60 | # screenshots whenever they are needed.
61 | # For more information about the recommended setup visit:
62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
63 |
64 | fastlane/report.xml
65 | fastlane/Preview.html
66 | fastlane/screenshots
67 | fastlane/test_output
68 |
--------------------------------------------------------------------------------
/Wrap.xcodeproj/xcshareddata/xcschemes/Wrap-watchOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
34 |
35 |
36 |
37 |
48 |
49 |
55 |
56 |
57 |
58 |
59 |
60 |
66 |
67 |
73 |
74 |
75 |
76 |
78 |
79 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Wrap Code of Conduct
2 |
3 | Below is the Code of Conduct that all contributors and participants in the Wrap community are expected to adhere to.
4 | It's adopted from the [Contributor Covenant Code of Conduct][homepage].
5 |
6 | ## Our Pledge
7 |
8 | In the interest of fostering an open and welcoming environment, we as
9 | contributors and maintainers pledge to making participation in our project and
10 | our community a harassment-free experience for everyone, regardless of age, body
11 | size, disability, ethnicity, gender identity and expression, level of experience,
12 | nationality, personal appearance, race, religion, or sexual identity and
13 | orientation.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to creating a positive environment
18 | include:
19 |
20 | * Using welcoming and inclusive language
21 | * Being respectful of differing viewpoints and experiences
22 | * Gracefully accepting constructive criticism
23 | * Focusing on what is best for the community
24 | * Showing empathy towards other community members
25 |
26 | Examples of unacceptable behavior by participants include:
27 |
28 | * The use of sexualized language or imagery and unwelcome sexual attention or
29 | advances
30 | * Trolling, insulting/derogatory comments, and personal or political attacks
31 | * Public or private harassment
32 | * Publishing others' private information, such as a physical or electronic
33 | address, without explicit permission
34 | * Other conduct which could reasonably be considered inappropriate in a
35 | professional setting
36 |
37 | ## Our Responsibilities
38 |
39 | Project maintainers are responsible for clarifying the standards of acceptable
40 | behavior and are expected to take appropriate and fair corrective action in
41 | response to any instances of unacceptable behavior.
42 |
43 | Project maintainers have the right and responsibility to remove, edit, or
44 | reject comments, commits, code, wiki edits, issues, and other contributions
45 | that are not aligned to this Code of Conduct, or to ban temporarily or
46 | permanently any contributor for other behaviors that they deem inappropriate,
47 | threatening, offensive, or harmful.
48 |
49 | ## Scope
50 |
51 | This Code of Conduct applies both within project spaces and in public spaces
52 | when an individual is representing the project or its community. Examples of
53 | representing a project or community include using an official project e-mail
54 | address, posting via an official social media account, or acting as an appointed
55 | representative at an online or offline event. Representation of a project may be
56 | further defined and clarified by project maintainers.
57 |
58 | ## Enforcement
59 |
60 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
61 | reported by contacting the project leader at john@sundell.co. All
62 | complaints will be reviewed and investigated and will result in a response that
63 | is deemed necessary and appropriate to the circumstances. The project team is
64 | obligated to maintain confidentiality with regard to the reporter of an incident.
65 | Further details of specific enforcement policies may be posted separately.
66 |
67 | Project maintainers who do not follow or enforce the Code of Conduct in good
68 | faith may face temporary or permanent repercussions as determined by other
69 | members of the project's leadership.
70 |
71 | ## Attribution
72 |
73 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
74 | available at [http://contributor-covenant.org/version/1/4][version]
75 |
76 | [homepage]: http://contributor-covenant.org
77 | [version]: http://contributor-covenant.org/version/1/4/
78 |
--------------------------------------------------------------------------------
/Tests/WrapTests/WrapTests+Linux.swift:
--------------------------------------------------------------------------------
1 | /**
2 | * Wrap
3 | *
4 | * Copyright (c) 2015 - 2017 John Sundell. Licensed under the MIT license, as follows:
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | import Foundation
26 |
27 | #if os(Linux)
28 | extension WrapTests {
29 | static var allTests = [
30 | ("testBasicStruct", testBasicStruct),
31 | ("testOptionalProperties", testOptionalProperties),
32 | ("testProtocolProperties", testProtocolProperties),
33 | ("testRootEnum", testRootEnum),
34 | ("testEnumProperties", testEnumProperties),
35 | ("testDateProperty", testDateProperty),
36 | ("testDatePropertyWithCustomizableStruct", testDatePropertyWithCustomizableStruct),
37 | ("testEmptyStruct", testEmptyStruct),
38 | ("testNestedEmptyStruct", testNestedEmptyStruct),
39 | ("testArrayProperties", testArrayProperties),
40 | ("testDictionaryProperties", testDictionaryProperties),
41 | ("testHomogeneousSetProperty", testHomogeneousSetProperty),
42 | ("testNSURLProperty", testNSURLProperty),
43 | ("testURLProperty", testURLProperty),
44 | ("test64BitIntegerProperties", test64BitIntegerProperties),
45 | ("testRootSubclass", testRootSubclass),
46 | ("testRootNSObjectSubclass", testRootNSObjectSubclass),
47 | ("testRootDictionary", testRootDictionary),
48 | ("testNestedStruct", testNestedStruct),
49 | ("testNestedArrayOfStructs", testNestedArrayOfStructs),
50 | ("testNestedDictionariesOfStructs", testNestedDictionariesOfStructs),
51 | ("testNestedSubclass", testNestedSubclass),
52 | ("testDeepNesting", testDeepNesting),
53 | ("testWrappableKey", testWrappableKey),
54 | ("testKeyCustomization", testKeyCustomization),
55 | ("testCustomWrapping", testCustomWrapping),
56 | ("testCustomWrappingCallingWrapFunction", testCustomWrappingCallingWrapFunction),
57 | ("testCustomWrappingForSingleProperty", testCustomWrappingForSingleProperty),
58 | ("testCustomWrappingFailureThrows", testCustomWrappingFailureThrows),
59 | ("testCustomWrappingForSinglePropertyFailureThrows", testCustomWrappingForSinglePropertyFailureThrows),
60 | ("testInvalidRootObjectThrows", testInvalidRootObjectThrows),
61 | ("testDataWrapping", testDataWrapping),
62 | ("testWrappingArray", testWrappingArray),
63 | ("testSnakeCasedKeyWrapping", testSnakeCasedKeyWrapping),
64 | ("testContext", testContext),
65 | ("testInheritance", testInheritance),
66 | ("testIgnoringClosureProperties", testIgnoringClosureProperties)
67 | ]
68 | }
69 | #endif
70 |
--------------------------------------------------------------------------------
/Wrap.xcodeproj/xcshareddata/xcschemes/Wrap-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
35 |
41 |
42 |
43 |
44 |
45 |
51 |
52 |
53 |
54 |
55 |
56 |
67 |
68 |
74 |
75 |
76 |
77 |
78 |
79 |
85 |
86 |
92 |
93 |
94 |
95 |
97 |
98 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/Wrap.xcodeproj/xcshareddata/xcschemes/Wrap-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
35 |
41 |
42 |
43 |
44 |
45 |
51 |
52 |
53 |
54 |
55 |
56 |
67 |
68 |
74 |
75 |
76 |
77 |
78 |
79 |
85 |
86 |
92 |
93 |
94 |
95 |
97 |
98 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/Wrap.xcodeproj/xcshareddata/xcschemes/Wrap-macOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
35 |
41 |
42 |
43 |
44 |
45 |
51 |
52 |
53 |
54 |
55 |
56 |
67 |
68 |
74 |
75 |
76 |
77 |
78 |
79 |
85 |
86 |
92 |
93 |
94 |
95 |
97 |
98 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ⚠️ **DEPRECATED**
2 |
3 | Wrap is deprecated in favor of Swift’s built-in `Codable` API and [the Codextended project](https://github.com/JohnSundell/Codextended). All current users are highly encouraged to migrate to `Codable` as soon as possible.
4 |
5 | ---
6 |
7 |
8 |
9 |
10 |
11 |
12 | Unbox
13 | |
14 | Wrap
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | Wrap is an easy to use Swift JSON encoder. Don't spend hours writing JSON encoding code - just wrap it instead!
34 |
35 | Using Wrap is as easy as calling `wrap()` on any instance of a `class` or `struct` that you wish to encode. It automatically encodes all of your type’s properties, including nested objects, collections, enums and more!
36 |
37 | It also provides a suite of simple but powerful customization APIs that enables you to use it on any model setup with ease.
38 |
39 | ## Basic example
40 |
41 | Say you have your usual-suspect `User` model:
42 |
43 | ```swift
44 | struct User {
45 | let name: String
46 | let age: Int
47 | }
48 |
49 | let user = User(name: "John", age: 28)
50 | ```
51 |
52 | Using `wrap()` you can now encode a `User` instance with one command:
53 |
54 | ```swift
55 | let dictionary: [String : Any] = try wrap(user)
56 | ```
57 |
58 | Which will produce the following `Dictionary`:
59 |
60 | ```json
61 | {
62 | "name": "John",
63 | "age": 28
64 | }
65 | ```
66 |
67 | ## Advanced example
68 |
69 | The first was a pretty simple example, but Wrap can encode even the most complicated structures for you, with both optional, non-optional and custom type values, all without any extra code on your part. Let’s say we have the following model setup:
70 |
71 | ```swift
72 | struct SpaceShip {
73 | let type: SpaceShipType
74 | let weight: Double
75 | let engine: Engine
76 | let passengers: [Astronaut]
77 | let launchLiveStreamURL: URL?
78 | let lastPilot: Astronaut?
79 | }
80 |
81 | enum SpaceShipType: Int, WrappableEnum {
82 | case apollo
83 | case sputnik
84 | }
85 |
86 | struct Engine {
87 | let manufacturer: String
88 | let fuelConsumption: Float
89 | }
90 |
91 | struct Astronaut {
92 | let name: String
93 | }
94 | ```
95 |
96 | Let’s create an instance of `SpaceShip`:
97 |
98 | ```swift
99 | let ship = SpaceShip(
100 | type: .apollo,
101 | weight: 3999.72,
102 | engine: Engine(
103 | manufacturer: "The Space Company",
104 | fuelConsumption: 17.2321
105 | ),
106 | passengers: [
107 | Astronaut(name: "Mike"),
108 | Astronaut(name: "Amanda")
109 | ],
110 | launchLiveStreamURL: URL(string: "http://livestream.com"),
111 | lastPilot: nil
112 | )
113 | ```
114 |
115 | And now let’s encode it with one call to `wrap()`:
116 |
117 | ```swift
118 | let dictionary: WrappedDictionary = try wrap(ship)
119 | ```
120 |
121 | Which will produce the following dictionary:
122 |
123 | ```json
124 | {
125 | "type": 0,
126 | "weight": 3999.72,
127 | "engine": {
128 | "manufacturer": "The Space Company",
129 | "fuelConsumption": 17.2321
130 | },
131 | "passengers": [
132 | {"name": "Mike"},
133 | {"name": "Amanda"}
134 | ],
135 | "launchLiveStreamURL": "http://livestream.com"
136 | }
137 | ```
138 |
139 | As you can see, Wrap automatically encoded the `URL` property to its `absoluteString`, and ignored any properties that were `nil` (reducing the size of the produced JSON).
140 |
141 | ## Customization
142 |
143 | While automation is awesome, customization is just as important. Thankfully, Wrap provides several override points that enables you to easily tweak its default behavior.
144 |
145 | ### Customizing keys
146 |
147 | Per default Wrap uses the property names of a type as its encoding keys, but sometimes this is not what you’re looking for. You can choose to override any or all of a type’s encoding keys by making it conform to `WrapCustomizable` and implementing `keyForWrapping(propertyNamed:)`, like this:
148 |
149 | ```swift
150 | struct Book: WrapCustomizable {
151 | let title: String
152 | let authorName: String
153 |
154 | func keyForWrapping(propertyNamed propertyName: String) -> String? {
155 | if propertyName == "authorName" {
156 | return "author_name"
157 | }
158 |
159 | return propertyName
160 | }
161 | }
162 | ```
163 |
164 | You can also use the `keyForWrapping(propertyNamed:)` API to skip a property entirely, by returning nil from this method for it.
165 |
166 | ### Custom key types
167 |
168 | You might have nested dictionaries that are not keyed on `Strings`, and for those Wrap provides the `WrappableKey` protocol. This enables you to easily convert any type into a string that can be used as a JSON key.
169 |
170 | ### Encoding keys as snake_case
171 |
172 | If you want the dictionary that Wrap produces to have snake_cased keys rather than the default (which is matching the names of the properties that were encoded), you can easily do so by conforming to `WrapCustomizable` and returning `.convertToSnakeCase` from the `wrapKeyStyle` property. Doing that will, for example, convert the property name `myProperty` into the key `my_property`.
173 |
174 | ### Customized wrapping
175 |
176 | For some nested types, you might want to handle the wrapping yourself. This may be especially true for any custom collections, or types that have a completely different representation when encoded. To do that, make a type conform to `WrapCustomizable` and implement `wrap(context:dateFormatter:)`, like this:
177 |
178 | ```swift
179 | struct Library: WrapCustomizable {
180 | private let booksByID: [String : Book]
181 |
182 | func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
183 | return Wrapper(context: context, dateFormatter: dateFormatter).wrap(self.booksByID)
184 | }
185 | }
186 | ```
187 |
188 | ## Enum support
189 |
190 | Wrap also makes it super easy to encode any `enum` values that your types are using. If an `enum` is based on a raw type (such as `String` or `Int`), all you have to do is to declare conformance to `WrappableEnum`, and the rest is taken care of for you.
191 |
192 | Non-raw type `enum` values are also automatically encoded. The default behavior encodes any values that don’t have associated values as their string representation, and those that do have associated values as a dictionary (with the string representation as the key), like this:
193 |
194 | ```swift
195 | enum Profession {
196 | case developer(favoriteLanguageName: String)
197 | case lawyer
198 | }
199 |
200 | struct Person {
201 | let profession = Profession.developer(favoriteLanguageName: "Swift")
202 | let hobbyProfession = Profession.lawyer
203 | }
204 | ```
205 |
206 | Encodes into:
207 |
208 | ```json
209 | {
210 | "profession": {
211 | "developer": "Swift"
212 | },
213 | "hobbyProfession": "lawyer"
214 | }
215 | ```
216 |
217 | ## Contextual objects
218 |
219 | To be able to easily encode any dependencies that you might want to use during the encoding process, Wrap provides the ability to supply a contextual object when initiating the wrapping process (by calling `wrap(object, context: myContext`).
220 |
221 | A context can be of `Any` type and is accessible in all `WrapCustomizable` wrapping methods. Here is an example, where we send in a prefix to add to a `Book`’s `title`:
222 |
223 | ```swift
224 | struct Book: WrapCustomizable {
225 | let title: String
226 |
227 | func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
228 | guard let prefix = context as? String else {
229 | return nil
230 | }
231 |
232 | return [
233 | "title" : prefix + self.title
234 | ]
235 | }
236 | }
237 | ```
238 |
239 | ## String serialization
240 |
241 | ```swift
242 | let data = try wrap(object) as Data
243 | let string = String(data: data, encoding: .utf8)
244 | ```
245 |
246 | ## Compatibility
247 |
248 | Wrap supports the following platforms:
249 |
250 | - 📱 iOS 8+
251 | - 🖥 macOS 10.9+
252 | - ⌚️ watchOS 2+
253 | - 📺 tvOS 9+
254 | - 🐧 Linux
255 |
256 | ## Usage
257 |
258 | Wrap can be easily used in either a Swift script, command line tool or in an app for iOS, macOS, watchOS, tvOS or Linux.
259 |
260 | ### In an application
261 |
262 | Either
263 |
264 | - Drag the file `Wrap.swift` into your application's Xcode project.
265 |
266 | or
267 |
268 | - Use [CocoaPods](cocoapods.org), [Carthage](https://github.com/Carthage/Carthage) or the [Swift Package Manager](https://github.com/apple/swift-package-manager) to include Wrap as a dependency in your project.
269 |
270 | ### In a script
271 |
272 | - Install [Marathon](https://github.com/johnsundell/marathon).
273 | - Add Wrap using `$ marathon add https://github.com/JohnSundell/Wrap.git`.
274 | - Run your script using `$ marathon run `.
275 |
276 | ### In a command line tool
277 |
278 | - Drag the file `Wrap.swift` into your command line tool's Xcode project.
279 |
280 | ### Hope you enjoy wrapping your objects!
281 |
282 | For more updates on Wrap, and my other open source projects, follow me on Twitter: [@johnsundell](http://www.twitter.com/johnsundell)
283 |
284 | Also make sure to check out [Unbox](http://github.com/johnsundell/unbox) that let’s you easily **decode** JSON.
285 |
--------------------------------------------------------------------------------
/Sources/Wrap.swift:
--------------------------------------------------------------------------------
1 | /**
2 | * Wrap - the easy to use Swift JSON encoder
3 | *
4 | * For usage, see documentation of the classes/symbols listed in this file, as well
5 | * as the guide available at: github.com/johnsundell/wrap
6 | *
7 | * Copyright (c) 2015 - 2017 John Sundell. Licensed under the MIT license, as follows:
8 | *
9 | * Permission is hereby granted, free of charge, to any person obtaining a copy
10 | * of this software and associated documentation files (the "Software"), to deal
11 | * in the Software without restriction, including without limitation the rights
12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | * copies of the Software, and to permit persons to whom the Software is
14 | * furnished to do so, subject to the following conditions:
15 | *
16 | * The above copyright notice and this permission notice shall be included in all
17 | * copies or substantial portions of the Software.
18 | *
19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 | * SOFTWARE.
26 | */
27 |
28 | import Foundation
29 |
30 | /// Type alias defining what type of Dictionary that Wrap produces
31 | public typealias WrappedDictionary = [String : Any]
32 |
33 | /**
34 | * Wrap any object or value, encoding it into a JSON compatible Dictionary
35 | *
36 | * - Parameter object: The object to encode
37 | * - Parameter context: An optional contextual object that will be available throughout
38 | * the wrapping process. Can be used to inject extra information or objects needed to
39 | * perform the wrapping.
40 | * - Parameter dateFormatter: Optionally pass in a date formatter to use to encode any
41 | * `NSDate` values found while encoding the object. If this is `nil`, any found date
42 | * values will be encoded using the "yyyy-MM-dd HH:mm:ss" format.
43 | *
44 | * All the type's stored properties (both public & private) will be recursively
45 | * encoded with their property names as the key. For example, given the following
46 | * Struct as input:
47 | *
48 | * ```
49 | * struct User {
50 | * let name = "John"
51 | * let age = 28
52 | * }
53 | * ```
54 | *
55 | * This function will produce the following output:
56 | *
57 | * ```
58 | * [
59 | * "name" : "John",
60 | * "age" : 28
61 | * ]
62 | * ```
63 | *
64 | * The object passed to this function must be an instance of a Class, or a value
65 | * based on a Struct. Standard library values, such as Ints, Strings, etc are not
66 | * valid input.
67 | *
68 | * Throws a WrapError if the operation could not be completed.
69 | *
70 | * For more customization options, make your type conform to `WrapCustomizable`,
71 | * that lets you override encoding keys and/or the whole wrapping process.
72 | *
73 | * See also `WrappableKey` (for dictionary keys) and `WrappableEnum` for Enum values.
74 | */
75 | public func wrap(_ object: T, context: Any? = nil, dateFormatter: DateFormatter? = nil) throws -> WrappedDictionary {
76 | return try Wrapper(context: context, dateFormatter: dateFormatter).wrap(object: object, enableCustomizedWrapping: true)
77 | }
78 |
79 | /**
80 | * Alternative `wrap()` overload that returns JSON-based `Data`
81 | *
82 | * See the documentation for the dictionary-based `wrap()` function for more information
83 | */
84 | public func wrap(_ object: T, writingOptions: JSONSerialization.WritingOptions? = nil, context: Any? = nil, dateFormatter: DateFormatter? = nil) throws -> Data {
85 | return try Wrapper(context: context, dateFormatter: dateFormatter).wrap(object: object, writingOptions: writingOptions ?? [])
86 | }
87 |
88 | /**
89 | * Alternative `wrap()` overload that encodes an array of objects into an array of dictionaries
90 | *
91 | * See the documentation for the dictionary-based `wrap()` function for more information
92 | */
93 | public func wrap(_ objects: [T], context: Any? = nil, dateFormatter: DateFormatter? = nil) throws -> [WrappedDictionary] {
94 | return try objects.map { try wrap($0, context: context, dateFormatter: dateFormatter) }
95 | }
96 |
97 | /**
98 | * Alternative `wrap()` overload that encodes an array of objects into JSON-based `Data`
99 | *
100 | * See the documentation for the dictionary-based `wrap()` function for more information
101 | */
102 | public func wrap(_ objects: [T], writingOptions: JSONSerialization.WritingOptions? = nil, context: Any? = nil, dateFormatter: DateFormatter? = nil) throws -> Data {
103 | let dictionaries: [WrappedDictionary] = try wrap(objects, context: context, dateFormatter: dateFormatter)
104 | return try JSONSerialization.data(withJSONObject: dictionaries, options: writingOptions ?? [])
105 | }
106 |
107 | // Enum describing various styles of keys in a wrapped dictionary
108 | public enum WrapKeyStyle {
109 | /// The keys in a dictionary produced by Wrap should match their property name (default)
110 | case matchPropertyName
111 | /// The keys in a dictionary produced by Wrap should be converted to snake_case.
112 | /// For example, "myProperty" will be converted to "my_property". All keys will be lowercased.
113 | case convertToSnakeCase
114 | }
115 |
116 | /**
117 | * Protocol providing the main customization point for Wrap
118 | *
119 | * It's optional to implement all of the methods in this protocol, as Wrap
120 | * supplies default implementations of them.
121 | */
122 | public protocol WrapCustomizable {
123 | /**
124 | * The style that wrap should apply to the keys of a wrapped dictionary
125 | *
126 | * The value of this property is ignored if a type provides a custom
127 | * implementation of the `keyForWrapping(propertyNamed:)` method.
128 | */
129 | var wrapKeyStyle: WrapKeyStyle { get }
130 | /**
131 | * Override the wrapping process for this type
132 | *
133 | * All top-level types should return a `WrappedDictionary` from this method.
134 | *
135 | * You may use the default wrapping implementation by using a `Wrapper`, but
136 | * never call `wrap()` from an implementation of this method, since that might
137 | * cause an infinite recursion.
138 | *
139 | * The context & dateFormatter passed to this method is any formatter that you
140 | * supplied when initiating the wrapping process by calling `wrap()`.
141 | *
142 | * Returning nil from this method will be treated as an error, and cause
143 | * a `WrapError.wrappingFailedForObject()` error to be thrown.
144 | */
145 | func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any?
146 | /**
147 | * Override the key that will be used when encoding a certain property
148 | *
149 | * Returning nil from this method will cause Wrap to skip the property
150 | */
151 | func keyForWrapping(propertyNamed propertyName: String) -> String?
152 | /**
153 | * Override the wrapping of any property of this type
154 | *
155 | * The original value passed to this method will be the original value that the
156 | * type is currently storing for the property. You can choose to either use this,
157 | * or just access the property in question directly.
158 | *
159 | * The dateFormatter passed to this method is any formatter that you supplied
160 | * when initiating the wrapping process by calling `wrap()`.
161 | *
162 | * Returning nil from this method will cause Wrap to use the default
163 | * wrapping mechanism for the property, so you can choose which properties
164 | * you want to customize the wrapping for.
165 | *
166 | * If you encounter an error while attempting to wrap the property in question,
167 | * you can choose to throw. This will cause a WrapError.WrappingFailedForObject
168 | * to be thrown from the main `wrap()` call that started the process.
169 | */
170 | func wrap(propertyNamed propertyName: String, originalValue: Any, context: Any?, dateFormatter: DateFormatter?) throws -> Any?
171 | }
172 |
173 | /// Protocol implemented by types that may be used as keys in a wrapped Dictionary
174 | public protocol WrappableKey {
175 | /// Convert this type into a key that can be used in a wrapped Dictionary
176 | func toWrappedKey() -> String
177 | }
178 |
179 | /**
180 | * Protocol implemented by Enums to enable them to be directly wrapped
181 | *
182 | * If an Enum implementing this protocol conforms to `RawRepresentable` (it's based
183 | * on a raw type), no further implementation is required. If you wish to customize
184 | * how the Enum is wrapped, you can use the APIs in `WrapCustomizable`.
185 | */
186 | public protocol WrappableEnum: WrapCustomizable {}
187 |
188 | /// Protocol implemented by Date types to enable them to be wrapped
189 | public protocol WrappableDate {
190 | /// Wrap the date using a date formatter, generating a string representation
191 | func wrap(dateFormatter: DateFormatter) -> String
192 | }
193 |
194 | /**
195 | * Class used to wrap an object or value. Use this in any custom `wrap()` implementations
196 | * in case you only want to add on top of the default implementation.
197 | *
198 | * You normally don't have to interact with this API. Use the `wrap()` function instead
199 | * to wrap an object from top-level code.
200 | */
201 | public class Wrapper {
202 | fileprivate let context: Any?
203 | fileprivate var dateFormatter: DateFormatter?
204 |
205 | /**
206 | * Initialize an instance of this class
207 | *
208 | * - Parameter context: An optional contextual object that will be available throughout the
209 | * wrapping process. Can be used to inject extra information or objects needed to perform
210 | * the wrapping.
211 | * - Parameter dateFormatter: Any specific date formatter to use to encode any found `NSDate`
212 | * values. If this is `nil`, any found date values will be encoded using the "yyyy-MM-dd
213 | * HH:mm:ss" format.
214 | */
215 | public init(context: Any? = nil, dateFormatter: DateFormatter? = nil) {
216 | self.context = context
217 | self.dateFormatter = dateFormatter
218 | }
219 |
220 | /// Perform automatic wrapping of an object or value. For more information, see `Wrap()`.
221 | public func wrap(object: Any) throws -> WrappedDictionary {
222 | return try self.wrap(object: object, enableCustomizedWrapping: false)
223 | }
224 | }
225 |
226 | /// Error type used by Wrap
227 | public enum WrapError: Error {
228 | /// Thrown when an invalid top level object (such as a String or Int) was passed to `Wrap()`
229 | case invalidTopLevelObject(Any)
230 | /// Thrown when an object couldn't be wrapped. This is a last resort error.
231 | case wrappingFailedForObject(Any)
232 | }
233 |
234 | // MARK: - Default protocol implementations
235 |
236 | /// Extension containing default implementations of `WrapCustomizable`. Override as you see fit.
237 | public extension WrapCustomizable {
238 | var wrapKeyStyle: WrapKeyStyle {
239 | return .matchPropertyName
240 | }
241 |
242 | func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
243 | return try? Wrapper(context: context, dateFormatter: dateFormatter).wrap(object: self)
244 | }
245 |
246 | func keyForWrapping(propertyNamed propertyName: String) -> String? {
247 | switch self.wrapKeyStyle {
248 | case .matchPropertyName:
249 | return propertyName
250 | case .convertToSnakeCase:
251 | return self.convertPropertyNameToSnakeCase(propertyName: propertyName)
252 | }
253 | }
254 |
255 | func wrap(propertyNamed propertyName: String, originalValue: Any, context: Any?, dateFormatter: DateFormatter?) throws -> Any? {
256 | return try Wrapper(context: context, dateFormatter: dateFormatter).wrap(value: originalValue, propertyName: propertyName)
257 | }
258 | }
259 |
260 | /// Extension adding convenience APIs to `WrapCustomizable` types
261 | public extension WrapCustomizable {
262 | /// Convert a given property name (assumed to be camelCased) to snake_case
263 | func convertPropertyNameToSnakeCase(propertyName: String) -> String {
264 | let regex = try! NSRegularExpression(pattern: "(?<=[a-z])([A-Z])|([A-Z])(?=[a-z])", options: [])
265 | let range = NSRange(location: 0, length: propertyName.count)
266 | let camelCasePropertyName = regex.stringByReplacingMatches(in: propertyName, options: [], range: range, withTemplate: "_$1$2")
267 | return camelCasePropertyName.lowercased()
268 | }
269 | }
270 |
271 | /// Extension providing a default wrapping implementation for `RawRepresentable` Enums
272 | public extension WrappableEnum where Self: RawRepresentable {
273 | public func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
274 | return self.rawValue
275 | }
276 | }
277 |
278 | /// Extension customizing how Arrays are wrapped
279 | extension Array: WrapCustomizable {
280 | public func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
281 | return try? Wrapper(context: context, dateFormatter: dateFormatter).wrap(collection: self)
282 | }
283 | }
284 |
285 | /// Extension customizing how Dictionaries are wrapped
286 | extension Dictionary: WrapCustomizable {
287 | public func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
288 | return try? Wrapper(context: context, dateFormatter: dateFormatter).wrap(dictionary: self)
289 | }
290 | }
291 |
292 | /// Extension customizing how Sets are wrapped
293 | extension Set: WrapCustomizable {
294 | public func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
295 | return try? Wrapper(context: context, dateFormatter: dateFormatter).wrap(collection: self)
296 | }
297 | }
298 |
299 | /// Extension customizing how Int64s are wrapped, ensuring compatbility with 32 bit systems
300 | extension Int64: WrapCustomizable {
301 | public func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
302 | return NSNumber(value: self)
303 | }
304 | }
305 |
306 | /// Extension customizing how UInt64s are wrapped, ensuring compatbility with 32 bit systems
307 | extension UInt64: WrapCustomizable {
308 | public func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
309 | return NSNumber(value: self)
310 | }
311 | }
312 |
313 | /// Extension customizing how NSStrings are wrapped
314 | extension NSString: WrapCustomizable {
315 | public func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
316 | return self
317 | }
318 | }
319 |
320 | /// Extension customizing how NSURLs are wrapped
321 | extension NSURL: WrapCustomizable {
322 | public func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
323 | return self.absoluteString
324 | }
325 | }
326 |
327 | /// Extension customizing how URLs are wrapped
328 | extension URL: WrapCustomizable {
329 | public func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
330 | return self.absoluteString
331 | }
332 | }
333 |
334 |
335 | /// Extension customizing how NSArrays are wrapped
336 | extension NSArray: WrapCustomizable {
337 | public func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
338 | return try? Wrapper(context: context, dateFormatter: dateFormatter).wrap(collection: Array(self))
339 | }
340 | }
341 |
342 | #if !os(Linux)
343 | /// Extension customizing how NSDictionaries are wrapped
344 | extension NSDictionary: WrapCustomizable {
345 | public func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
346 | return try? Wrapper(context: context, dateFormatter: dateFormatter).wrap(dictionary: self as [NSObject : AnyObject])
347 | }
348 | }
349 | #endif
350 |
351 | /// Extension making Int a WrappableKey
352 | extension Int: WrappableKey {
353 | public func toWrappedKey() -> String {
354 | return String(self)
355 | }
356 | }
357 |
358 | /// Extension making Date a WrappableDate
359 | extension Date: WrappableDate {
360 | public func wrap(dateFormatter: DateFormatter) -> String {
361 | return dateFormatter.string(from: self)
362 | }
363 | }
364 |
365 | #if !os(Linux)
366 | /// Extension making NSdate a WrappableDate
367 | extension NSDate: WrappableDate {
368 | public func wrap(dateFormatter: DateFormatter) -> String {
369 | return dateFormatter.string(from: self as Date)
370 | }
371 | }
372 | #endif
373 |
374 | // MARK: - Private
375 |
376 | private extension Wrapper {
377 | func wrap(object: T, enableCustomizedWrapping: Bool) throws -> WrappedDictionary {
378 | if enableCustomizedWrapping {
379 | if let customizable = object as? WrapCustomizable {
380 | let wrapped = try self.performCustomWrapping(object: customizable)
381 |
382 | guard let wrappedDictionary = wrapped as? WrappedDictionary else {
383 | throw WrapError.invalidTopLevelObject(object)
384 | }
385 |
386 | return wrappedDictionary
387 | }
388 | }
389 |
390 | var mirrors = [Mirror]()
391 | var currentMirror: Mirror? = Mirror(reflecting: object)
392 |
393 | while let mirror = currentMirror {
394 | mirrors.append(mirror)
395 | currentMirror = mirror.superclassMirror
396 | }
397 |
398 | return try self.performWrapping(object: object, mirrors: mirrors.reversed())
399 | }
400 |
401 | func wrap(object: T, writingOptions: JSONSerialization.WritingOptions) throws -> Data {
402 | let dictionary = try self.wrap(object: object, enableCustomizedWrapping: true)
403 | return try JSONSerialization.data(withJSONObject: dictionary, options: writingOptions)
404 | }
405 |
406 | func wrap(value: T, propertyName: String? = nil) throws -> Any? {
407 | if let customizable = value as? WrapCustomizable {
408 | return try self.performCustomWrapping(object: customizable)
409 | }
410 |
411 | if let date = value as? WrappableDate {
412 | return self.wrap(date: date)
413 | }
414 |
415 | let mirror = Mirror(reflecting: value)
416 |
417 | if mirror.children.isEmpty {
418 | if let displayStyle = mirror.displayStyle {
419 | switch displayStyle {
420 | case .enum:
421 | if let wrappableEnum = value as? WrappableEnum {
422 | if let wrapped = wrappableEnum.wrap(context: self.context, dateFormatter: self.dateFormatter) {
423 | return wrapped
424 | }
425 |
426 | throw WrapError.wrappingFailedForObject(value)
427 | }
428 |
429 | return "\(value)"
430 | case .struct:
431 | return [:]
432 | default:
433 | return value
434 | }
435 | }
436 |
437 | if !(value is CustomStringConvertible) {
438 | if String(describing: value) == "(Function)" {
439 | return nil
440 | }
441 | }
442 |
443 | return value
444 | } else if value is ExpressibleByNilLiteral && mirror.children.count == 1 {
445 | if let firstMirrorChild = mirror.children.first {
446 | return try self.wrap(value: firstMirrorChild.value, propertyName: propertyName)
447 | }
448 | }
449 |
450 | return try self.wrap(object: value, enableCustomizedWrapping: false)
451 | }
452 |
453 | func wrap(collection: T) throws -> [Any] {
454 | var wrappedArray = [Any]()
455 | let wrapper = Wrapper(context: self.context, dateFormatter: self.dateFormatter)
456 |
457 | for element in collection {
458 | if let wrapped = try wrapper.wrap(value: element) {
459 | wrappedArray.append(wrapped)
460 | }
461 | }
462 |
463 | return wrappedArray
464 | }
465 |
466 | func wrap(dictionary: [K : V]) throws -> WrappedDictionary {
467 | var wrappedDictionary = WrappedDictionary()
468 | let wrapper = Wrapper(context: self.context, dateFormatter: self.dateFormatter)
469 |
470 | for (key, value) in dictionary {
471 | let wrappedKey: String?
472 |
473 | if let stringKey = key as? String {
474 | wrappedKey = stringKey
475 | } else if let wrappableKey = key as? WrappableKey {
476 | wrappedKey = wrappableKey.toWrappedKey()
477 | } else if let stringConvertible = key as? CustomStringConvertible {
478 | wrappedKey = stringConvertible.description
479 | } else {
480 | wrappedKey = nil
481 | }
482 |
483 | if let wrappedKey = wrappedKey {
484 | wrappedDictionary[wrappedKey] = try wrapper.wrap(value: value, propertyName: wrappedKey)
485 | }
486 | }
487 |
488 | return wrappedDictionary
489 | }
490 |
491 | func wrap(date: WrappableDate) -> String {
492 | let dateFormatter: DateFormatter
493 |
494 | if let existingFormatter = self.dateFormatter {
495 | dateFormatter = existingFormatter
496 | } else {
497 | dateFormatter = DateFormatter()
498 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
499 | self.dateFormatter = dateFormatter
500 | }
501 |
502 | return date.wrap(dateFormatter: dateFormatter)
503 | }
504 |
505 | func performWrapping(object: T, mirrors: [Mirror]) throws -> WrappedDictionary {
506 | let customizable = object as? WrapCustomizable
507 | var wrappedDictionary = WrappedDictionary()
508 |
509 | for mirror in mirrors {
510 | for property in mirror.children {
511 |
512 | if (property.value as? WrapOptional)?.isNil == true {
513 | continue
514 | }
515 |
516 | guard let propertyName = property.label else {
517 | continue
518 | }
519 |
520 | let wrappingKey: String?
521 |
522 | if let customizable = customizable {
523 | wrappingKey = customizable.keyForWrapping(propertyNamed: propertyName)
524 | } else {
525 | wrappingKey = propertyName
526 | }
527 |
528 | if let wrappingKey = wrappingKey {
529 | if let wrappedProperty = try customizable?.wrap(propertyNamed: propertyName, originalValue: property.value, context: self.context, dateFormatter: self.dateFormatter) {
530 | wrappedDictionary[wrappingKey] = wrappedProperty
531 | } else {
532 | wrappedDictionary[wrappingKey] = try self.wrap(value: property.value, propertyName: propertyName)
533 | }
534 | }
535 | }
536 | }
537 |
538 | return wrappedDictionary
539 | }
540 |
541 | func performCustomWrapping(object: WrapCustomizable) throws -> Any {
542 | guard let wrapped = object.wrap(context: self.context, dateFormatter: self.dateFormatter) else {
543 | throw WrapError.wrappingFailedForObject(object)
544 | }
545 |
546 | return wrapped
547 | }
548 | }
549 |
550 | // MARK: - Nil Handling
551 |
552 | private protocol WrapOptional {
553 | var isNil: Bool { get }
554 | }
555 |
556 | extension Optional : WrapOptional {
557 | var isNil: Bool {
558 | switch self {
559 | case .none:
560 | return true
561 | case .some(let wrapped):
562 | if let nillable = wrapped as? WrapOptional {
563 | return nillable.isNil
564 | }
565 | return false
566 | }
567 | }
568 | }
569 |
--------------------------------------------------------------------------------
/Wrap.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 47;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 52D6D9871BEFF229002C0205 /* Wrap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* Wrap.framework */; };
11 | 52D6D9981BEFF375002C0205 /* Wrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D6D9961BEFF375002C0205 /* Wrap.swift */; };
12 | 52D6D99B1BEFF375002C0205 /* WrapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D6D9971BEFF375002C0205 /* WrapTests.swift */; };
13 | 52D6D9EA1BEFFFA4002C0205 /* Wrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D6D9961BEFF375002C0205 /* Wrap.swift */; };
14 | 52D6DA091BF00081002C0205 /* Wrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D6D9961BEFF375002C0205 /* Wrap.swift */; };
15 | 52D6DA261BF00118002C0205 /* Wrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D6D9961BEFF375002C0205 /* Wrap.swift */; };
16 | DD7502861C68FDDC006590AF /* WrapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D6D9971BEFF375002C0205 /* WrapTests.swift */; };
17 | DD7502881C68FEDE006590AF /* Wrap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6DA0F1BF000BD002C0205 /* Wrap.framework */; };
18 | DD7502921C690C7A006590AF /* Wrap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D9F01BEFFFBE002C0205 /* Wrap.framework */; };
19 | DD75029A1C690CBE006590AF /* WrapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D6D9971BEFF375002C0205 /* WrapTests.swift */; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXContainerItemProxy section */
23 | 52D6D9881BEFF229002C0205 /* PBXContainerItemProxy */ = {
24 | isa = PBXContainerItemProxy;
25 | containerPortal = 52D6D9731BEFF229002C0205 /* Project object */;
26 | proxyType = 1;
27 | remoteGlobalIDString = 52D6D97B1BEFF229002C0205;
28 | remoteInfo = Wrap;
29 | };
30 | DD7502801C68FCFC006590AF /* PBXContainerItemProxy */ = {
31 | isa = PBXContainerItemProxy;
32 | containerPortal = 52D6D9731BEFF229002C0205 /* Project object */;
33 | proxyType = 1;
34 | remoteGlobalIDString = 52D6DA0E1BF000BD002C0205;
35 | remoteInfo = "Wrap-macOS";
36 | };
37 | DD7502931C690C7A006590AF /* PBXContainerItemProxy */ = {
38 | isa = PBXContainerItemProxy;
39 | containerPortal = 52D6D9731BEFF229002C0205 /* Project object */;
40 | proxyType = 1;
41 | remoteGlobalIDString = 52D6D9EF1BEFFFBE002C0205;
42 | remoteInfo = "Wrap-tvOS";
43 | };
44 | /* End PBXContainerItemProxy section */
45 |
46 | /* Begin PBXFileReference section */
47 | 52D6D97C1BEFF229002C0205 /* Wrap.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Wrap.framework; sourceTree = BUILT_PRODUCTS_DIR; };
48 | 52D6D9861BEFF229002C0205 /* Wrap-iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Wrap-iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
49 | 52D6D9961BEFF375002C0205 /* Wrap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Wrap.swift; path = Sources/Wrap.swift; sourceTree = ""; };
50 | 52D6D9971BEFF375002C0205 /* WrapTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WrapTests.swift; path = Tests/WrapTests/WrapTests.swift; sourceTree = ""; };
51 | 52D6D9E21BEFFF6E002C0205 /* Wrap.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Wrap.framework; sourceTree = BUILT_PRODUCTS_DIR; };
52 | 52D6D9F01BEFFFBE002C0205 /* Wrap.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Wrap.framework; sourceTree = BUILT_PRODUCTS_DIR; };
53 | 52D6DA0F1BF000BD002C0205 /* Wrap.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Wrap.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54 | 52E48D2F1E60FA5C0053A668 /* WrapTests+Linux.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "WrapTests+Linux.swift"; path = "Tests/WrapTests/WrapTests+Linux.swift"; sourceTree = ""; };
55 | AD2FAA261CD0B6D800659CF4 /* Wrap.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Wrap.plist; sourceTree = ""; };
56 | AD2FAA281CD0B6E100659CF4 /* WrapTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = WrapTests.plist; sourceTree = ""; };
57 | DD75027A1C68FCFC006590AF /* Wrap-macOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Wrap-macOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
58 | DD75028D1C690C7A006590AF /* Wrap-tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Wrap-tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
59 | /* End PBXFileReference section */
60 |
61 | /* Begin PBXFrameworksBuildPhase section */
62 | 52D6D9781BEFF229002C0205 /* Frameworks */ = {
63 | isa = PBXFrameworksBuildPhase;
64 | buildActionMask = 2147483647;
65 | files = (
66 | );
67 | runOnlyForDeploymentPostprocessing = 0;
68 | };
69 | 52D6D9831BEFF229002C0205 /* Frameworks */ = {
70 | isa = PBXFrameworksBuildPhase;
71 | buildActionMask = 2147483647;
72 | files = (
73 | 52D6D9871BEFF229002C0205 /* Wrap.framework in Frameworks */,
74 | );
75 | runOnlyForDeploymentPostprocessing = 0;
76 | };
77 | 52D6D9DE1BEFFF6E002C0205 /* Frameworks */ = {
78 | isa = PBXFrameworksBuildPhase;
79 | buildActionMask = 2147483647;
80 | files = (
81 | );
82 | runOnlyForDeploymentPostprocessing = 0;
83 | };
84 | 52D6D9EC1BEFFFBE002C0205 /* Frameworks */ = {
85 | isa = PBXFrameworksBuildPhase;
86 | buildActionMask = 2147483647;
87 | files = (
88 | );
89 | runOnlyForDeploymentPostprocessing = 0;
90 | };
91 | 52D6DA0B1BF000BD002C0205 /* Frameworks */ = {
92 | isa = PBXFrameworksBuildPhase;
93 | buildActionMask = 2147483647;
94 | files = (
95 | );
96 | runOnlyForDeploymentPostprocessing = 0;
97 | };
98 | DD7502771C68FCFC006590AF /* Frameworks */ = {
99 | isa = PBXFrameworksBuildPhase;
100 | buildActionMask = 2147483647;
101 | files = (
102 | DD7502881C68FEDE006590AF /* Wrap.framework in Frameworks */,
103 | );
104 | runOnlyForDeploymentPostprocessing = 0;
105 | };
106 | DD75028A1C690C7A006590AF /* Frameworks */ = {
107 | isa = PBXFrameworksBuildPhase;
108 | buildActionMask = 2147483647;
109 | files = (
110 | DD7502921C690C7A006590AF /* Wrap.framework in Frameworks */,
111 | );
112 | runOnlyForDeploymentPostprocessing = 0;
113 | };
114 | /* End PBXFrameworksBuildPhase section */
115 |
116 | /* Begin PBXGroup section */
117 | 52D6D9721BEFF229002C0205 = {
118 | isa = PBXGroup;
119 | children = (
120 | 52D6D9961BEFF375002C0205 /* Wrap.swift */,
121 | 52D6D9971BEFF375002C0205 /* WrapTests.swift */,
122 | 52E48D2F1E60FA5C0053A668 /* WrapTests+Linux.swift */,
123 | 52D6D99C1BEFF38C002C0205 /* Configs */,
124 | 52D6D97D1BEFF229002C0205 /* Products */,
125 | );
126 | sourceTree = "";
127 | };
128 | 52D6D97D1BEFF229002C0205 /* Products */ = {
129 | isa = PBXGroup;
130 | children = (
131 | 52D6D97C1BEFF229002C0205 /* Wrap.framework */,
132 | 52D6D9861BEFF229002C0205 /* Wrap-iOS Tests.xctest */,
133 | 52D6D9E21BEFFF6E002C0205 /* Wrap.framework */,
134 | 52D6D9F01BEFFFBE002C0205 /* Wrap.framework */,
135 | 52D6DA0F1BF000BD002C0205 /* Wrap.framework */,
136 | DD75027A1C68FCFC006590AF /* Wrap-macOS Tests.xctest */,
137 | DD75028D1C690C7A006590AF /* Wrap-tvOS Tests.xctest */,
138 | );
139 | name = Products;
140 | sourceTree = "";
141 | };
142 | 52D6D99C1BEFF38C002C0205 /* Configs */ = {
143 | isa = PBXGroup;
144 | children = (
145 | DD7502721C68FC1B006590AF /* Frameworks */,
146 | DD7502731C68FC20006590AF /* Tests */,
147 | );
148 | path = Configs;
149 | sourceTree = "";
150 | };
151 | DD7502721C68FC1B006590AF /* Frameworks */ = {
152 | isa = PBXGroup;
153 | children = (
154 | AD2FAA261CD0B6D800659CF4 /* Wrap.plist */,
155 | );
156 | name = Frameworks;
157 | sourceTree = "";
158 | };
159 | DD7502731C68FC20006590AF /* Tests */ = {
160 | isa = PBXGroup;
161 | children = (
162 | AD2FAA281CD0B6E100659CF4 /* WrapTests.plist */,
163 | );
164 | name = Tests;
165 | sourceTree = "";
166 | };
167 | /* End PBXGroup section */
168 |
169 | /* Begin PBXHeadersBuildPhase section */
170 | 52D6D9791BEFF229002C0205 /* Headers */ = {
171 | isa = PBXHeadersBuildPhase;
172 | buildActionMask = 2147483647;
173 | files = (
174 | );
175 | runOnlyForDeploymentPostprocessing = 0;
176 | };
177 | 52D6D9DF1BEFFF6E002C0205 /* Headers */ = {
178 | isa = PBXHeadersBuildPhase;
179 | buildActionMask = 2147483647;
180 | files = (
181 | );
182 | runOnlyForDeploymentPostprocessing = 0;
183 | };
184 | 52D6D9ED1BEFFFBE002C0205 /* Headers */ = {
185 | isa = PBXHeadersBuildPhase;
186 | buildActionMask = 2147483647;
187 | files = (
188 | );
189 | runOnlyForDeploymentPostprocessing = 0;
190 | };
191 | 52D6DA0C1BF000BD002C0205 /* Headers */ = {
192 | isa = PBXHeadersBuildPhase;
193 | buildActionMask = 2147483647;
194 | files = (
195 | );
196 | runOnlyForDeploymentPostprocessing = 0;
197 | };
198 | /* End PBXHeadersBuildPhase section */
199 |
200 | /* Begin PBXNativeTarget section */
201 | 52D6D97B1BEFF229002C0205 /* Wrap-iOS */ = {
202 | isa = PBXNativeTarget;
203 | buildConfigurationList = 52D6D9901BEFF229002C0205 /* Build configuration list for PBXNativeTarget "Wrap-iOS" */;
204 | buildPhases = (
205 | 52D6D9771BEFF229002C0205 /* Sources */,
206 | 52D6D9781BEFF229002C0205 /* Frameworks */,
207 | 52D6D9791BEFF229002C0205 /* Headers */,
208 | 52D6D97A1BEFF229002C0205 /* Resources */,
209 | );
210 | buildRules = (
211 | );
212 | dependencies = (
213 | );
214 | name = "Wrap-iOS";
215 | productName = Wrap;
216 | productReference = 52D6D97C1BEFF229002C0205 /* Wrap.framework */;
217 | productType = "com.apple.product-type.framework";
218 | };
219 | 52D6D9851BEFF229002C0205 /* Wrap-iOS Tests */ = {
220 | isa = PBXNativeTarget;
221 | buildConfigurationList = 52D6D9931BEFF229002C0205 /* Build configuration list for PBXNativeTarget "Wrap-iOS Tests" */;
222 | buildPhases = (
223 | 52D6D9821BEFF229002C0205 /* Sources */,
224 | 52D6D9831BEFF229002C0205 /* Frameworks */,
225 | 52D6D9841BEFF229002C0205 /* Resources */,
226 | );
227 | buildRules = (
228 | );
229 | dependencies = (
230 | 52D6D9891BEFF229002C0205 /* PBXTargetDependency */,
231 | );
232 | name = "Wrap-iOS Tests";
233 | productName = WrapTests;
234 | productReference = 52D6D9861BEFF229002C0205 /* Wrap-iOS Tests.xctest */;
235 | productType = "com.apple.product-type.bundle.unit-test";
236 | };
237 | 52D6D9E11BEFFF6E002C0205 /* Wrap-watchOS */ = {
238 | isa = PBXNativeTarget;
239 | buildConfigurationList = 52D6D9E71BEFFF6E002C0205 /* Build configuration list for PBXNativeTarget "Wrap-watchOS" */;
240 | buildPhases = (
241 | 52D6D9DD1BEFFF6E002C0205 /* Sources */,
242 | 52D6D9DE1BEFFF6E002C0205 /* Frameworks */,
243 | 52D6D9DF1BEFFF6E002C0205 /* Headers */,
244 | 52D6D9E01BEFFF6E002C0205 /* Resources */,
245 | );
246 | buildRules = (
247 | );
248 | dependencies = (
249 | );
250 | name = "Wrap-watchOS";
251 | productName = "Wrap-watchOS";
252 | productReference = 52D6D9E21BEFFF6E002C0205 /* Wrap.framework */;
253 | productType = "com.apple.product-type.framework";
254 | };
255 | 52D6D9EF1BEFFFBE002C0205 /* Wrap-tvOS */ = {
256 | isa = PBXNativeTarget;
257 | buildConfigurationList = 52D6DA011BEFFFBE002C0205 /* Build configuration list for PBXNativeTarget "Wrap-tvOS" */;
258 | buildPhases = (
259 | 52D6D9EB1BEFFFBE002C0205 /* Sources */,
260 | 52D6D9EC1BEFFFBE002C0205 /* Frameworks */,
261 | 52D6D9ED1BEFFFBE002C0205 /* Headers */,
262 | 52D6D9EE1BEFFFBE002C0205 /* Resources */,
263 | );
264 | buildRules = (
265 | );
266 | dependencies = (
267 | );
268 | name = "Wrap-tvOS";
269 | productName = "Wrap-tvOS";
270 | productReference = 52D6D9F01BEFFFBE002C0205 /* Wrap.framework */;
271 | productType = "com.apple.product-type.framework";
272 | };
273 | 52D6DA0E1BF000BD002C0205 /* Wrap-macOS */ = {
274 | isa = PBXNativeTarget;
275 | buildConfigurationList = 52D6DA201BF000BD002C0205 /* Build configuration list for PBXNativeTarget "Wrap-macOS" */;
276 | buildPhases = (
277 | 52D6DA0A1BF000BD002C0205 /* Sources */,
278 | 52D6DA0B1BF000BD002C0205 /* Frameworks */,
279 | 52D6DA0C1BF000BD002C0205 /* Headers */,
280 | 52D6DA0D1BF000BD002C0205 /* Resources */,
281 | );
282 | buildRules = (
283 | );
284 | dependencies = (
285 | );
286 | name = "Wrap-macOS";
287 | productName = "Wrap-macOS";
288 | productReference = 52D6DA0F1BF000BD002C0205 /* Wrap.framework */;
289 | productType = "com.apple.product-type.framework";
290 | };
291 | DD7502791C68FCFC006590AF /* Wrap-macOS Tests */ = {
292 | isa = PBXNativeTarget;
293 | buildConfigurationList = DD7502821C68FCFC006590AF /* Build configuration list for PBXNativeTarget "Wrap-macOS Tests" */;
294 | buildPhases = (
295 | DD7502761C68FCFC006590AF /* Sources */,
296 | DD7502771C68FCFC006590AF /* Frameworks */,
297 | DD7502781C68FCFC006590AF /* Resources */,
298 | );
299 | buildRules = (
300 | );
301 | dependencies = (
302 | DD7502811C68FCFC006590AF /* PBXTargetDependency */,
303 | );
304 | name = "Wrap-macOS Tests";
305 | productName = "Wrap-OS Tests";
306 | productReference = DD75027A1C68FCFC006590AF /* Wrap-macOS Tests.xctest */;
307 | productType = "com.apple.product-type.bundle.unit-test";
308 | };
309 | DD75028C1C690C7A006590AF /* Wrap-tvOS Tests */ = {
310 | isa = PBXNativeTarget;
311 | buildConfigurationList = DD7502951C690C7A006590AF /* Build configuration list for PBXNativeTarget "Wrap-tvOS Tests" */;
312 | buildPhases = (
313 | DD7502891C690C7A006590AF /* Sources */,
314 | DD75028A1C690C7A006590AF /* Frameworks */,
315 | DD75028B1C690C7A006590AF /* Resources */,
316 | );
317 | buildRules = (
318 | );
319 | dependencies = (
320 | DD7502941C690C7A006590AF /* PBXTargetDependency */,
321 | );
322 | name = "Wrap-tvOS Tests";
323 | productName = "Wrap-tvOS Tests";
324 | productReference = DD75028D1C690C7A006590AF /* Wrap-tvOS Tests.xctest */;
325 | productType = "com.apple.product-type.bundle.unit-test";
326 | };
327 | /* End PBXNativeTarget section */
328 |
329 | /* Begin PBXProject section */
330 | 52D6D9731BEFF229002C0205 /* Project object */ = {
331 | isa = PBXProject;
332 | attributes = {
333 | LastSwiftUpdateCheck = 0720;
334 | LastUpgradeCheck = 0910;
335 | ORGANIZATIONNAME = Wrap;
336 | TargetAttributes = {
337 | 52D6D97B1BEFF229002C0205 = {
338 | CreatedOnToolsVersion = 7.1;
339 | LastSwiftMigration = 0910;
340 | };
341 | 52D6D9851BEFF229002C0205 = {
342 | CreatedOnToolsVersion = 7.1;
343 | LastSwiftMigration = 0910;
344 | };
345 | 52D6D9E11BEFFF6E002C0205 = {
346 | CreatedOnToolsVersion = 7.1;
347 | LastSwiftMigration = 0800;
348 | };
349 | 52D6D9EF1BEFFFBE002C0205 = {
350 | CreatedOnToolsVersion = 7.1;
351 | LastSwiftMigration = 0800;
352 | };
353 | 52D6DA0E1BF000BD002C0205 = {
354 | CreatedOnToolsVersion = 7.1;
355 | LastSwiftMigration = 0800;
356 | };
357 | DD7502791C68FCFC006590AF = {
358 | CreatedOnToolsVersion = 7.2.1;
359 | LastSwiftMigration = 0800;
360 | };
361 | DD75028C1C690C7A006590AF = {
362 | CreatedOnToolsVersion = 7.2.1;
363 | LastSwiftMigration = 0800;
364 | };
365 | };
366 | };
367 | buildConfigurationList = 52D6D9761BEFF229002C0205 /* Build configuration list for PBXProject "Wrap" */;
368 | compatibilityVersion = "Xcode 6.3";
369 | developmentRegion = English;
370 | hasScannedForEncodings = 0;
371 | knownRegions = (
372 | en,
373 | );
374 | mainGroup = 52D6D9721BEFF229002C0205;
375 | productRefGroup = 52D6D97D1BEFF229002C0205 /* Products */;
376 | projectDirPath = "";
377 | projectRoot = "";
378 | targets = (
379 | 52D6D97B1BEFF229002C0205 /* Wrap-iOS */,
380 | 52D6DA0E1BF000BD002C0205 /* Wrap-macOS */,
381 | 52D6D9E11BEFFF6E002C0205 /* Wrap-watchOS */,
382 | 52D6D9EF1BEFFFBE002C0205 /* Wrap-tvOS */,
383 | 52D6D9851BEFF229002C0205 /* Wrap-iOS Tests */,
384 | DD7502791C68FCFC006590AF /* Wrap-macOS Tests */,
385 | DD75028C1C690C7A006590AF /* Wrap-tvOS Tests */,
386 | );
387 | };
388 | /* End PBXProject section */
389 |
390 | /* Begin PBXResourcesBuildPhase section */
391 | 52D6D97A1BEFF229002C0205 /* Resources */ = {
392 | isa = PBXResourcesBuildPhase;
393 | buildActionMask = 2147483647;
394 | files = (
395 | );
396 | runOnlyForDeploymentPostprocessing = 0;
397 | };
398 | 52D6D9841BEFF229002C0205 /* Resources */ = {
399 | isa = PBXResourcesBuildPhase;
400 | buildActionMask = 2147483647;
401 | files = (
402 | );
403 | runOnlyForDeploymentPostprocessing = 0;
404 | };
405 | 52D6D9E01BEFFF6E002C0205 /* Resources */ = {
406 | isa = PBXResourcesBuildPhase;
407 | buildActionMask = 2147483647;
408 | files = (
409 | );
410 | runOnlyForDeploymentPostprocessing = 0;
411 | };
412 | 52D6D9EE1BEFFFBE002C0205 /* Resources */ = {
413 | isa = PBXResourcesBuildPhase;
414 | buildActionMask = 2147483647;
415 | files = (
416 | );
417 | runOnlyForDeploymentPostprocessing = 0;
418 | };
419 | 52D6DA0D1BF000BD002C0205 /* Resources */ = {
420 | isa = PBXResourcesBuildPhase;
421 | buildActionMask = 2147483647;
422 | files = (
423 | );
424 | runOnlyForDeploymentPostprocessing = 0;
425 | };
426 | DD7502781C68FCFC006590AF /* Resources */ = {
427 | isa = PBXResourcesBuildPhase;
428 | buildActionMask = 2147483647;
429 | files = (
430 | );
431 | runOnlyForDeploymentPostprocessing = 0;
432 | };
433 | DD75028B1C690C7A006590AF /* Resources */ = {
434 | isa = PBXResourcesBuildPhase;
435 | buildActionMask = 2147483647;
436 | files = (
437 | );
438 | runOnlyForDeploymentPostprocessing = 0;
439 | };
440 | /* End PBXResourcesBuildPhase section */
441 |
442 | /* Begin PBXSourcesBuildPhase section */
443 | 52D6D9771BEFF229002C0205 /* Sources */ = {
444 | isa = PBXSourcesBuildPhase;
445 | buildActionMask = 2147483647;
446 | files = (
447 | 52D6D9981BEFF375002C0205 /* Wrap.swift in Sources */,
448 | );
449 | runOnlyForDeploymentPostprocessing = 0;
450 | };
451 | 52D6D9821BEFF229002C0205 /* Sources */ = {
452 | isa = PBXSourcesBuildPhase;
453 | buildActionMask = 2147483647;
454 | files = (
455 | 52D6D99B1BEFF375002C0205 /* WrapTests.swift in Sources */,
456 | );
457 | runOnlyForDeploymentPostprocessing = 0;
458 | };
459 | 52D6D9DD1BEFFF6E002C0205 /* Sources */ = {
460 | isa = PBXSourcesBuildPhase;
461 | buildActionMask = 2147483647;
462 | files = (
463 | 52D6D9EA1BEFFFA4002C0205 /* Wrap.swift in Sources */,
464 | );
465 | runOnlyForDeploymentPostprocessing = 0;
466 | };
467 | 52D6D9EB1BEFFFBE002C0205 /* Sources */ = {
468 | isa = PBXSourcesBuildPhase;
469 | buildActionMask = 2147483647;
470 | files = (
471 | 52D6DA091BF00081002C0205 /* Wrap.swift in Sources */,
472 | );
473 | runOnlyForDeploymentPostprocessing = 0;
474 | };
475 | 52D6DA0A1BF000BD002C0205 /* Sources */ = {
476 | isa = PBXSourcesBuildPhase;
477 | buildActionMask = 2147483647;
478 | files = (
479 | 52D6DA261BF00118002C0205 /* Wrap.swift in Sources */,
480 | );
481 | runOnlyForDeploymentPostprocessing = 0;
482 | };
483 | DD7502761C68FCFC006590AF /* Sources */ = {
484 | isa = PBXSourcesBuildPhase;
485 | buildActionMask = 2147483647;
486 | files = (
487 | DD7502861C68FDDC006590AF /* WrapTests.swift in Sources */,
488 | );
489 | runOnlyForDeploymentPostprocessing = 0;
490 | };
491 | DD7502891C690C7A006590AF /* Sources */ = {
492 | isa = PBXSourcesBuildPhase;
493 | buildActionMask = 2147483647;
494 | files = (
495 | DD75029A1C690CBE006590AF /* WrapTests.swift in Sources */,
496 | );
497 | runOnlyForDeploymentPostprocessing = 0;
498 | };
499 | /* End PBXSourcesBuildPhase section */
500 |
501 | /* Begin PBXTargetDependency section */
502 | 52D6D9891BEFF229002C0205 /* PBXTargetDependency */ = {
503 | isa = PBXTargetDependency;
504 | target = 52D6D97B1BEFF229002C0205 /* Wrap-iOS */;
505 | targetProxy = 52D6D9881BEFF229002C0205 /* PBXContainerItemProxy */;
506 | };
507 | DD7502811C68FCFC006590AF /* PBXTargetDependency */ = {
508 | isa = PBXTargetDependency;
509 | target = 52D6DA0E1BF000BD002C0205 /* Wrap-macOS */;
510 | targetProxy = DD7502801C68FCFC006590AF /* PBXContainerItemProxy */;
511 | };
512 | DD7502941C690C7A006590AF /* PBXTargetDependency */ = {
513 | isa = PBXTargetDependency;
514 | target = 52D6D9EF1BEFFFBE002C0205 /* Wrap-tvOS */;
515 | targetProxy = DD7502931C690C7A006590AF /* PBXContainerItemProxy */;
516 | };
517 | /* End PBXTargetDependency section */
518 |
519 | /* Begin XCBuildConfiguration section */
520 | 52D6D98E1BEFF229002C0205 /* Debug */ = {
521 | isa = XCBuildConfiguration;
522 | buildSettings = {
523 | ALWAYS_SEARCH_USER_PATHS = NO;
524 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
525 | CLANG_CXX_LIBRARY = "libc++";
526 | CLANG_ENABLE_MODULES = YES;
527 | CLANG_ENABLE_OBJC_ARC = YES;
528 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
529 | CLANG_WARN_BOOL_CONVERSION = YES;
530 | CLANG_WARN_COMMA = YES;
531 | CLANG_WARN_CONSTANT_CONVERSION = YES;
532 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
533 | CLANG_WARN_EMPTY_BODY = YES;
534 | CLANG_WARN_ENUM_CONVERSION = YES;
535 | CLANG_WARN_INFINITE_RECURSION = YES;
536 | CLANG_WARN_INT_CONVERSION = YES;
537 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
538 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
539 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
540 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
541 | CLANG_WARN_STRICT_PROTOTYPES = YES;
542 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
543 | CLANG_WARN_UNREACHABLE_CODE = YES;
544 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
545 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
546 | COPY_PHASE_STRIP = NO;
547 | CURRENT_PROJECT_VERSION = 1;
548 | DEBUG_INFORMATION_FORMAT = dwarf;
549 | ENABLE_STRICT_OBJC_MSGSEND = YES;
550 | ENABLE_TESTABILITY = YES;
551 | GCC_C_LANGUAGE_STANDARD = gnu99;
552 | GCC_DYNAMIC_NO_PIC = NO;
553 | GCC_NO_COMMON_BLOCKS = YES;
554 | GCC_OPTIMIZATION_LEVEL = 0;
555 | GCC_PREPROCESSOR_DEFINITIONS = (
556 | "DEBUG=1",
557 | "$(inherited)",
558 | );
559 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
560 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
561 | GCC_WARN_UNDECLARED_SELECTOR = YES;
562 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
563 | GCC_WARN_UNUSED_FUNCTION = YES;
564 | GCC_WARN_UNUSED_VARIABLE = YES;
565 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
566 | MTL_ENABLE_DEBUG_INFO = YES;
567 | ONLY_ACTIVE_ARCH = YES;
568 | SDKROOT = iphoneos;
569 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
570 | SWIFT_VERSION = 3.0;
571 | TARGETED_DEVICE_FAMILY = "1,2";
572 | VERSIONING_SYSTEM = "apple-generic";
573 | VERSION_INFO_PREFIX = "";
574 | };
575 | name = Debug;
576 | };
577 | 52D6D98F1BEFF229002C0205 /* Release */ = {
578 | isa = XCBuildConfiguration;
579 | buildSettings = {
580 | ALWAYS_SEARCH_USER_PATHS = NO;
581 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
582 | CLANG_CXX_LIBRARY = "libc++";
583 | CLANG_ENABLE_MODULES = YES;
584 | CLANG_ENABLE_OBJC_ARC = YES;
585 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
586 | CLANG_WARN_BOOL_CONVERSION = YES;
587 | CLANG_WARN_COMMA = YES;
588 | CLANG_WARN_CONSTANT_CONVERSION = YES;
589 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
590 | CLANG_WARN_EMPTY_BODY = YES;
591 | CLANG_WARN_ENUM_CONVERSION = YES;
592 | CLANG_WARN_INFINITE_RECURSION = YES;
593 | CLANG_WARN_INT_CONVERSION = YES;
594 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
595 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
596 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
597 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
598 | CLANG_WARN_STRICT_PROTOTYPES = YES;
599 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
600 | CLANG_WARN_UNREACHABLE_CODE = YES;
601 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
602 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
603 | COPY_PHASE_STRIP = NO;
604 | CURRENT_PROJECT_VERSION = 1;
605 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
606 | ENABLE_NS_ASSERTIONS = NO;
607 | ENABLE_STRICT_OBJC_MSGSEND = YES;
608 | GCC_C_LANGUAGE_STANDARD = gnu99;
609 | GCC_NO_COMMON_BLOCKS = YES;
610 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
611 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
612 | GCC_WARN_UNDECLARED_SELECTOR = YES;
613 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
614 | GCC_WARN_UNUSED_FUNCTION = YES;
615 | GCC_WARN_UNUSED_VARIABLE = YES;
616 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
617 | MTL_ENABLE_DEBUG_INFO = NO;
618 | SDKROOT = iphoneos;
619 | SWIFT_VERSION = 3.0;
620 | TARGETED_DEVICE_FAMILY = "1,2";
621 | VALIDATE_PRODUCT = YES;
622 | VERSIONING_SYSTEM = "apple-generic";
623 | VERSION_INFO_PREFIX = "";
624 | };
625 | name = Release;
626 | };
627 | 52D6D9911BEFF229002C0205 /* Debug */ = {
628 | isa = XCBuildConfiguration;
629 | buildSettings = {
630 | APPLICATION_EXTENSION_API_ONLY = YES;
631 | CLANG_ENABLE_MODULES = YES;
632 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
633 | DEFINES_MODULE = YES;
634 | DYLIB_COMPATIBILITY_VERSION = 1;
635 | DYLIB_CURRENT_VERSION = 1;
636 | DYLIB_INSTALL_NAME_BASE = "@rpath";
637 | INFOPLIST_FILE = Configs/Wrap.plist;
638 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
639 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
640 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
641 | ONLY_ACTIVE_ARCH = NO;
642 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-iOS";
643 | PRODUCT_NAME = Wrap;
644 | SKIP_INSTALL = YES;
645 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
646 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
647 | SWIFT_VERSION = 4.0;
648 | };
649 | name = Debug;
650 | };
651 | 52D6D9921BEFF229002C0205 /* Release */ = {
652 | isa = XCBuildConfiguration;
653 | buildSettings = {
654 | APPLICATION_EXTENSION_API_ONLY = YES;
655 | CLANG_ENABLE_MODULES = YES;
656 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
657 | DEFINES_MODULE = YES;
658 | DYLIB_COMPATIBILITY_VERSION = 1;
659 | DYLIB_CURRENT_VERSION = 1;
660 | DYLIB_INSTALL_NAME_BASE = "@rpath";
661 | INFOPLIST_FILE = Configs/Wrap.plist;
662 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
663 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
664 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
665 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-iOS";
666 | PRODUCT_NAME = Wrap;
667 | SKIP_INSTALL = YES;
668 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
669 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
670 | SWIFT_VERSION = 4.0;
671 | };
672 | name = Release;
673 | };
674 | 52D6D9941BEFF229002C0205 /* Debug */ = {
675 | isa = XCBuildConfiguration;
676 | buildSettings = {
677 | CLANG_ENABLE_MODULES = YES;
678 | INFOPLIST_FILE = Configs/WrapTests.plist;
679 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
680 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-iOS-Tests";
681 | PRODUCT_NAME = "$(TARGET_NAME)";
682 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
683 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
684 | SWIFT_VERSION = 4.0;
685 | };
686 | name = Debug;
687 | };
688 | 52D6D9951BEFF229002C0205 /* Release */ = {
689 | isa = XCBuildConfiguration;
690 | buildSettings = {
691 | CLANG_ENABLE_MODULES = YES;
692 | INFOPLIST_FILE = Configs/WrapTests.plist;
693 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
694 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-iOS-Tests";
695 | PRODUCT_NAME = "$(TARGET_NAME)";
696 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
697 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
698 | SWIFT_VERSION = 4.0;
699 | };
700 | name = Release;
701 | };
702 | 52D6D9E81BEFFF6E002C0205 /* Debug */ = {
703 | isa = XCBuildConfiguration;
704 | buildSettings = {
705 | APPLICATION_EXTENSION_API_ONLY = YES;
706 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
707 | DEFINES_MODULE = YES;
708 | DYLIB_COMPATIBILITY_VERSION = 1;
709 | DYLIB_CURRENT_VERSION = 1;
710 | DYLIB_INSTALL_NAME_BASE = "@rpath";
711 | INFOPLIST_FILE = Configs/Wrap.plist;
712 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
713 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
714 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-watchOS";
715 | PRODUCT_NAME = Wrap;
716 | SDKROOT = watchos;
717 | SKIP_INSTALL = YES;
718 | SWIFT_VERSION = 3.0;
719 | TARGETED_DEVICE_FAMILY = 4;
720 | WATCHOS_DEPLOYMENT_TARGET = 2.0;
721 | };
722 | name = Debug;
723 | };
724 | 52D6D9E91BEFFF6E002C0205 /* Release */ = {
725 | isa = XCBuildConfiguration;
726 | buildSettings = {
727 | APPLICATION_EXTENSION_API_ONLY = YES;
728 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
729 | DEFINES_MODULE = YES;
730 | DYLIB_COMPATIBILITY_VERSION = 1;
731 | DYLIB_CURRENT_VERSION = 1;
732 | DYLIB_INSTALL_NAME_BASE = "@rpath";
733 | INFOPLIST_FILE = Configs/Wrap.plist;
734 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
735 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
736 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-watchOS";
737 | PRODUCT_NAME = Wrap;
738 | SDKROOT = watchos;
739 | SKIP_INSTALL = YES;
740 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
741 | SWIFT_VERSION = 3.0;
742 | TARGETED_DEVICE_FAMILY = 4;
743 | WATCHOS_DEPLOYMENT_TARGET = 2.0;
744 | };
745 | name = Release;
746 | };
747 | 52D6DA021BEFFFBE002C0205 /* Debug */ = {
748 | isa = XCBuildConfiguration;
749 | buildSettings = {
750 | APPLICATION_EXTENSION_API_ONLY = YES;
751 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
752 | DEFINES_MODULE = YES;
753 | DYLIB_COMPATIBILITY_VERSION = 1;
754 | DYLIB_CURRENT_VERSION = 1;
755 | DYLIB_INSTALL_NAME_BASE = "@rpath";
756 | INFOPLIST_FILE = Configs/Wrap.plist;
757 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
758 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
759 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-tvOS";
760 | PRODUCT_NAME = Wrap;
761 | SDKROOT = appletvos;
762 | SKIP_INSTALL = YES;
763 | SWIFT_VERSION = 3.0;
764 | TARGETED_DEVICE_FAMILY = 3;
765 | TVOS_DEPLOYMENT_TARGET = 9.0;
766 | };
767 | name = Debug;
768 | };
769 | 52D6DA031BEFFFBE002C0205 /* Release */ = {
770 | isa = XCBuildConfiguration;
771 | buildSettings = {
772 | APPLICATION_EXTENSION_API_ONLY = YES;
773 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
774 | DEFINES_MODULE = YES;
775 | DYLIB_COMPATIBILITY_VERSION = 1;
776 | DYLIB_CURRENT_VERSION = 1;
777 | DYLIB_INSTALL_NAME_BASE = "@rpath";
778 | INFOPLIST_FILE = Configs/Wrap.plist;
779 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
780 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
781 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-tvOS";
782 | PRODUCT_NAME = Wrap;
783 | SDKROOT = appletvos;
784 | SKIP_INSTALL = YES;
785 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
786 | SWIFT_VERSION = 3.0;
787 | TARGETED_DEVICE_FAMILY = 3;
788 | TVOS_DEPLOYMENT_TARGET = 9.0;
789 | };
790 | name = Release;
791 | };
792 | 52D6DA211BF000BD002C0205 /* Debug */ = {
793 | isa = XCBuildConfiguration;
794 | buildSettings = {
795 | APPLICATION_EXTENSION_API_ONLY = YES;
796 | CODE_SIGN_IDENTITY = "-";
797 | COMBINE_HIDPI_IMAGES = YES;
798 | DEFINES_MODULE = YES;
799 | DYLIB_COMPATIBILITY_VERSION = 1;
800 | DYLIB_CURRENT_VERSION = 1;
801 | DYLIB_INSTALL_NAME_BASE = "@rpath";
802 | FRAMEWORK_VERSION = A;
803 | INFOPLIST_FILE = Configs/Wrap.plist;
804 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
805 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
806 | MACOSX_DEPLOYMENT_TARGET = 10.10;
807 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-macOS";
808 | PRODUCT_NAME = Wrap;
809 | SDKROOT = macosx;
810 | SKIP_INSTALL = YES;
811 | SWIFT_VERSION = 3.0;
812 | };
813 | name = Debug;
814 | };
815 | 52D6DA221BF000BD002C0205 /* Release */ = {
816 | isa = XCBuildConfiguration;
817 | buildSettings = {
818 | APPLICATION_EXTENSION_API_ONLY = YES;
819 | CODE_SIGN_IDENTITY = "-";
820 | COMBINE_HIDPI_IMAGES = YES;
821 | DEFINES_MODULE = YES;
822 | DYLIB_COMPATIBILITY_VERSION = 1;
823 | DYLIB_CURRENT_VERSION = 1;
824 | DYLIB_INSTALL_NAME_BASE = "@rpath";
825 | FRAMEWORK_VERSION = A;
826 | INFOPLIST_FILE = Configs/Wrap.plist;
827 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
828 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
829 | MACOSX_DEPLOYMENT_TARGET = 10.10;
830 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-macOS";
831 | PRODUCT_NAME = Wrap;
832 | SDKROOT = macosx;
833 | SKIP_INSTALL = YES;
834 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
835 | SWIFT_VERSION = 3.0;
836 | };
837 | name = Release;
838 | };
839 | DD7502831C68FCFC006590AF /* Debug */ = {
840 | isa = XCBuildConfiguration;
841 | buildSettings = {
842 | CODE_SIGN_IDENTITY = "-";
843 | COMBINE_HIDPI_IMAGES = YES;
844 | INFOPLIST_FILE = Configs/WrapTests.plist;
845 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
846 | MACOSX_DEPLOYMENT_TARGET = 10.11;
847 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-macOS-Tests";
848 | PRODUCT_NAME = "$(TARGET_NAME)";
849 | SDKROOT = macosx;
850 | SWIFT_VERSION = 3.0;
851 | };
852 | name = Debug;
853 | };
854 | DD7502841C68FCFC006590AF /* Release */ = {
855 | isa = XCBuildConfiguration;
856 | buildSettings = {
857 | CODE_SIGN_IDENTITY = "-";
858 | COMBINE_HIDPI_IMAGES = YES;
859 | INFOPLIST_FILE = Configs/WrapTests.plist;
860 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
861 | MACOSX_DEPLOYMENT_TARGET = 10.11;
862 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-macOS-Tests";
863 | PRODUCT_NAME = "$(TARGET_NAME)";
864 | SDKROOT = macosx;
865 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
866 | SWIFT_VERSION = 3.0;
867 | };
868 | name = Release;
869 | };
870 | DD7502961C690C7A006590AF /* Debug */ = {
871 | isa = XCBuildConfiguration;
872 | buildSettings = {
873 | INFOPLIST_FILE = Configs/WrapTests.plist;
874 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
875 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-tvOS-Tests";
876 | PRODUCT_NAME = "$(TARGET_NAME)";
877 | SDKROOT = appletvos;
878 | SWIFT_VERSION = 3.0;
879 | TVOS_DEPLOYMENT_TARGET = 9.1;
880 | };
881 | name = Debug;
882 | };
883 | DD7502971C690C7A006590AF /* Release */ = {
884 | isa = XCBuildConfiguration;
885 | buildSettings = {
886 | INFOPLIST_FILE = Configs/WrapTests.plist;
887 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
888 | PRODUCT_BUNDLE_IDENTIFIER = "com.Wrap.Wrap-tvOS-Tests";
889 | PRODUCT_NAME = "$(TARGET_NAME)";
890 | SDKROOT = appletvos;
891 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
892 | SWIFT_VERSION = 3.0;
893 | TVOS_DEPLOYMENT_TARGET = 9.1;
894 | };
895 | name = Release;
896 | };
897 | /* End XCBuildConfiguration section */
898 |
899 | /* Begin XCConfigurationList section */
900 | 52D6D9761BEFF229002C0205 /* Build configuration list for PBXProject "Wrap" */ = {
901 | isa = XCConfigurationList;
902 | buildConfigurations = (
903 | 52D6D98E1BEFF229002C0205 /* Debug */,
904 | 52D6D98F1BEFF229002C0205 /* Release */,
905 | );
906 | defaultConfigurationIsVisible = 0;
907 | defaultConfigurationName = Release;
908 | };
909 | 52D6D9901BEFF229002C0205 /* Build configuration list for PBXNativeTarget "Wrap-iOS" */ = {
910 | isa = XCConfigurationList;
911 | buildConfigurations = (
912 | 52D6D9911BEFF229002C0205 /* Debug */,
913 | 52D6D9921BEFF229002C0205 /* Release */,
914 | );
915 | defaultConfigurationIsVisible = 0;
916 | defaultConfigurationName = Release;
917 | };
918 | 52D6D9931BEFF229002C0205 /* Build configuration list for PBXNativeTarget "Wrap-iOS Tests" */ = {
919 | isa = XCConfigurationList;
920 | buildConfigurations = (
921 | 52D6D9941BEFF229002C0205 /* Debug */,
922 | 52D6D9951BEFF229002C0205 /* Release */,
923 | );
924 | defaultConfigurationIsVisible = 0;
925 | defaultConfigurationName = Release;
926 | };
927 | 52D6D9E71BEFFF6E002C0205 /* Build configuration list for PBXNativeTarget "Wrap-watchOS" */ = {
928 | isa = XCConfigurationList;
929 | buildConfigurations = (
930 | 52D6D9E81BEFFF6E002C0205 /* Debug */,
931 | 52D6D9E91BEFFF6E002C0205 /* Release */,
932 | );
933 | defaultConfigurationIsVisible = 0;
934 | defaultConfigurationName = Release;
935 | };
936 | 52D6DA011BEFFFBE002C0205 /* Build configuration list for PBXNativeTarget "Wrap-tvOS" */ = {
937 | isa = XCConfigurationList;
938 | buildConfigurations = (
939 | 52D6DA021BEFFFBE002C0205 /* Debug */,
940 | 52D6DA031BEFFFBE002C0205 /* Release */,
941 | );
942 | defaultConfigurationIsVisible = 0;
943 | defaultConfigurationName = Release;
944 | };
945 | 52D6DA201BF000BD002C0205 /* Build configuration list for PBXNativeTarget "Wrap-macOS" */ = {
946 | isa = XCConfigurationList;
947 | buildConfigurations = (
948 | 52D6DA211BF000BD002C0205 /* Debug */,
949 | 52D6DA221BF000BD002C0205 /* Release */,
950 | );
951 | defaultConfigurationIsVisible = 0;
952 | defaultConfigurationName = Release;
953 | };
954 | DD7502821C68FCFC006590AF /* Build configuration list for PBXNativeTarget "Wrap-macOS Tests" */ = {
955 | isa = XCConfigurationList;
956 | buildConfigurations = (
957 | DD7502831C68FCFC006590AF /* Debug */,
958 | DD7502841C68FCFC006590AF /* Release */,
959 | );
960 | defaultConfigurationIsVisible = 0;
961 | defaultConfigurationName = Release;
962 | };
963 | DD7502951C690C7A006590AF /* Build configuration list for PBXNativeTarget "Wrap-tvOS Tests" */ = {
964 | isa = XCConfigurationList;
965 | buildConfigurations = (
966 | DD7502961C690C7A006590AF /* Debug */,
967 | DD7502971C690C7A006590AF /* Release */,
968 | );
969 | defaultConfigurationIsVisible = 0;
970 | defaultConfigurationName = Release;
971 | };
972 | /* End XCConfigurationList section */
973 | };
974 | rootObject = 52D6D9731BEFF229002C0205 /* Project object */;
975 | }
976 |
--------------------------------------------------------------------------------
/Tests/WrapTests/WrapTests.swift:
--------------------------------------------------------------------------------
1 | /**
2 | * Wrap
3 | *
4 | * Copyright (c) 2015 - 2017 John Sundell. Licensed under the MIT license, as follows:
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | import Foundation
26 | import XCTest
27 | import Wrap
28 |
29 | // MARK: - Tests
30 |
31 | class WrapTests: XCTestCase {
32 | func testBasicStruct() {
33 | struct Model {
34 | let string = "A string"
35 | let int = 15
36 | let double = 7.6
37 | }
38 |
39 | do {
40 | try verify(dictionary: wrap(Model()), againstDictionary: [
41 | "string" : "A string",
42 | "int" : 15,
43 | "double" : 7.6
44 | ])
45 | } catch {
46 | XCTFail(error.toString())
47 | }
48 | }
49 |
50 | func testOptionalProperties() {
51 | struct Model {
52 | let string: String? = "A string"
53 | let int: Int? = 5
54 | let missing: String? = nil
55 | let missingNestedOptional: Optional> = .some(.none)
56 | }
57 |
58 | do {
59 | try verify(dictionary: wrap(Model()), againstDictionary: [
60 | "string" : "A string",
61 | "int" : 5
62 | ])
63 | } catch {
64 | XCTFail(error.toString())
65 | }
66 | }
67 |
68 | func testSpecificNonOptionalProperties() {
69 | struct Model {
70 | let some: String = "value"
71 | let Some: Int = 1
72 | }
73 |
74 | do {
75 | try verify(dictionary: wrap(Model()), againstDictionary: [
76 | "some" : "value",
77 | "Some" : 1
78 | ])
79 | } catch {
80 | XCTFail(error.toString())
81 | }
82 | }
83 |
84 | func testSpecificNonOptionalValues() {
85 | struct Model {
86 | let string: String = "nil"
87 | }
88 |
89 | do {
90 | try verify(dictionary: wrap(Model()), againstDictionary: [
91 | "string" : "nil"
92 | ])
93 | } catch {
94 | XCTFail(error.toString())
95 | }
96 | }
97 |
98 |
99 | func testProtocolProperties() {
100 | struct NestedModel: MockProtocol {
101 | let constantString = "Another string"
102 | var mutableInt = 27
103 | }
104 |
105 | struct Model: MockProtocol {
106 | let constantString = "A string"
107 | var mutableInt = 15
108 | let nested: MockProtocol = NestedModel()
109 | }
110 |
111 | do {
112 | try verify(dictionary: wrap(Model()), againstDictionary: [
113 | "constantString" : "A string",
114 | "mutableInt" : 15,
115 | "nested": [
116 | "constantString" : "Another string",
117 | "mutableInt" : 27
118 | ]
119 | ])
120 | } catch {
121 | XCTFail(error.toString())
122 | }
123 | }
124 |
125 | func testRootEnum() {
126 | enum Enum {
127 | case first
128 | case second(String)
129 | }
130 |
131 | do {
132 | try verify(dictionary: wrap(Enum.first), againstDictionary: [:])
133 |
134 | try verify(dictionary: wrap(Enum.second("Hello")), againstDictionary: [
135 | "second" : "Hello"
136 | ])
137 | } catch {
138 | XCTFail(error.toString())
139 | }
140 | }
141 |
142 | func testEnumProperties() {
143 | enum Enum {
144 | case first
145 | case second(String)
146 | case third(intValue: Int)
147 | }
148 |
149 | enum IntEnum: Int, WrappableEnum {
150 | case first
151 | case second = 17
152 | }
153 |
154 | enum StringEnum: String, WrappableEnum {
155 | case first = "First string"
156 | case second = "Second string"
157 | }
158 |
159 | struct Model {
160 | let first = Enum.first
161 | let second = Enum.second("Hello")
162 | let third = Enum.third(intValue: 15)
163 | let firstInt = IntEnum.first
164 | let secondInt = IntEnum.second
165 | let firstString = StringEnum.first
166 | let secondString = StringEnum.second
167 | }
168 |
169 | do {
170 | try verify(dictionary: wrap(Model()), againstDictionary: [
171 | "first" : "first",
172 | "second" : [
173 | "second" : "Hello"
174 | ],
175 | "third" : [
176 | "third" : 15
177 | ],
178 | "firstInt" : 0,
179 | "secondInt" : 17,
180 | "firstString" : "First string",
181 | "secondString" : "Second string"
182 | ])
183 | } catch {
184 | XCTFail(error.toString())
185 | }
186 | }
187 |
188 | func testDateProperty() {
189 | let date = Date()
190 |
191 | struct Model {
192 | let date: Date
193 | }
194 |
195 | let dateFormatter = DateFormatter()
196 |
197 | do {
198 | let model = Model(date: date)
199 |
200 | try verify(dictionary: wrap(model, dateFormatter: dateFormatter), againstDictionary: [
201 | "date" : dateFormatter.string(from: date)
202 | ])
203 | } catch {
204 | XCTFail("\(try! wrap(Model(date: date), dateFormatter: dateFormatter) as WrappedDictionary)")
205 | XCTFail(error.toString())
206 | }
207 | }
208 |
209 | #if !os(Linux)
210 | func testNSDateProperty() {
211 | let date = NSDate()
212 |
213 | struct Model {
214 | let date: NSDate
215 | }
216 |
217 | let dateFormatter = DateFormatter()
218 |
219 | do {
220 | let model = Model(date: date)
221 |
222 | try verify(dictionary: wrap(model, dateFormatter: dateFormatter), againstDictionary: [
223 | "date" : dateFormatter.string(from: date as Date)
224 | ])
225 | } catch {
226 | XCTFail("\(try! wrap(Model(date: date), dateFormatter: dateFormatter) as WrappedDictionary)")
227 | XCTFail(error.toString())
228 | }
229 | }
230 | #endif
231 |
232 | func testDatePropertyWithCustomizableStruct() {
233 | let date = Date()
234 |
235 | struct Model: WrapCustomizable {
236 | let date: Date
237 | }
238 |
239 | let dateFormatter = DateFormatter()
240 |
241 | do {
242 | let model = Model(date: date)
243 |
244 | try verify(dictionary: wrap(model, dateFormatter: dateFormatter), againstDictionary: [
245 | "date" : dateFormatter.string(from: date)
246 | ])
247 | } catch {
248 | XCTFail(error.toString())
249 | }
250 | }
251 |
252 | func testEmptyStruct() {
253 | struct Empty {}
254 |
255 | do {
256 | try verify(dictionary: wrap(Empty()), againstDictionary: [:])
257 | } catch {
258 | XCTFail(error.toString())
259 | }
260 | }
261 |
262 | func testNestedEmptyStruct() {
263 | struct Empty {}
264 |
265 | struct EmptyWithOptional {
266 | let optional: String? = nil
267 | }
268 |
269 | struct Model {
270 | let empty = Empty()
271 | let emptyWithOptional = EmptyWithOptional()
272 | }
273 |
274 | do {
275 | try verify(dictionary: wrap(Model()), againstDictionary: [
276 | "empty" : [:],
277 | "emptyWithOptional" : [:]
278 | ])
279 | } catch {
280 | XCTFail(error.toString())
281 | }
282 | }
283 |
284 | func testArrayProperties() {
285 | struct Model {
286 | let homogeneous = ["Wrap", "Tests"]
287 | let mixed = ["Wrap", 15, 8.3] as [Any]
288 | }
289 |
290 | do {
291 | try verify(dictionary: wrap(Model()), againstDictionary: [
292 | "homogeneous" : ["Wrap", "Tests"],
293 | "mixed" : ["Wrap", 15, 8.3]
294 | ])
295 | } catch {
296 | XCTFail(error.toString())
297 | }
298 | }
299 |
300 | func testDictionaryProperties() {
301 | struct Model {
302 | let homogeneous = [
303 | "Key1" : "Value1",
304 | "Key2" : "Value2"
305 | ]
306 |
307 | let mixed: WrappedDictionary = [
308 | "Key1" : 15,
309 | "Key2" : 19.2,
310 | "Key3" : "Value",
311 | "Key4" : ["Wrap", "Tests"],
312 | "Key5" : [
313 | "NestedKey" : "NestedValue"
314 | ]
315 | ]
316 | }
317 |
318 | do {
319 | try verify(dictionary: wrap(Model()), againstDictionary: [
320 | "homogeneous" : [
321 | "Key1" : "Value1",
322 | "Key2" : "Value2"
323 | ],
324 | "mixed" : [
325 | "Key1" : 15,
326 | "Key2" : 19.2,
327 | "Key3" : "Value",
328 | "Key4" : ["Wrap", "Tests"],
329 | "Key5" : [
330 | "NestedKey" : "NestedValue"
331 | ]
332 | ]
333 | ])
334 | } catch {
335 | XCTFail(error.toString())
336 | }
337 | }
338 |
339 | func testHomogeneousSetProperty() {
340 | struct Model {
341 | let set: Set = ["Wrap", "Tests"]
342 | }
343 |
344 | do {
345 | let dictionary: WrappedDictionary = try wrap(Model())
346 | XCTAssertEqual(dictionary.count, 1)
347 |
348 | guard let array = dictionary["set"] as? [String] else {
349 | return XCTFail("Expected array for key \"set\"")
350 | }
351 |
352 | XCTAssertEqual(Set(array), ["Wrap", "Tests"])
353 | } catch {
354 | XCTFail(error.toString())
355 | }
356 | }
357 |
358 | #if !os(Linux)
359 | func testMixedNSObjectSetProperty() {
360 | struct Model {
361 | let set: Set = ["Wrap" as NSObject, 15 as NSObject, 8.3 as NSObject]
362 | }
363 |
364 | do {
365 | try verify(dictionary: wrap(Model()), againstDictionary: [
366 | "set" : ["Wrap", 15, 8.3]
367 | ])
368 | } catch {
369 | XCTFail(error.toString())
370 | }
371 | }
372 | #endif
373 |
374 | func testNSURLProperty() {
375 | struct Model {
376 | let optionalURL = NSURL(string: "http://github.com")
377 | let URL = NSURL(string: "http://google.com")!
378 | }
379 |
380 | do {
381 | try verify(dictionary: wrap(Model()), againstDictionary: [
382 | "optionalURL" : "http://github.com",
383 | "URL" : "http://google.com"
384 | ])
385 | } catch {
386 | XCTFail(error.toString())
387 | }
388 | }
389 |
390 | func testURLProperty() {
391 | struct Model {
392 | let optionalUrl = URL(string: "http://github.com")
393 | let url = URL(string: "http://google.com")!
394 | }
395 |
396 | do {
397 | try verify(dictionary: wrap(Model()), againstDictionary: [
398 | "optionalUrl" : "http://github.com",
399 | "url" : "http://google.com"
400 | ])
401 | } catch {
402 | XCTFail(error.toString())
403 | }
404 | }
405 |
406 | func test64BitIntegerProperties() {
407 | struct Model {
408 | let int = Int64.max
409 | let uint = UInt64.max
410 | }
411 |
412 | do {
413 | let dictionary = try JSONSerialization.jsonObject(with: wrap(Model()), options: []) as! WrappedDictionary
414 |
415 | try verify(dictionary: dictionary, againstDictionary: [
416 | "int" : Int64.max,
417 | "uint" : UInt64.max
418 | ])
419 | } catch {
420 | XCTFail(error.toString())
421 | }
422 | }
423 |
424 | func testRootSubclass() {
425 | class Superclass {
426 | let string1 = "String1"
427 | let int1 = 1
428 | }
429 |
430 | class Subclass: Superclass {
431 | let string2 = "String2"
432 | let int2 = 2
433 | }
434 |
435 | do {
436 | try verify(dictionary: wrap(Subclass()), againstDictionary: [
437 | "string1" : "String1",
438 | "string2" : "String2",
439 | "int1" : 1,
440 | "int2" : 2
441 | ])
442 | } catch {
443 | XCTFail(error.toString())
444 | }
445 | }
446 |
447 | func testRootNSObjectSubclass() {
448 | class Model: NSObject {
449 | let string = "String"
450 | let double = 7.14
451 | }
452 |
453 | do {
454 | try verify(dictionary: wrap(Model()), againstDictionary: [
455 | "string" : "String",
456 | "double" : 7.14
457 | ])
458 | } catch {
459 | XCTFail(error.toString())
460 | }
461 | }
462 |
463 | func testRootDictionary() {
464 | struct Model {
465 | var string: String
466 | }
467 |
468 | let dictionary = [
469 | "model1" : Model(string: "First"),
470 | "model2" : Model(string: "Second")
471 | ]
472 |
473 | do {
474 | try verify(dictionary: wrap(dictionary), againstDictionary: [
475 | "model1" : [
476 | "string" : "First"
477 | ],
478 | "model2" : [
479 | "string" : "Second"
480 | ]
481 | ])
482 | } catch {
483 | XCTFail(error.toString())
484 | }
485 | }
486 |
487 | func testNestedStruct() {
488 | struct NestedModel {
489 | let string = "Nested model"
490 | }
491 |
492 | struct Model {
493 | let nested = NestedModel()
494 | }
495 |
496 | do {
497 | try verify(dictionary: wrap(Model()), againstDictionary: [
498 | "nested" : [
499 | "string" : "Nested model"
500 | ]
501 | ])
502 | } catch {
503 | XCTFail(error.toString())
504 | }
505 | }
506 |
507 | func testNestedArrayOfStructs() {
508 | struct NestedModel1 {
509 | let string1: String
510 | }
511 |
512 | struct NestedModel2 {
513 | let string2: String
514 | }
515 |
516 | struct Model {
517 | let nested: [Any] = [
518 | NestedModel1(string1: "String1"),
519 | NestedModel2(string2: "String2"),
520 | ]
521 | }
522 |
523 | do {
524 | let wrapped: WrappedDictionary = try wrap(Model())
525 |
526 | if let nested = wrapped["nested"] as? [WrappedDictionary] {
527 | XCTAssertEqual(nested.count, 2)
528 |
529 | if let firstDictionary = nested.first, let secondDictionary = nested.last {
530 | try verify(dictionary: firstDictionary, againstDictionary: [
531 | "string1" : "String1"
532 | ])
533 |
534 | try verify(dictionary: secondDictionary, againstDictionary: [
535 | "string2" : "String2"
536 | ])
537 | } else {
538 | XCTFail("Missing dictionaries")
539 | }
540 | } else {
541 | XCTFail("Unexpected type")
542 | }
543 | } catch {
544 | XCTFail(error.toString())
545 | }
546 | }
547 |
548 | func testNestedDictionariesOfStructs() {
549 | struct NestedModel {
550 | let string = "Hello"
551 | }
552 |
553 | struct Model {
554 | let nested = [
555 | "model" : NestedModel()
556 | ]
557 | }
558 |
559 | do {
560 | try verify(dictionary: wrap(Model()), againstDictionary: [
561 | "nested" : [
562 | "model" : [
563 | "string" : "Hello"
564 | ]
565 | ]
566 | ])
567 | } catch {
568 | XCTFail(error.toString())
569 | }
570 | }
571 |
572 | func testNestedSubclass() {
573 | class Superclass {
574 | let string1 = "String1"
575 | }
576 |
577 | class Subclass: Superclass {
578 | let string2 = "String2"
579 | }
580 |
581 | struct Model {
582 | let superclass = Superclass()
583 | let subclass = Subclass()
584 | }
585 |
586 | do {
587 | try verify(dictionary: wrap(Model()), againstDictionary: [
588 | "superclass" : [
589 | "string1" : "String1"
590 | ],
591 | "subclass" : [
592 | "string1" : "String1",
593 | "string2" : "String2"
594 | ]
595 | ])
596 | } catch {
597 | XCTFail(error.toString())
598 | }
599 | }
600 |
601 | func testDeepNesting() {
602 | struct ThirdModel {
603 | let string = "Third String"
604 | }
605 |
606 | struct SecondModel {
607 | let string = "Second String"
608 | let nestedArray = [ThirdModel()]
609 | }
610 |
611 | struct FirstModel {
612 | let string = "First String"
613 | let nestedDictionary = [ "nestedDictionary" : SecondModel()]
614 | }
615 |
616 | do {
617 | let wrappedDictionary :WrappedDictionary = try wrap(FirstModel())
618 | try verify(dictionary: wrappedDictionary, againstDictionary: [
619 | "string" : "First String",
620 | "nestedDictionary" : [
621 | "nestedDictionary" : [
622 | "string" : "Second String",
623 | "nestedArray" : [
624 | ["string" : "Third String"]
625 | ]
626 | ]
627 | ]
628 | ])
629 | } catch {
630 | XCTFail(error.toString())
631 | }
632 | }
633 |
634 | #if !os(Linux)
635 | func testObjectiveCObjectProperties() {
636 | struct Model {
637 | let string = NSString(string: "Hello")
638 | let number = NSNumber(value: 17)
639 | let array = NSArray(object: NSString(string: "Unwrap"))
640 | }
641 |
642 | do {
643 | try verify(dictionary: wrap(Model()), againstDictionary: [
644 | "string" : "Hello",
645 | "number" : 17,
646 | "array" : ["Unwrap"]
647 | ])
648 | } catch {
649 | XCTFail(error.toString())
650 | }
651 | }
652 | #endif
653 |
654 | func testWrappableKey() {
655 | enum Key: Int, WrappableKey {
656 | case first = 15
657 | case second = 19
658 |
659 | func toWrappedKey() -> String {
660 | return String(self.rawValue)
661 | }
662 | }
663 |
664 | struct Model {
665 | let dictionary = [
666 | Key.first : "First value",
667 | Key.second : "Second value"
668 | ]
669 | }
670 |
671 | do {
672 | try verify(dictionary: wrap(Model()), againstDictionary: [
673 | "dictionary" : [
674 | "15" : "First value",
675 | "19" : "Second value"
676 | ]
677 | ])
678 | } catch {
679 | XCTFail(error.toString())
680 | }
681 | }
682 |
683 | func testKeyCustomization() {
684 | struct Model: WrapCustomizable {
685 | let string = "Default"
686 | let customized = "I'm customized"
687 | let skipThis = 15
688 |
689 | fileprivate func keyForWrapping(propertyNamed propertyName: String) -> String? {
690 | if propertyName == "customized" {
691 | return "totallyCustomized"
692 | }
693 |
694 | if propertyName == "skipThis" {
695 | return nil
696 | }
697 |
698 | return propertyName
699 | }
700 | }
701 |
702 | do {
703 | try verify(dictionary: wrap(Model()), againstDictionary: [
704 | "string" : "Default",
705 | "totallyCustomized" : "I'm customized"
706 | ])
707 | } catch {
708 | XCTFail(error.toString())
709 | }
710 | }
711 |
712 | func testCustomWrapping() {
713 | struct Model: WrapCustomizable {
714 | let string = "A string"
715 |
716 | func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
717 | return [
718 | "custom" : "A value"
719 | ]
720 | }
721 | }
722 |
723 | do {
724 | try verify(dictionary: wrap(Model()), againstDictionary: [
725 | "custom" : "A value"
726 | ])
727 | } catch {
728 | XCTFail(error.toString())
729 | }
730 | }
731 |
732 | func testCustomWrappingCallingWrapFunction() {
733 | struct Model: WrapCustomizable {
734 | let int = 27
735 |
736 | func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
737 | do {
738 | var wrapped = try Wrapper().wrap(object: self)
739 | wrapped["custom"] = "A value"
740 | return wrapped
741 | } catch {
742 | return nil
743 | }
744 | }
745 | }
746 |
747 | do {
748 | try verify(dictionary: wrap(Model()), againstDictionary: [
749 | "int" : 27,
750 | "custom" : "A value"
751 | ])
752 | } catch {
753 | XCTFail(error.toString())
754 | }
755 | }
756 |
757 | func testCustomWrappingForSingleProperty() {
758 | struct Model: WrapCustomizable {
759 | let string = "Hello"
760 | let int = 16
761 |
762 | func wrap(propertyNamed propertyName: String, originalValue: Any, context: Any?, dateFormatter: DateFormatter?) throws -> Any? {
763 | if propertyName == "int" {
764 | XCTAssertEqual((originalValue as? Int) ?? 0, self.int)
765 | return 27
766 | }
767 |
768 | return nil
769 | }
770 | }
771 |
772 | do {
773 | try verify(dictionary: wrap(Model()), againstDictionary: [
774 | "string" : "Hello",
775 | "int" : 27
776 | ])
777 | } catch {
778 | XCTFail(error.toString())
779 | }
780 | }
781 |
782 | func testCustomWrappingFailureThrows() {
783 | struct Model: WrapCustomizable {
784 | func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
785 | return nil
786 | }
787 | }
788 |
789 | do {
790 | _ = try wrap(Model()) as WrappedDictionary
791 | XCTFail("Should have thrown")
792 | } catch WrapError.wrappingFailedForObject(let object) {
793 | XCTAssertTrue(object is Model)
794 | } catch {
795 | XCTFail("Invalid error type: " + error.toString())
796 | }
797 | }
798 |
799 | func testCustomWrappingForSinglePropertyFailureThrows() {
800 | struct Model: WrapCustomizable {
801 | let string = "A string"
802 |
803 | func wrap(propertyNamed propertyName: String, originalValue: Any, context: Any?, dateFormatter: DateFormatter?) throws -> Any? {
804 | throw NSError(domain: "ERROR", code: 0, userInfo: nil)
805 | }
806 | }
807 |
808 | do {
809 | _ = try wrap(Model()) as WrappedDictionary
810 | XCTFail("Should have thrown")
811 | } catch WrapError.wrappingFailedForObject(let object) {
812 | XCTAssertTrue(object is Model)
813 | } catch {
814 | XCTFail("Invalid error type: " + error.toString())
815 | }
816 | }
817 |
818 | func testInvalidRootObjectThrows() {
819 | do {
820 | _ = try wrap("A string") as WrappedDictionary
821 | } catch WrapError.invalidTopLevelObject(let object) {
822 | XCTAssertEqual((object as? String) ?? "", "A string")
823 | } catch {
824 | XCTFail("Invalid error type: " + error.toString())
825 | }
826 | }
827 |
828 | func testDataWrapping() {
829 | struct Model {
830 | let string = "A string"
831 | let int = 42
832 | let array = [4, 1, 9]
833 | }
834 |
835 | do {
836 | let data: Data = try wrap(Model())
837 | let object = try JSONSerialization.jsonObject(with: data, options: [])
838 |
839 | guard let dictionary = object as? WrappedDictionary else {
840 | return XCTFail("Invalid encoded type")
841 | }
842 |
843 | try verify(dictionary: dictionary, againstDictionary: [
844 | "string" : "A string",
845 | "int" : 42,
846 | "array" : [4, 1, 9]
847 | ])
848 | } catch {
849 | XCTFail(error.toString())
850 | }
851 | }
852 |
853 | func testWrappingArray() {
854 | struct Model {
855 | let string: String
856 | }
857 |
858 | do {
859 | let models = [Model(string: "A"), Model(string: "B"), Model(string: "C")]
860 | let wrapped: [WrappedDictionary] = try wrap(models)
861 | XCTAssertEqual(wrapped.count, 3)
862 |
863 | try verify(dictionary: wrapped[0], againstDictionary: ["string" : "A"])
864 | try verify(dictionary: wrapped[1], againstDictionary: ["string" : "B"])
865 | try verify(dictionary: wrapped[2], againstDictionary: ["string" : "C"])
866 | } catch {
867 | XCTFail(error.toString())
868 | }
869 | }
870 |
871 | func testSnakeCasedKeyWrapping() {
872 | struct Model: WrapCustomizable {
873 | var wrapKeyStyle: WrapKeyStyle { return .convertToSnakeCase }
874 |
875 | let simple = "simple name"
876 | let camelCased = "camel cased name"
877 | let CAPITALIZED = "capitalized name"
878 | let _underscored = "underscored name"
879 | let center_underscored = "center underscored name"
880 | let double__underscored = "double underscored name"
881 | }
882 |
883 | do {
884 | try verify(dictionary: wrap(Model()), againstDictionary: [
885 | "simple" : "simple name",
886 | "camel_cased" : "camel cased name",
887 | "capitalized" : "capitalized name",
888 | "_underscored" : "underscored name",
889 | "center_underscored" : "center underscored name",
890 | "double__underscored" : "double underscored name"
891 | ])
892 | } catch {
893 | XCTFail(error.toString())
894 | }
895 | }
896 |
897 | func testContext() {
898 | struct NestedModel: WrapCustomizable {
899 | let string = "String"
900 |
901 | func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
902 | XCTAssertEqual(context as! String, "Context")
903 | return try? Wrapper(context: context, dateFormatter: dateFormatter).wrap(object: self)
904 | }
905 |
906 | func wrap(propertyNamed propertyName: String, originalValue: Any, context: Any?, dateFormatter: DateFormatter?) throws -> Any? {
907 | XCTAssertEqual(context as! String, "Context")
908 | return context
909 | }
910 | }
911 |
912 | class Model: WrapCustomizable {
913 | let string = "String"
914 | let nestedArray = [NestedModel()]
915 | let nestedDictionary = ["nested" : NestedModel()]
916 |
917 | func wrap(context: Any?, dateFormatter: DateFormatter?) -> Any? {
918 | XCTAssertEqual(context as! String, "Context")
919 | return try? Wrapper(context: context, dateFormatter: dateFormatter).wrap(object: self)
920 | }
921 |
922 | func wrap(propertyNamed propertyName: String, originalValue: Any, context: Any?, dateFormatter: DateFormatter?) throws -> Any? {
923 | XCTAssertEqual(context as! String, "Context")
924 | return nil
925 | }
926 | }
927 |
928 | do {
929 | try verify(dictionary: wrap(Model(), context: "Context"), againstDictionary: [
930 | "string" : "String",
931 | "nestedArray" : [["string" : "Context"]],
932 | "nestedDictionary" : ["nested" : ["string" : "Context"]]
933 | ])
934 | } catch {
935 | XCTFail(error.toString())
936 | }
937 | }
938 |
939 | func testInheritance() {
940 | class Superclass {
941 | let string = "String"
942 | }
943 |
944 | class Subclass: Superclass {
945 | let int = 22
946 | }
947 |
948 | do {
949 | try verify(dictionary: wrap(Subclass()), againstDictionary: [
950 | "string" : "String",
951 | "int" : 22
952 | ])
953 | } catch {
954 | XCTFail(error.toString())
955 | }
956 | }
957 |
958 | func testIgnoringClosureProperties() {
959 | struct StringConvertible: CustomStringConvertible {
960 | var description: String { return "(Function)" }
961 | }
962 |
963 | struct Model {
964 | let closure = {}
965 | let string = "(Function)"
966 | let stringConvertible = StringConvertible()
967 | }
968 |
969 | do {
970 | try verify(dictionary: wrap(Model()), againstDictionary: [
971 | "string" : "(Function)",
972 | "stringConvertible" : [:]
973 | ])
974 | } catch {
975 | XCTFail(error.toString())
976 | }
977 | }
978 | }
979 |
980 | // MARK: - Mocks
981 |
982 | private protocol MockProtocol {
983 | var constantString: String { get }
984 | var mutableInt: Int { get set }
985 | }
986 |
987 | // MARK: - Utilities
988 |
989 | private enum VerificationError: Error {
990 | case arrayCountMismatch(Int, Int)
991 | case dictionaryKeyMismatch([String], [String])
992 | case cannotVerifyValue(Any)
993 | case missingValueForKey(String)
994 | case valueMismatchBetween(Any, Any)
995 | }
996 |
997 | extension VerificationError: CustomStringConvertible {
998 | var description: String {
999 | switch self {
1000 | case .arrayCountMismatch(let countA, let countB):
1001 | return "Array count mismatch: \(countA) vs \(countB)"
1002 | case .dictionaryKeyMismatch(let keysA, let keysB):
1003 | return "Dictionary key count mismatch: \(keysA) vs \(keysB)"
1004 | case .cannotVerifyValue(let value):
1005 | return "Cannot verify value: \(value)"
1006 | case .missingValueForKey(let key):
1007 | return "Missing expected value for key: \(key)"
1008 | case .valueMismatchBetween(let valueA, let valueB):
1009 | return "Values don't match: \(valueA) vs \(valueB)"
1010 | }
1011 | }
1012 | }
1013 |
1014 | private protocol Verifiable {
1015 | static func convert(objectiveCObject: NSObject) -> Self?
1016 | var hashValue: Int { get }
1017 | }
1018 |
1019 | extension Int: Verifiable {
1020 | fileprivate static func convert(objectiveCObject object: NSObject) -> Int? {
1021 | guard let number = object as? NSNumber else {
1022 | return nil
1023 | }
1024 |
1025 | return number.intValue
1026 | }
1027 | }
1028 |
1029 | extension Int64: Verifiable {
1030 | fileprivate static func convert(objectiveCObject object: NSObject) -> Int64? {
1031 | guard let number = object as? NSNumber else {
1032 | return nil
1033 | }
1034 |
1035 | return number.int64Value
1036 | }
1037 | }
1038 |
1039 | extension UInt64: Verifiable {
1040 | fileprivate static func convert(objectiveCObject object: NSObject) -> UInt64? {
1041 | guard let number = object as? NSNumber else {
1042 | return nil
1043 | }
1044 |
1045 | return number.uint64Value
1046 | }
1047 | }
1048 |
1049 | extension Double: Verifiable {
1050 | fileprivate static func convert(objectiveCObject object: NSObject) -> Double? {
1051 | guard let number = object as? NSNumber else {
1052 | return nil
1053 | }
1054 |
1055 | return number.doubleValue
1056 | }
1057 | }
1058 |
1059 | extension String: Verifiable {
1060 | fileprivate static func convert(objectiveCObject object: NSObject) -> String? {
1061 | guard let string = object as? NSString else {
1062 | return nil
1063 | }
1064 |
1065 | #if os(Linux)
1066 | return nil
1067 | #else
1068 | return String(string)
1069 | #endif
1070 | }
1071 | }
1072 |
1073 | extension Date: Verifiable {
1074 | fileprivate static func convert(objectiveCObject object: NSObject) -> Date? {
1075 | guard let date = object as? NSDate else {
1076 | return nil
1077 | }
1078 |
1079 | return Date(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
1080 | }
1081 | }
1082 |
1083 | extension NSNumber: Verifiable {
1084 | fileprivate static func convert(objectiveCObject object: NSObject) -> Self? {
1085 | return nil
1086 | }
1087 | }
1088 |
1089 | extension NSString: Verifiable {
1090 | fileprivate static func convert(objectiveCObject object: NSObject) -> Self? {
1091 | return nil
1092 | }
1093 | }
1094 |
1095 | extension NSDate: Verifiable {
1096 | fileprivate static func convert(objectiveCObject object: NSObject) -> Self? {
1097 | return nil
1098 | }
1099 | }
1100 |
1101 | private func verify(dictionary: WrappedDictionary, againstDictionary expectedDictionary: WrappedDictionary) throws {
1102 | if dictionary.count != expectedDictionary.count {
1103 | throw VerificationError.dictionaryKeyMismatch(Array(dictionary.keys), Array(expectedDictionary.keys))
1104 | }
1105 |
1106 | for (key, expectedValue) in expectedDictionary {
1107 | guard let actualValue = dictionary[key] else {
1108 | throw VerificationError.missingValueForKey(key)
1109 | }
1110 |
1111 | if let expectedNestedDictionary = expectedValue as? WrappedDictionary {
1112 | if let actualNestedDictionary = actualValue as? WrappedDictionary {
1113 | try verify(dictionary: actualNestedDictionary, againstDictionary: expectedNestedDictionary)
1114 | continue
1115 | } else {
1116 | throw VerificationError.valueMismatchBetween(actualValue, expectedValue)
1117 | }
1118 | }
1119 |
1120 | if let expectedNestedArray = expectedValue as? [Any] {
1121 | if let actualNestedArray = actualValue as? [Any] {
1122 | try verify(array: actualNestedArray, againstArray: expectedNestedArray)
1123 | continue
1124 | } else {
1125 | throw VerificationError.valueMismatchBetween(actualValue, expectedValue)
1126 | }
1127 | }
1128 |
1129 | try verify(value: actualValue, againstValue: expectedValue)
1130 | }
1131 | }
1132 |
1133 | private func verify(array: [Any], againstArray expectedArray: [Any]) throws {
1134 | if array.count != expectedArray.count {
1135 | throw VerificationError.arrayCountMismatch(array.count, expectedArray.count)
1136 | }
1137 |
1138 | for (index, expectedValue) in expectedArray.enumerated() {
1139 | let actualValue = array[index]
1140 |
1141 | if let expectedNestedDictionary = expectedValue as? WrappedDictionary {
1142 | if let actualNestedDictionary = actualValue as? WrappedDictionary {
1143 | try verify(dictionary: actualNestedDictionary, againstDictionary: expectedNestedDictionary)
1144 | continue
1145 | } else {
1146 | throw VerificationError.valueMismatchBetween(actualValue, expectedValue)
1147 | }
1148 | }
1149 |
1150 | if let expectedNestedArray = expectedValue as? [Any] {
1151 | if let actualNestedArray = actualValue as? [Any] {
1152 | try verify(array: actualNestedArray, againstArray: expectedNestedArray)
1153 | continue
1154 | } else {
1155 | throw VerificationError.valueMismatchBetween(actualValue, expectedValue)
1156 | }
1157 | }
1158 |
1159 | try verify(value: actualValue, againstValue: expectedValue)
1160 | }
1161 | }
1162 |
1163 | private func verify(value: Any, againstValue expectedValue: Any, convertToObjectiveCObjectIfNeeded: Bool = true) throws {
1164 | guard let expectedVerifiableValue = expectedValue as? Verifiable else {
1165 | throw VerificationError.cannotVerifyValue(expectedValue)
1166 | }
1167 |
1168 | guard let actualVerifiableValue = value as? Verifiable else {
1169 | throw VerificationError.cannotVerifyValue(value)
1170 | }
1171 |
1172 | if actualVerifiableValue.hashValue != expectedVerifiableValue.hashValue {
1173 | if convertToObjectiveCObjectIfNeeded {
1174 | if let objectiveCObject = value as? NSObject {
1175 | let expectedValueType = type(of: expectedVerifiableValue)
1176 |
1177 | guard let convertedObject = expectedValueType.convert(objectiveCObject: objectiveCObject) else {
1178 | throw VerificationError.cannotVerifyValue(value)
1179 | }
1180 |
1181 | return try verify(value: convertedObject, againstValue: expectedVerifiableValue, convertToObjectiveCObjectIfNeeded: false)
1182 | }
1183 | }
1184 |
1185 | throw VerificationError.valueMismatchBetween(value, expectedValue)
1186 | }
1187 | }
1188 |
1189 | private extension Error {
1190 | func toString() -> String {
1191 | return "\(self)"
1192 | }
1193 | }
1194 |
--------------------------------------------------------------------------------