├── .gitignore
├── .travis.yml
├── Info.plist
├── JLRoutes.podspec
├── JLRoutes.xcodeproj
├── project.pbxproj
└── xcshareddata
│ └── xcschemes
│ ├── JLRoutes-iOS.xcscheme
│ └── JLRoutes.xcscheme
├── JLRoutes
├── JLRParsingUtilities.h
├── JLRParsingUtilities.m
├── JLRRouteDefinition.h
├── JLRRouteDefinition.m
├── JLRRouteHandler.h
├── JLRRouteHandler.m
├── JLRRouteRequest.h
├── JLRRouteRequest.m
├── JLRRouteResponse.h
├── JLRRouteResponse.m
├── JLRoutes.h
└── JLRoutes.m
├── JLRoutesTests
├── Info.plist
└── JLRoutesTests.m
├── LICENSE
├── Package.swift
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | .DS_Store
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | *.xcworkspace
13 | !default.xcworkspace
14 | xcuserdata
15 | profile
16 | *.moved-aside
17 | DerivedData
18 | .idea/
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode9.2
3 | branches:
4 | only:
5 | master
6 | script: travis_retry xcodebuild test -scheme 'JLRoutes-iOS' -configuration Debug -destination "platform=iOS Simulator,name=iPhone 6,OS=8.4" -destination "platform=iOS Simulator,name=iPhone 6,OS=9.3" -destination "platform=iOS Simulator,name=iPhone 6,OS=10.3.1" -destination "platform=iOS Simulator,name=iPhone 6,OS=latest"
7 |
--------------------------------------------------------------------------------
/Info.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 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/JLRoutes.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "JLRoutes"
3 | s.version = "2.1"
4 | s.summary = "URL routing library for iOS with a simple block-based API."
5 | s.homepage = "https://github.com/joeldev/JLRoutes"
6 | s.license = "BSD 3-Clause \"New\" License"
7 | s.author = { "Joel Levin" => "joel@joeldev.com" }
8 | s.source = { :git => "https://github.com/joeldev/JLRoutes.git", :tag => "2.1" }
9 | s.framework = 'Foundation'
10 | s.requires_arc = true
11 |
12 | s.source_files = 'JLRoutes', 'JLRoutes/*.{h,m}'
13 |
14 | s.ios.deployment_target = '8.0'
15 | s.osx.deployment_target = '10.10'
16 | s.tvos.deployment_target = '9.0'
17 | end
18 |
--------------------------------------------------------------------------------
/JLRoutes.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5C5AD9B51B45C07300ED25A3 /* JLRoutes.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D33681A16C6DC9300F983AA /* JLRoutes.h */; settings = {ATTRIBUTES = (Public, ); }; };
11 | 5C5AD9B61B45C07800ED25A3 /* JLRoutes.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D33681C16C6DC9300F983AA /* JLRoutes.m */; };
12 | 5D33681616C6DC9300F983AA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D33681516C6DC9300F983AA /* Foundation.framework */; };
13 | 5D33681D16C6DC9300F983AA /* JLRoutes.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D33681C16C6DC9300F983AA /* JLRoutes.m */; };
14 | 5D3CB0721C73DA2700870B55 /* JLRoutesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D3CB0711C73DA2700870B55 /* JLRoutesTests.m */; };
15 | 5D3CB0741C73DA2700870B55 /* libJLRoutes.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D33681216C6DC9300F983AA /* libJLRoutes.a */; };
16 | 5DA69C5C1DAB4C3A007C8E9C /* JLRParsingUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DA69C541DAB4C3A007C8E9C /* JLRParsingUtilities.h */; };
17 | 5DA69C5D1DAB4C3A007C8E9C /* JLRParsingUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DA69C541DAB4C3A007C8E9C /* JLRParsingUtilities.h */; };
18 | 5DA69C5E1DAB4C3A007C8E9C /* JLRParsingUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA69C551DAB4C3A007C8E9C /* JLRParsingUtilities.m */; };
19 | 5DA69C5F1DAB4C3A007C8E9C /* JLRParsingUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA69C551DAB4C3A007C8E9C /* JLRParsingUtilities.m */; };
20 | 5DA69C601DAB4C3A007C8E9C /* JLRRouteDefinition.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DA69C561DAB4C3A007C8E9C /* JLRRouteDefinition.h */; settings = {ATTRIBUTES = (Public, ); }; };
21 | 5DA69C611DAB4C3A007C8E9C /* JLRRouteDefinition.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DA69C561DAB4C3A007C8E9C /* JLRRouteDefinition.h */; settings = {ATTRIBUTES = (Public, ); }; };
22 | 5DA69C621DAB4C3A007C8E9C /* JLRRouteDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA69C571DAB4C3A007C8E9C /* JLRRouteDefinition.m */; };
23 | 5DA69C631DAB4C3A007C8E9C /* JLRRouteDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA69C571DAB4C3A007C8E9C /* JLRRouteDefinition.m */; };
24 | 5DA69C641DAB4C3A007C8E9C /* JLRRouteRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DA69C581DAB4C3A007C8E9C /* JLRRouteRequest.h */; settings = {ATTRIBUTES = (Public, ); }; };
25 | 5DA69C651DAB4C3A007C8E9C /* JLRRouteRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DA69C581DAB4C3A007C8E9C /* JLRRouteRequest.h */; settings = {ATTRIBUTES = (Public, ); }; };
26 | 5DA69C661DAB4C3A007C8E9C /* JLRRouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA69C591DAB4C3A007C8E9C /* JLRRouteRequest.m */; };
27 | 5DA69C671DAB4C3A007C8E9C /* JLRRouteRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA69C591DAB4C3A007C8E9C /* JLRRouteRequest.m */; };
28 | 5DA69C681DAB4C3A007C8E9C /* JLRRouteResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DA69C5A1DAB4C3A007C8E9C /* JLRRouteResponse.h */; settings = {ATTRIBUTES = (Public, ); }; };
29 | 5DA69C691DAB4C3A007C8E9C /* JLRRouteResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DA69C5A1DAB4C3A007C8E9C /* JLRRouteResponse.h */; settings = {ATTRIBUTES = (Public, ); }; };
30 | 5DA69C6A1DAB4C3A007C8E9C /* JLRRouteResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA69C5B1DAB4C3A007C8E9C /* JLRRouteResponse.m */; };
31 | 5DA69C6B1DAB4C3A007C8E9C /* JLRRouteResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA69C5B1DAB4C3A007C8E9C /* JLRRouteResponse.m */; };
32 | 5DE775661EA1B15200375C1D /* JLRRouteHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DE775641EA1B15200375C1D /* JLRRouteHandler.h */; settings = {ATTRIBUTES = (Public, ); }; };
33 | 5DE775671EA1B15200375C1D /* JLRRouteHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DE775641EA1B15200375C1D /* JLRRouteHandler.h */; settings = {ATTRIBUTES = (Public, ); }; };
34 | 5DE775681EA1B15200375C1D /* JLRRouteHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DE775651EA1B15200375C1D /* JLRRouteHandler.m */; };
35 | 5DE775691EA1B15200375C1D /* JLRRouteHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DE775651EA1B15200375C1D /* JLRRouteHandler.m */; };
36 | D0C20A3017061066007746A6 /* JLRoutes.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D33681A16C6DC9300F983AA /* JLRoutes.h */; settings = {ATTRIBUTES = (Public, ); }; };
37 | /* End PBXBuildFile section */
38 |
39 | /* Begin PBXContainerItemProxy section */
40 | 5D3CB0751C73DA2700870B55 /* PBXContainerItemProxy */ = {
41 | isa = PBXContainerItemProxy;
42 | containerPortal = 5D33680A16C6DC9300F983AA /* Project object */;
43 | proxyType = 1;
44 | remoteGlobalIDString = 5D33681116C6DC9300F983AA;
45 | remoteInfo = JLRoutes;
46 | };
47 | /* End PBXContainerItemProxy section */
48 |
49 | /* Begin PBXFileReference section */
50 | 5CB4EB491B45BF5B0058E91A /* JLRoutes.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JLRoutes.framework; sourceTree = BUILT_PRODUCTS_DIR; };
51 | 5D33681216C6DC9300F983AA /* libJLRoutes.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libJLRoutes.a; sourceTree = BUILT_PRODUCTS_DIR; };
52 | 5D33681516C6DC9300F983AA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
53 | 5D33681A16C6DC9300F983AA /* JLRoutes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JLRoutes.h; sourceTree = ""; };
54 | 5D33681C16C6DC9300F983AA /* JLRoutes.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JLRoutes.m; sourceTree = ""; };
55 | 5D33682616C6DC9300F983AA /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
56 | 5D33684416C6DCCC00F983AA /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
57 | 5D3CB06F1C73DA2700870B55 /* JLRoutesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JLRoutesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
58 | 5D3CB0711C73DA2700870B55 /* JLRoutesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JLRoutesTests.m; sourceTree = ""; };
59 | 5D3CB0731C73DA2700870B55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
60 | 5DA69C541DAB4C3A007C8E9C /* JLRParsingUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JLRParsingUtilities.h; sourceTree = ""; };
61 | 5DA69C551DAB4C3A007C8E9C /* JLRParsingUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JLRParsingUtilities.m; sourceTree = ""; };
62 | 5DA69C561DAB4C3A007C8E9C /* JLRRouteDefinition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JLRRouteDefinition.h; sourceTree = ""; };
63 | 5DA69C571DAB4C3A007C8E9C /* JLRRouteDefinition.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JLRRouteDefinition.m; sourceTree = ""; };
64 | 5DA69C581DAB4C3A007C8E9C /* JLRRouteRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JLRRouteRequest.h; sourceTree = ""; };
65 | 5DA69C591DAB4C3A007C8E9C /* JLRRouteRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JLRRouteRequest.m; sourceTree = ""; };
66 | 5DA69C5A1DAB4C3A007C8E9C /* JLRRouteResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JLRRouteResponse.h; sourceTree = ""; };
67 | 5DA69C5B1DAB4C3A007C8E9C /* JLRRouteResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JLRRouteResponse.m; sourceTree = ""; };
68 | 5DE775641EA1B15200375C1D /* JLRRouteHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JLRRouteHandler.h; sourceTree = ""; };
69 | 5DE775651EA1B15200375C1D /* JLRRouteHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JLRRouteHandler.m; sourceTree = ""; };
70 | /* End PBXFileReference section */
71 |
72 | /* Begin PBXFrameworksBuildPhase section */
73 | 5CB4EB451B45BF5B0058E91A /* Frameworks */ = {
74 | isa = PBXFrameworksBuildPhase;
75 | buildActionMask = 2147483647;
76 | files = (
77 | );
78 | runOnlyForDeploymentPostprocessing = 0;
79 | };
80 | 5D33680F16C6DC9300F983AA /* Frameworks */ = {
81 | isa = PBXFrameworksBuildPhase;
82 | buildActionMask = 2147483647;
83 | files = (
84 | 5D33681616C6DC9300F983AA /* Foundation.framework in Frameworks */,
85 | );
86 | runOnlyForDeploymentPostprocessing = 0;
87 | };
88 | 5D3CB06C1C73DA2700870B55 /* Frameworks */ = {
89 | isa = PBXFrameworksBuildPhase;
90 | buildActionMask = 2147483647;
91 | files = (
92 | 5D3CB0741C73DA2700870B55 /* libJLRoutes.a in Frameworks */,
93 | );
94 | runOnlyForDeploymentPostprocessing = 0;
95 | };
96 | /* End PBXFrameworksBuildPhase section */
97 |
98 | /* Begin PBXGroup section */
99 | 5D33680916C6DC9300F983AA = {
100 | isa = PBXGroup;
101 | children = (
102 | 5D33681716C6DC9300F983AA /* JLRoutes */,
103 | 5D3CB0701C73DA2700870B55 /* JLRoutesTests */,
104 | 5D33681416C6DC9300F983AA /* Frameworks */,
105 | 5D33681316C6DC9300F983AA /* Products */,
106 | );
107 | sourceTree = "";
108 | };
109 | 5D33681316C6DC9300F983AA /* Products */ = {
110 | isa = PBXGroup;
111 | children = (
112 | 5D33681216C6DC9300F983AA /* libJLRoutes.a */,
113 | 5CB4EB491B45BF5B0058E91A /* JLRoutes.framework */,
114 | 5D3CB06F1C73DA2700870B55 /* JLRoutesTests.xctest */,
115 | );
116 | name = Products;
117 | sourceTree = "";
118 | };
119 | 5D33681416C6DC9300F983AA /* Frameworks */ = {
120 | isa = PBXGroup;
121 | children = (
122 | 5D33681516C6DC9300F983AA /* Foundation.framework */,
123 | 5D33682616C6DC9300F983AA /* UIKit.framework */,
124 | 5D33684416C6DCCC00F983AA /* CoreGraphics.framework */,
125 | );
126 | name = Frameworks;
127 | sourceTree = "";
128 | };
129 | 5D33681716C6DC9300F983AA /* JLRoutes */ = {
130 | isa = PBXGroup;
131 | children = (
132 | 5D33681A16C6DC9300F983AA /* JLRoutes.h */,
133 | 5D33681C16C6DC9300F983AA /* JLRoutes.m */,
134 | 5DA69C531DAB4C3A007C8E9C /* Classes */,
135 | );
136 | path = JLRoutes;
137 | sourceTree = "";
138 | };
139 | 5D3CB0701C73DA2700870B55 /* JLRoutesTests */ = {
140 | isa = PBXGroup;
141 | children = (
142 | 5D3CB0711C73DA2700870B55 /* JLRoutesTests.m */,
143 | 5D3CB0731C73DA2700870B55 /* Info.plist */,
144 | );
145 | path = JLRoutesTests;
146 | sourceTree = "";
147 | };
148 | 5DA69C531DAB4C3A007C8E9C /* Classes */ = {
149 | isa = PBXGroup;
150 | children = (
151 | 5DA69C561DAB4C3A007C8E9C /* JLRRouteDefinition.h */,
152 | 5DA69C571DAB4C3A007C8E9C /* JLRRouteDefinition.m */,
153 | 5DE775641EA1B15200375C1D /* JLRRouteHandler.h */,
154 | 5DE775651EA1B15200375C1D /* JLRRouteHandler.m */,
155 | 5DA69C581DAB4C3A007C8E9C /* JLRRouteRequest.h */,
156 | 5DA69C591DAB4C3A007C8E9C /* JLRRouteRequest.m */,
157 | 5DA69C5A1DAB4C3A007C8E9C /* JLRRouteResponse.h */,
158 | 5DA69C5B1DAB4C3A007C8E9C /* JLRRouteResponse.m */,
159 | 5DA69C541DAB4C3A007C8E9C /* JLRParsingUtilities.h */,
160 | 5DA69C551DAB4C3A007C8E9C /* JLRParsingUtilities.m */,
161 | );
162 | name = Classes;
163 | sourceTree = "";
164 | };
165 | /* End PBXGroup section */
166 |
167 | /* Begin PBXHeadersBuildPhase section */
168 | 5CB4EB461B45BF5B0058E91A /* Headers */ = {
169 | isa = PBXHeadersBuildPhase;
170 | buildActionMask = 2147483647;
171 | files = (
172 | 5DA69C691DAB4C3A007C8E9C /* JLRRouteResponse.h in Headers */,
173 | 5DA69C5D1DAB4C3A007C8E9C /* JLRParsingUtilities.h in Headers */,
174 | 5DE775671EA1B15200375C1D /* JLRRouteHandler.h in Headers */,
175 | 5DA69C611DAB4C3A007C8E9C /* JLRRouteDefinition.h in Headers */,
176 | 5C5AD9B51B45C07300ED25A3 /* JLRoutes.h in Headers */,
177 | 5DA69C651DAB4C3A007C8E9C /* JLRRouteRequest.h in Headers */,
178 | );
179 | runOnlyForDeploymentPostprocessing = 0;
180 | };
181 | D0C20A2F1706105A007746A6 /* Headers */ = {
182 | isa = PBXHeadersBuildPhase;
183 | buildActionMask = 2147483647;
184 | files = (
185 | 5DA69C681DAB4C3A007C8E9C /* JLRRouteResponse.h in Headers */,
186 | 5DA69C5C1DAB4C3A007C8E9C /* JLRParsingUtilities.h in Headers */,
187 | 5DE775661EA1B15200375C1D /* JLRRouteHandler.h in Headers */,
188 | 5DA69C601DAB4C3A007C8E9C /* JLRRouteDefinition.h in Headers */,
189 | D0C20A3017061066007746A6 /* JLRoutes.h in Headers */,
190 | 5DA69C641DAB4C3A007C8E9C /* JLRRouteRequest.h in Headers */,
191 | );
192 | runOnlyForDeploymentPostprocessing = 0;
193 | };
194 | /* End PBXHeadersBuildPhase section */
195 |
196 | /* Begin PBXNativeTarget section */
197 | 5CB4EB481B45BF5B0058E91A /* JLRoutes-iOS */ = {
198 | isa = PBXNativeTarget;
199 | buildConfigurationList = 5CB4EB601B45BF5B0058E91A /* Build configuration list for PBXNativeTarget "JLRoutes-iOS" */;
200 | buildPhases = (
201 | 5CB4EB441B45BF5B0058E91A /* Sources */,
202 | 5CB4EB451B45BF5B0058E91A /* Frameworks */,
203 | 5CB4EB461B45BF5B0058E91A /* Headers */,
204 | 5CB4EB471B45BF5B0058E91A /* Resources */,
205 | );
206 | buildRules = (
207 | );
208 | dependencies = (
209 | );
210 | name = "JLRoutes-iOS";
211 | productName = "JLRoutes-iOS";
212 | productReference = 5CB4EB491B45BF5B0058E91A /* JLRoutes.framework */;
213 | productType = "com.apple.product-type.framework";
214 | };
215 | 5D33681116C6DC9300F983AA /* JLRoutes */ = {
216 | isa = PBXNativeTarget;
217 | buildConfigurationList = 5D33683716C6DC9300F983AA /* Build configuration list for PBXNativeTarget "JLRoutes" */;
218 | buildPhases = (
219 | 5D33680E16C6DC9300F983AA /* Sources */,
220 | 5D33680F16C6DC9300F983AA /* Frameworks */,
221 | D0C20A2F1706105A007746A6 /* Headers */,
222 | );
223 | buildRules = (
224 | );
225 | dependencies = (
226 | );
227 | name = JLRoutes;
228 | productName = JLRoutes;
229 | productReference = 5D33681216C6DC9300F983AA /* libJLRoutes.a */;
230 | productType = "com.apple.product-type.library.static";
231 | };
232 | 5D3CB06E1C73DA2700870B55 /* JLRoutesTests */ = {
233 | isa = PBXNativeTarget;
234 | buildConfigurationList = 5D3CB0771C73DA2700870B55 /* Build configuration list for PBXNativeTarget "JLRoutesTests" */;
235 | buildPhases = (
236 | 5D3CB06B1C73DA2700870B55 /* Sources */,
237 | 5D3CB06C1C73DA2700870B55 /* Frameworks */,
238 | 5D3CB06D1C73DA2700870B55 /* Resources */,
239 | );
240 | buildRules = (
241 | );
242 | dependencies = (
243 | 5D3CB0761C73DA2700870B55 /* PBXTargetDependency */,
244 | );
245 | name = JLRoutesTests;
246 | productName = JLRoutesTests;
247 | productReference = 5D3CB06F1C73DA2700870B55 /* JLRoutesTests.xctest */;
248 | productType = "com.apple.product-type.bundle.unit-test";
249 | };
250 | /* End PBXNativeTarget section */
251 |
252 | /* Begin PBXProject section */
253 | 5D33680A16C6DC9300F983AA /* Project object */ = {
254 | isa = PBXProject;
255 | attributes = {
256 | LastTestingUpgradeCheck = 0510;
257 | LastUpgradeCheck = 0900;
258 | ORGANIZATIONNAME = "Afterwork Studios";
259 | TargetAttributes = {
260 | 5CB4EB481B45BF5B0058E91A = {
261 | CreatedOnToolsVersion = 6.4;
262 | };
263 | 5D3CB06E1C73DA2700870B55 = {
264 | CreatedOnToolsVersion = 7.3;
265 | };
266 | };
267 | };
268 | buildConfigurationList = 5D33680D16C6DC9300F983AA /* Build configuration list for PBXProject "JLRoutes" */;
269 | compatibilityVersion = "Xcode 3.2";
270 | developmentRegion = English;
271 | hasScannedForEncodings = 0;
272 | knownRegions = (
273 | en,
274 | );
275 | mainGroup = 5D33680916C6DC9300F983AA;
276 | productRefGroup = 5D33681316C6DC9300F983AA /* Products */;
277 | projectDirPath = "";
278 | projectRoot = "";
279 | targets = (
280 | 5D33681116C6DC9300F983AA /* JLRoutes */,
281 | 5CB4EB481B45BF5B0058E91A /* JLRoutes-iOS */,
282 | 5D3CB06E1C73DA2700870B55 /* JLRoutesTests */,
283 | );
284 | };
285 | /* End PBXProject section */
286 |
287 | /* Begin PBXResourcesBuildPhase section */
288 | 5CB4EB471B45BF5B0058E91A /* Resources */ = {
289 | isa = PBXResourcesBuildPhase;
290 | buildActionMask = 2147483647;
291 | files = (
292 | );
293 | runOnlyForDeploymentPostprocessing = 0;
294 | };
295 | 5D3CB06D1C73DA2700870B55 /* Resources */ = {
296 | isa = PBXResourcesBuildPhase;
297 | buildActionMask = 2147483647;
298 | files = (
299 | );
300 | runOnlyForDeploymentPostprocessing = 0;
301 | };
302 | /* End PBXResourcesBuildPhase section */
303 |
304 | /* Begin PBXSourcesBuildPhase section */
305 | 5CB4EB441B45BF5B0058E91A /* Sources */ = {
306 | isa = PBXSourcesBuildPhase;
307 | buildActionMask = 2147483647;
308 | files = (
309 | 5DA69C6B1DAB4C3A007C8E9C /* JLRRouteResponse.m in Sources */,
310 | 5DA69C671DAB4C3A007C8E9C /* JLRRouteRequest.m in Sources */,
311 | 5C5AD9B61B45C07800ED25A3 /* JLRoutes.m in Sources */,
312 | 5DA69C5F1DAB4C3A007C8E9C /* JLRParsingUtilities.m in Sources */,
313 | 5DA69C631DAB4C3A007C8E9C /* JLRRouteDefinition.m in Sources */,
314 | 5DE775691EA1B15200375C1D /* JLRRouteHandler.m in Sources */,
315 | );
316 | runOnlyForDeploymentPostprocessing = 0;
317 | };
318 | 5D33680E16C6DC9300F983AA /* Sources */ = {
319 | isa = PBXSourcesBuildPhase;
320 | buildActionMask = 2147483647;
321 | files = (
322 | 5DA69C6A1DAB4C3A007C8E9C /* JLRRouteResponse.m in Sources */,
323 | 5DA69C661DAB4C3A007C8E9C /* JLRRouteRequest.m in Sources */,
324 | 5D33681D16C6DC9300F983AA /* JLRoutes.m in Sources */,
325 | 5DA69C5E1DAB4C3A007C8E9C /* JLRParsingUtilities.m in Sources */,
326 | 5DA69C621DAB4C3A007C8E9C /* JLRRouteDefinition.m in Sources */,
327 | 5DE775681EA1B15200375C1D /* JLRRouteHandler.m in Sources */,
328 | );
329 | runOnlyForDeploymentPostprocessing = 0;
330 | };
331 | 5D3CB06B1C73DA2700870B55 /* Sources */ = {
332 | isa = PBXSourcesBuildPhase;
333 | buildActionMask = 2147483647;
334 | files = (
335 | 5D3CB0721C73DA2700870B55 /* JLRoutesTests.m in Sources */,
336 | );
337 | runOnlyForDeploymentPostprocessing = 0;
338 | };
339 | /* End PBXSourcesBuildPhase section */
340 |
341 | /* Begin PBXTargetDependency section */
342 | 5D3CB0761C73DA2700870B55 /* PBXTargetDependency */ = {
343 | isa = PBXTargetDependency;
344 | target = 5D33681116C6DC9300F983AA /* JLRoutes */;
345 | targetProxy = 5D3CB0751C73DA2700870B55 /* PBXContainerItemProxy */;
346 | };
347 | /* End PBXTargetDependency section */
348 |
349 | /* Begin XCBuildConfiguration section */
350 | 5CB4EB5C1B45BF5B0058E91A /* Debug */ = {
351 | isa = XCBuildConfiguration;
352 | buildSettings = {
353 | CLANG_ENABLE_MODULES = YES;
354 | CLANG_WARN_BOOL_CONVERSION = YES;
355 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
356 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
357 | CLANG_WARN_UNREACHABLE_CODE = YES;
358 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
359 | CURRENT_PROJECT_VERSION = 1;
360 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
361 | DEFINES_MODULE = YES;
362 | DYLIB_COMPATIBILITY_VERSION = 1;
363 | DYLIB_CURRENT_VERSION = 1;
364 | DYLIB_INSTALL_NAME_BASE = "@rpath";
365 | ENABLE_STRICT_OBJC_MSGSEND = YES;
366 | GCC_NO_COMMON_BLOCKS = YES;
367 | GCC_PREPROCESSOR_DEFINITIONS = (
368 | "DEBUG=1",
369 | "$(inherited)",
370 | );
371 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
372 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
373 | GCC_WARN_UNDECLARED_SELECTOR = YES;
374 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
375 | GCC_WARN_UNUSED_FUNCTION = YES;
376 | INFOPLIST_FILE = Info.plist;
377 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
378 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
379 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
380 | MTL_ENABLE_DEBUG_INFO = YES;
381 | PRODUCT_BUNDLE_IDENTIFIER = "com.joeldev.$(PRODUCT_NAME:rfc1034identifier)";
382 | PRODUCT_NAME = JLRoutes;
383 | SKIP_INSTALL = YES;
384 | TARGETED_DEVICE_FAMILY = "1,2";
385 | VERSIONING_SYSTEM = "apple-generic";
386 | VERSION_INFO_PREFIX = "";
387 | };
388 | name = Debug;
389 | };
390 | 5CB4EB5D1B45BF5B0058E91A /* Release */ = {
391 | isa = XCBuildConfiguration;
392 | buildSettings = {
393 | CLANG_ENABLE_MODULES = YES;
394 | CLANG_WARN_BOOL_CONVERSION = YES;
395 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
397 | CLANG_WARN_UNREACHABLE_CODE = YES;
398 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
399 | COPY_PHASE_STRIP = NO;
400 | CURRENT_PROJECT_VERSION = 1;
401 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
402 | DEFINES_MODULE = YES;
403 | DYLIB_COMPATIBILITY_VERSION = 1;
404 | DYLIB_CURRENT_VERSION = 1;
405 | DYLIB_INSTALL_NAME_BASE = "@rpath";
406 | ENABLE_NS_ASSERTIONS = YES;
407 | ENABLE_STRICT_OBJC_MSGSEND = YES;
408 | GCC_NO_COMMON_BLOCKS = YES;
409 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
410 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
411 | GCC_WARN_UNDECLARED_SELECTOR = YES;
412 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
413 | GCC_WARN_UNUSED_FUNCTION = YES;
414 | INFOPLIST_FILE = Info.plist;
415 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
416 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
418 | MTL_ENABLE_DEBUG_INFO = NO;
419 | PRODUCT_BUNDLE_IDENTIFIER = "com.joeldev.$(PRODUCT_NAME:rfc1034identifier)";
420 | PRODUCT_NAME = JLRoutes;
421 | SKIP_INSTALL = YES;
422 | TARGETED_DEVICE_FAMILY = "1,2";
423 | VERSIONING_SYSTEM = "apple-generic";
424 | VERSION_INFO_PREFIX = "";
425 | };
426 | name = Release;
427 | };
428 | 5D33683516C6DC9300F983AA /* Debug */ = {
429 | isa = XCBuildConfiguration;
430 | buildSettings = {
431 | ALWAYS_SEARCH_USER_PATHS = NO;
432 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
433 | CLANG_CXX_LIBRARY = "libc++";
434 | CLANG_ENABLE_OBJC_ARC = YES;
435 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
436 | CLANG_WARN_BOOL_CONVERSION = YES;
437 | CLANG_WARN_COMMA = YES;
438 | CLANG_WARN_CONSTANT_CONVERSION = YES;
439 | CLANG_WARN_EMPTY_BODY = YES;
440 | CLANG_WARN_ENUM_CONVERSION = YES;
441 | CLANG_WARN_INFINITE_RECURSION = YES;
442 | CLANG_WARN_INT_CONVERSION = YES;
443 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
444 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
445 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
446 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
447 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
448 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
449 | CLANG_WARN_STRICT_PROTOTYPES = YES;
450 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
451 | CLANG_WARN_UNREACHABLE_CODE = YES;
452 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
453 | COPY_PHASE_STRIP = NO;
454 | ENABLE_STRICT_OBJC_MSGSEND = YES;
455 | ENABLE_TESTABILITY = YES;
456 | GCC_C_LANGUAGE_STANDARD = gnu99;
457 | GCC_DYNAMIC_NO_PIC = NO;
458 | GCC_NO_COMMON_BLOCKS = YES;
459 | GCC_OPTIMIZATION_LEVEL = 0;
460 | GCC_PREPROCESSOR_DEFINITIONS = (
461 | "DEBUG=1",
462 | "$(inherited)",
463 | );
464 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
465 | GCC_TREAT_WARNINGS_AS_ERRORS = YES;
466 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
467 | GCC_WARN_ABOUT_RETURN_TYPE = YES;
468 | GCC_WARN_PEDANTIC = NO;
469 | GCC_WARN_UNDECLARED_SELECTOR = YES;
470 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
471 | GCC_WARN_UNUSED_FUNCTION = YES;
472 | GCC_WARN_UNUSED_VARIABLE = YES;
473 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
474 | ONLY_ACTIVE_ARCH = YES;
475 | SDKROOT = iphoneos;
476 | };
477 | name = Debug;
478 | };
479 | 5D33683616C6DC9300F983AA /* Release */ = {
480 | isa = XCBuildConfiguration;
481 | buildSettings = {
482 | ALWAYS_SEARCH_USER_PATHS = NO;
483 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
484 | CLANG_CXX_LIBRARY = "libc++";
485 | CLANG_ENABLE_OBJC_ARC = YES;
486 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
487 | CLANG_WARN_BOOL_CONVERSION = YES;
488 | CLANG_WARN_COMMA = YES;
489 | CLANG_WARN_CONSTANT_CONVERSION = YES;
490 | CLANG_WARN_EMPTY_BODY = YES;
491 | CLANG_WARN_ENUM_CONVERSION = YES;
492 | CLANG_WARN_INFINITE_RECURSION = YES;
493 | CLANG_WARN_INT_CONVERSION = YES;
494 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
495 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
496 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
497 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
498 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
499 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
500 | CLANG_WARN_STRICT_PROTOTYPES = YES;
501 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
502 | CLANG_WARN_UNREACHABLE_CODE = YES;
503 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
504 | COPY_PHASE_STRIP = YES;
505 | ENABLE_STRICT_OBJC_MSGSEND = YES;
506 | GCC_C_LANGUAGE_STANDARD = gnu99;
507 | GCC_NO_COMMON_BLOCKS = YES;
508 | GCC_TREAT_WARNINGS_AS_ERRORS = YES;
509 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
510 | GCC_WARN_ABOUT_RETURN_TYPE = YES;
511 | GCC_WARN_PEDANTIC = NO;
512 | GCC_WARN_UNDECLARED_SELECTOR = YES;
513 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
514 | GCC_WARN_UNUSED_FUNCTION = YES;
515 | GCC_WARN_UNUSED_VARIABLE = YES;
516 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
517 | SDKROOT = iphoneos;
518 | VALIDATE_PRODUCT = YES;
519 | };
520 | name = Release;
521 | };
522 | 5D33683816C6DC9300F983AA /* Debug */ = {
523 | isa = XCBuildConfiguration;
524 | buildSettings = {
525 | DSTROOT = /tmp/JLRoutes.dst;
526 | GCC_PRECOMPILE_PREFIX_HEADER = NO;
527 | GCC_PREFIX_HEADER = "";
528 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
529 | OTHER_LDFLAGS = "-ObjC";
530 | PRODUCT_NAME = "$(TARGET_NAME)";
531 | PUBLIC_HEADERS_FOLDER_PATH = "include/${PRODUCT_NAME}";
532 | SKIP_INSTALL = YES;
533 | };
534 | name = Debug;
535 | };
536 | 5D33683916C6DC9300F983AA /* Release */ = {
537 | isa = XCBuildConfiguration;
538 | buildSettings = {
539 | DSTROOT = /tmp/JLRoutes.dst;
540 | GCC_PRECOMPILE_PREFIX_HEADER = NO;
541 | GCC_PREFIX_HEADER = "";
542 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
543 | OTHER_LDFLAGS = "-ObjC";
544 | PRODUCT_NAME = "$(TARGET_NAME)";
545 | PUBLIC_HEADERS_FOLDER_PATH = "include/${PRODUCT_NAME}";
546 | SKIP_INSTALL = YES;
547 | };
548 | name = Release;
549 | };
550 | 5D3CB0781C73DA2700870B55 /* Debug */ = {
551 | isa = XCBuildConfiguration;
552 | buildSettings = {
553 | CLANG_ANALYZER_NONNULL = YES;
554 | CLANG_ENABLE_MODULES = YES;
555 | CLANG_WARN_BOOL_CONVERSION = YES;
556 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
557 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
558 | CLANG_WARN_UNREACHABLE_CODE = YES;
559 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
560 | DEBUG_INFORMATION_FORMAT = dwarf;
561 | ENABLE_STRICT_OBJC_MSGSEND = YES;
562 | GCC_NO_COMMON_BLOCKS = YES;
563 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
564 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
565 | GCC_WARN_UNDECLARED_SELECTOR = YES;
566 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
567 | GCC_WARN_UNUSED_FUNCTION = YES;
568 | INFOPLIST_FILE = JLRoutesTests/Info.plist;
569 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
570 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
571 | MTL_ENABLE_DEBUG_INFO = YES;
572 | OTHER_LDFLAGS = (
573 | "-all_load",
574 | "-ObjC",
575 | );
576 | PRODUCT_BUNDLE_IDENTIFIER = com.joeldev.JLRoutesTests;
577 | PRODUCT_NAME = "$(TARGET_NAME)";
578 | };
579 | name = Debug;
580 | };
581 | 5D3CB0791C73DA2700870B55 /* Release */ = {
582 | isa = XCBuildConfiguration;
583 | buildSettings = {
584 | CLANG_ANALYZER_NONNULL = YES;
585 | CLANG_ENABLE_MODULES = YES;
586 | CLANG_WARN_BOOL_CONVERSION = YES;
587 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
588 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
589 | CLANG_WARN_UNREACHABLE_CODE = YES;
590 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
591 | COPY_PHASE_STRIP = NO;
592 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
593 | ENABLE_NS_ASSERTIONS = NO;
594 | ENABLE_STRICT_OBJC_MSGSEND = YES;
595 | GCC_NO_COMMON_BLOCKS = YES;
596 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
597 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
598 | GCC_WARN_UNDECLARED_SELECTOR = YES;
599 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
600 | GCC_WARN_UNUSED_FUNCTION = YES;
601 | INFOPLIST_FILE = JLRoutesTests/Info.plist;
602 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
603 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
604 | MTL_ENABLE_DEBUG_INFO = NO;
605 | OTHER_LDFLAGS = (
606 | "-all_load",
607 | "-ObjC",
608 | );
609 | PRODUCT_BUNDLE_IDENTIFIER = com.joeldev.JLRoutesTests;
610 | PRODUCT_NAME = "$(TARGET_NAME)";
611 | };
612 | name = Release;
613 | };
614 | /* End XCBuildConfiguration section */
615 |
616 | /* Begin XCConfigurationList section */
617 | 5CB4EB601B45BF5B0058E91A /* Build configuration list for PBXNativeTarget "JLRoutes-iOS" */ = {
618 | isa = XCConfigurationList;
619 | buildConfigurations = (
620 | 5CB4EB5C1B45BF5B0058E91A /* Debug */,
621 | 5CB4EB5D1B45BF5B0058E91A /* Release */,
622 | );
623 | defaultConfigurationIsVisible = 0;
624 | defaultConfigurationName = Release;
625 | };
626 | 5D33680D16C6DC9300F983AA /* Build configuration list for PBXProject "JLRoutes" */ = {
627 | isa = XCConfigurationList;
628 | buildConfigurations = (
629 | 5D33683516C6DC9300F983AA /* Debug */,
630 | 5D33683616C6DC9300F983AA /* Release */,
631 | );
632 | defaultConfigurationIsVisible = 0;
633 | defaultConfigurationName = Release;
634 | };
635 | 5D33683716C6DC9300F983AA /* Build configuration list for PBXNativeTarget "JLRoutes" */ = {
636 | isa = XCConfigurationList;
637 | buildConfigurations = (
638 | 5D33683816C6DC9300F983AA /* Debug */,
639 | 5D33683916C6DC9300F983AA /* Release */,
640 | );
641 | defaultConfigurationIsVisible = 0;
642 | defaultConfigurationName = Release;
643 | };
644 | 5D3CB0771C73DA2700870B55 /* Build configuration list for PBXNativeTarget "JLRoutesTests" */ = {
645 | isa = XCConfigurationList;
646 | buildConfigurations = (
647 | 5D3CB0781C73DA2700870B55 /* Debug */,
648 | 5D3CB0791C73DA2700870B55 /* Release */,
649 | );
650 | defaultConfigurationIsVisible = 0;
651 | defaultConfigurationName = Release;
652 | };
653 | /* End XCConfigurationList section */
654 | };
655 | rootObject = 5D33680A16C6DC9300F983AA /* Project object */;
656 | }
657 |
--------------------------------------------------------------------------------
/JLRoutes.xcodeproj/xcshareddata/xcschemes/JLRoutes-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
48 |
54 |
55 |
56 |
57 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
80 |
81 |
87 |
88 |
89 |
90 |
91 |
92 |
98 |
99 |
105 |
106 |
107 |
108 |
110 |
111 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/JLRoutes.xcodeproj/xcshareddata/xcschemes/JLRoutes.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
57 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
76 |
78 |
79 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/JLRoutes/JLRParsingUtilities.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 |
18 | @interface JLRParsingUtilities : NSObject
19 |
20 | + (NSString *)variableValueFrom:(NSString *)value decodePlusSymbols:(BOOL)decodePlusSymbols;
21 |
22 | + (NSDictionary *)queryParams:(NSDictionary *)queryParams decodePlusSymbols:(BOOL)decodePlusSymbols;
23 |
24 | + (NSArray *)expandOptionalRoutePatternsForPattern:(NSString *)routePattern;
25 |
26 | @end
27 |
28 |
29 | NS_ASSUME_NONNULL_END
30 |
--------------------------------------------------------------------------------
/JLRoutes/JLRParsingUtilities.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import "JLRParsingUtilities.h"
14 |
15 |
16 | @interface NSArray (JLRoutes_Utilities)
17 |
18 | - (NSArray *)JLRoutes_allOrderedCombinations;
19 | - (NSArray *)JLRoutes_filter:(BOOL (^)(id object))filterBlock;
20 | - (NSArray *)JLRoutes_map:(id (^)(id object))mapBlock;
21 |
22 | @end
23 |
24 |
25 | @interface NSString (JLRoutes_Utilities)
26 |
27 | - (NSArray *)JLRoutes_trimmedPathComponents;
28 |
29 | @end
30 |
31 |
32 | #pragma mark - Parsing Utility Methods
33 |
34 |
35 | @interface JLRParsingUtilities_RouteSubpath : NSObject
36 |
37 | @property (nonatomic, strong) NSArray *subpathComponents;
38 | @property (nonatomic, assign) BOOL isOptionalSubpath;
39 |
40 | @end
41 |
42 |
43 | @implementation JLRParsingUtilities_RouteSubpath
44 |
45 | - (NSString *)description
46 | {
47 | NSString *type = self.isOptionalSubpath ? @"OPTIONAL" : @"REQUIRED";
48 | return [NSString stringWithFormat:@"%@ - %@: %@", [super description], type, [self.subpathComponents componentsJoinedByString:@"/"]];
49 | }
50 |
51 | - (BOOL)isEqual:(id)object
52 | {
53 | if (![object isKindOfClass:[self class]]) {
54 | return NO;
55 | }
56 |
57 | JLRParsingUtilities_RouteSubpath *otherSubpath = (JLRParsingUtilities_RouteSubpath *)object;
58 | if (![self.subpathComponents isEqual:otherSubpath.subpathComponents]) {
59 | return NO;
60 | }
61 |
62 | if (self.isOptionalSubpath != otherSubpath.isOptionalSubpath) {
63 | return NO;
64 | }
65 |
66 | return YES;
67 | }
68 |
69 | - (NSUInteger)hash
70 | {
71 | return self.subpathComponents.hash ^ self.isOptionalSubpath;
72 | }
73 |
74 | @end
75 |
76 |
77 | @implementation JLRParsingUtilities
78 |
79 | + (NSString *)variableValueFrom:(NSString *)value decodePlusSymbols:(BOOL)decodePlusSymbols
80 | {
81 | if (!decodePlusSymbols) {
82 | return value;
83 | }
84 | return [value stringByReplacingOccurrencesOfString:@"+" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, value.length)];
85 | }
86 |
87 | + (NSDictionary *)queryParams:(NSDictionary *)queryParams decodePlusSymbols:(BOOL)decodePlusSymbols
88 | {
89 | if (!decodePlusSymbols) {
90 | return queryParams;
91 | }
92 |
93 | NSMutableDictionary *updatedQueryParams = [NSMutableDictionary dictionary];
94 |
95 | for (NSString *name in queryParams) {
96 | id value = queryParams[name];
97 |
98 | if ([value isKindOfClass:[NSArray class]]) {
99 | NSMutableArray *variables = [NSMutableArray array];
100 | for (NSString *arrayValue in (NSArray *)value) {
101 | [variables addObject:[self variableValueFrom:arrayValue decodePlusSymbols:YES]];
102 | }
103 | updatedQueryParams[name] = [variables copy];
104 | } else if ([value isKindOfClass:[NSString class]]) {
105 | NSString *variable = [self variableValueFrom:value decodePlusSymbols:YES];
106 | updatedQueryParams[name] = variable;
107 | } else {
108 | NSAssert(NO, @"Unexpected query parameter type: %@", NSStringFromClass([value class]));
109 | }
110 | }
111 |
112 | return [updatedQueryParams copy];
113 | }
114 |
115 | + (NSArray *)expandOptionalRoutePatternsForPattern:(NSString *)routePattern
116 | {
117 | /* this method exists to take a route pattern that is known to contain optional params, such as:
118 |
119 | /path/:thing/(/a)(/b)(/c)
120 |
121 | and create the following paths:
122 |
123 | /path/:thing/a/b/c
124 | /path/:thing/a/b
125 | /path/:thing/a/c
126 | /path/:thing/b/c
127 | /path/:thing/a
128 | /path/:thing/b
129 | /path/:thing/c
130 |
131 | */
132 |
133 | if ([routePattern rangeOfString:@"("].location == NSNotFound) {
134 | return @[];
135 | }
136 |
137 | // First, parse the route pattern into subpath objects.
138 | NSArray *subpaths = [self _routeSubpathsForPattern:routePattern];
139 | if (subpaths.count == 0) {
140 | return @[];
141 | }
142 |
143 | // Next, etract out the required subpaths.
144 | NSSet *requiredSubpaths = [NSSet setWithArray:[subpaths JLRoutes_filter:^BOOL(JLRParsingUtilities_RouteSubpath *subpath) {
145 | return !subpath.isOptionalSubpath;
146 | }]];
147 |
148 | // Then, expand the subpath permutations into possible route patterns.
149 | NSArray *> *allSubpathCombinations = [subpaths JLRoutes_allOrderedCombinations];
150 |
151 | // Finally, we need to filter out any possible route patterns that don't actually satisfy the rules of the route.
152 | // What this means in practice is throwing out any that do not contain all required subpaths (since those are explicitly not optional).
153 | NSArray *> *validSubpathCombinations = [allSubpathCombinations JLRoutes_filter:^BOOL(NSArray *possibleRouteSubpaths) {
154 | return [requiredSubpaths isSubsetOfSet:[NSSet setWithArray:possibleRouteSubpaths]];
155 | }];
156 |
157 | // Once we have a filtered list of valid subpaths, we just need to convert them back into string routes that can we registered.
158 | NSArray *validSubpathRouteStrings = [validSubpathCombinations JLRoutes_map:^id(NSArray *subpaths) {
159 | NSString *routePattern = @"/";
160 | for (JLRParsingUtilities_RouteSubpath *subpath in subpaths) {
161 | NSString *subpathString = [subpath.subpathComponents componentsJoinedByString:@"/"];
162 | routePattern = [routePattern stringByAppendingPathComponent:subpathString];
163 | }
164 | return routePattern;
165 | }];
166 |
167 | // Before returning, sort them by length so that the longest and most specific routes are registered first before the less specific shorter ones.
168 | validSubpathRouteStrings = [validSubpathRouteStrings sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"length" ascending:NO selector:@selector(compare:)]]];
169 |
170 | return validSubpathRouteStrings;
171 | }
172 |
173 | + (NSArray *)_routeSubpathsForPattern:(NSString *)routePattern
174 | {
175 | NSMutableArray *subpaths = [NSMutableArray array];
176 |
177 | NSScanner *scanner = [NSScanner scannerWithString:routePattern];
178 | while (![scanner isAtEnd]) {
179 | NSString *preOptionalSubpath = nil;
180 | BOOL didScan = [scanner scanUpToString:@"(" intoString:&preOptionalSubpath];
181 | if (!didScan) {
182 | NSAssert([routePattern characterAtIndex:scanner.scanLocation] == '(', @"Unexpected character: %c", [routePattern characterAtIndex:scanner.scanLocation]);
183 | }
184 |
185 | if (!scanner.isAtEnd) {
186 | // otherwise, advance past the ( character
187 | scanner.scanLocation = scanner.scanLocation + 1;
188 | }
189 |
190 | if (preOptionalSubpath.length > 0 && ![preOptionalSubpath isEqualToString:@")"] && ![preOptionalSubpath isEqualToString:@"/"]) {
191 | // content before the start of an optional subpath
192 | JLRParsingUtilities_RouteSubpath *subpath = [[JLRParsingUtilities_RouteSubpath alloc] init];
193 | subpath.subpathComponents = [preOptionalSubpath JLRoutes_trimmedPathComponents];
194 | [subpaths addObject:subpath];
195 | }
196 |
197 | if (scanner.isAtEnd) {
198 | break;
199 | }
200 |
201 | NSString *optionalSubpath = nil;
202 | didScan = [scanner scanUpToString:@")" intoString:&optionalSubpath];
203 | NSAssert(didScan, @"Could not find closing parenthesis");
204 |
205 | scanner.scanLocation = scanner.scanLocation + 1;
206 |
207 | if (optionalSubpath.length > 0) {
208 | JLRParsingUtilities_RouteSubpath *subpath = [[JLRParsingUtilities_RouteSubpath alloc] init];
209 | subpath.isOptionalSubpath = YES;
210 | subpath.subpathComponents = [optionalSubpath JLRoutes_trimmedPathComponents];
211 | [subpaths addObject:subpath];
212 | }
213 | }
214 |
215 | return [subpaths copy];
216 | }
217 |
218 | @end
219 |
220 |
221 | #pragma mark - Categories
222 |
223 |
224 | @implementation NSArray (JLRoutes_Utilities)
225 |
226 | - (NSArray *)JLRoutes_allOrderedCombinations
227 | {
228 | NSInteger length = self.count;
229 | if (length == 0) {
230 | return [NSArray arrayWithObject:[NSArray array]];
231 | }
232 |
233 | id lastObject = [self lastObject];
234 | NSArray *subarray = [self subarrayWithRange:NSMakeRange(0, length - 1)];
235 | NSArray *subarrayCombinations = [subarray JLRoutes_allOrderedCombinations];
236 | NSMutableArray *combinations = [NSMutableArray arrayWithArray:subarrayCombinations];
237 |
238 | for (NSArray *subarrayCombos in subarrayCombinations) {
239 | [combinations addObject:[subarrayCombos arrayByAddingObject:lastObject]];
240 | }
241 |
242 | return [NSArray arrayWithArray:combinations];
243 | }
244 |
245 | - (NSArray *)JLRoutes_filter:(BOOL (^)(id object))filterBlock
246 | {
247 | NSParameterAssert(filterBlock != nil);
248 | NSMutableArray *filteredArray = [NSMutableArray array];
249 |
250 | for (id object in self) {
251 | if (filterBlock(object)) {
252 | [filteredArray addObject:object];
253 | }
254 | }
255 |
256 | return [filteredArray copy];
257 | }
258 |
259 | - (NSArray *)JLRoutes_map:(id (^)(id object))mapBlock
260 | {
261 | NSParameterAssert(mapBlock != nil);
262 | NSMutableArray *mappedArray = [NSMutableArray array];
263 |
264 | for (id object in self) {
265 | id mappedObject = mapBlock(object);
266 | [mappedArray addObject:mappedObject];
267 | }
268 |
269 | return [mappedArray copy];
270 | }
271 |
272 | @end
273 |
274 |
275 | @implementation NSString (JLRoutes_Utilities)
276 |
277 | - (NSArray *)JLRoutes_trimmedPathComponents
278 | {
279 | // Trims leading and trailing slashes and then separates by slash
280 | return [[self stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"/"]] componentsSeparatedByString:@"/"];
281 | }
282 |
283 | @end
284 |
285 |
--------------------------------------------------------------------------------
/JLRoutes/JLRRouteDefinition.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import
14 | #import "JLRRouteRequest.h"
15 | #import "JLRRouteResponse.h"
16 |
17 | NS_ASSUME_NONNULL_BEGIN
18 |
19 |
20 | /**
21 | JLRRouteDefinition is a model object representing a registered route, including the URL scheme, route pattern, and priority.
22 |
23 | This class can be subclassed to customize route parsing behavior by overriding -routeResponseForRequest:.
24 | -callHandlerBlockWithParameters can also be overriden to customize the parameters passed to the handlerBlock.
25 | */
26 |
27 | @interface JLRRouteDefinition : NSObject
28 |
29 | /// The URL scheme for which this route applies, or JLRoutesGlobalRoutesScheme if global.
30 | @property (nonatomic, copy, readonly) NSString *scheme;
31 |
32 | /// The route pattern.
33 | @property (nonatomic, copy, readonly) NSString *pattern;
34 |
35 | /// The priority of this route pattern.
36 | @property (nonatomic, assign, readonly) NSUInteger priority;
37 |
38 | /// The route pattern path components.
39 | @property (nonatomic, copy, readonly) NSArray *patternPathComponents;
40 |
41 | /// The handler block to invoke when a match is found.
42 | @property (nonatomic, copy, readonly) BOOL (^handlerBlock)(NSDictionary *parameters);
43 |
44 | /// Check for route definition equality.
45 | - (BOOL)isEqualToRouteDefinition:(JLRRouteDefinition *)routeDefinition;
46 |
47 |
48 | ///----------------------------------
49 | /// @name Creating Route Definitions
50 | ///----------------------------------
51 |
52 |
53 | /**
54 | Creates a new route definition. The created definition can be directly added to an instance of JLRoutes.
55 |
56 | This is the designated initializer.
57 |
58 | @param pattern The full route pattern ('/foo/:bar')
59 | @param priority The route priority, or 0 if default.
60 | @param handlerBlock The handler block to call when a successful match is found.
61 |
62 | @returns The newly initialized route definition.
63 | */
64 | - (instancetype)initWithPattern:(NSString *)pattern priority:(NSUInteger)priority handlerBlock:(BOOL (^)(NSDictionary *parameters))handlerBlock NS_DESIGNATED_INITIALIZER;
65 |
66 | /// Unavailable, use initWithScheme:pattern:priority:handlerBlock: instead.
67 | - (instancetype)init NS_UNAVAILABLE;
68 |
69 | /// Unavailable, use initWithScheme:pattern:priority:handlerBlock: instead.
70 | + (instancetype)new NS_UNAVAILABLE;
71 |
72 |
73 | ///----------------------------------
74 | /// @name Responding To Registration
75 | ///----------------------------------
76 |
77 |
78 | /**
79 | Called when the route has been registered for the given scheme.
80 |
81 | @param scheme The scheme this route has become active for.
82 | */
83 | - (void)didBecomeRegisteredForScheme:(NSString *)scheme;
84 |
85 |
86 | ///-------------------------------
87 | /// @name Matching Route Requests
88 | ///-------------------------------
89 |
90 |
91 | /**
92 | Creates and returns a JLRRouteResponse for the provided JLRRouteRequest. The response specifies if there was a match or not.
93 |
94 | @param request The JLRRouteRequest to create a response for.
95 |
96 | @returns An JLRRouteResponse instance representing the result of attempting to match request to thie route definition.
97 | */
98 | - (JLRRouteResponse *)routeResponseForRequest:(JLRRouteRequest *)request;
99 |
100 |
101 | /**
102 | Invoke handlerBlock with the given parameters. This may be overriden by subclasses.
103 |
104 | @param parameters The parameters to pass to handlerBlock.
105 |
106 | @returns The value returned by calling handlerBlock (YES if it is considered handled and NO if not).
107 | */
108 | - (BOOL)callHandlerBlockWithParameters:(NSDictionary *)parameters;
109 |
110 |
111 | ///---------------------------------
112 | /// @name Creating Match Parameters
113 | ///---------------------------------
114 |
115 |
116 | /**
117 | Creates and returns the full set of match parameters to be passed as part of a valid match.
118 | Subclasses can override this method to mutate the match parameters, or simply call it to generate the expected value.
119 |
120 | @param request The request being routed.
121 | @param routeVariables The parsed route variables (aka a route of '/route/:param' being routed with '/foo/bar' would create [ 'param' : 'bar' ])
122 |
123 | @returns The full set of match parameters to be passed as part of a valid match.
124 | @see defaultMatchParametersForRequest:
125 | @see routeVariablesForRequest:
126 | */
127 | - (NSDictionary *)matchParametersForRequest:(JLRRouteRequest *)request routeVariables:(NSDictionary *)routeVariables;
128 |
129 |
130 | /**
131 | Creates and returns the default base match parameters for a given request. Does not include any parsed fields.
132 |
133 | @param request The request being routed.
134 |
135 | @returns The default match parameters for a given request. Only includes key/value pairs for JLRoutePatternKey, JLRouteURLKey, and JLRouteSchemeKey.
136 | */
137 | - (NSDictionary *)defaultMatchParametersForRequest:(JLRRouteRequest *)request;
138 |
139 |
140 | ///-------------------------------
141 | /// @name Parsing Route Variables
142 | ///-------------------------------
143 |
144 |
145 | /**
146 | Parses and returns route variables for the given request.
147 |
148 | @param request The request to parse variable values from.
149 |
150 | @returns The parsed route variables if there was a match, or nil if it was not a match.
151 | */
152 | - (nullable NSDictionary *)routeVariablesForRequest:(JLRRouteRequest *)request;
153 |
154 |
155 | /**
156 | Parses value into a variable name, including stripping out any extra characters if needed.
157 |
158 | @param value The raw string value that should be parsed into a variable name.
159 |
160 | @returns The variable name to use as the key of a key/value pair in the parsed route variables.
161 | */
162 | - (NSString *)routeVariableNameForValue:(NSString *)value;
163 |
164 |
165 | /**
166 | Parses value into a variable value, including stripping out any extra characters if needed.
167 |
168 | @param value The raw string value that should be parsed into a variable value.
169 |
170 | @returns The variable value to use as the value of a key/value pair in the parsed route variables.
171 | */
172 | - (NSString *)routeVariableValueForValue:(NSString *)value;
173 |
174 |
175 | @end
176 |
177 |
178 | NS_ASSUME_NONNULL_END
179 |
--------------------------------------------------------------------------------
/JLRoutes/JLRRouteDefinition.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import "JLRRouteDefinition.h"
14 | #import "JLRoutes.h"
15 | #import "JLRParsingUtilities.h"
16 |
17 |
18 | @interface JLRRouteDefinition ()
19 |
20 | @property (nonatomic, copy) NSString *pattern;
21 | @property (nonatomic, copy) NSString *scheme;
22 | @property (nonatomic, assign) NSUInteger priority;
23 | @property (nonatomic, copy) NSArray *patternPathComponents;
24 | @property (nonatomic, copy) BOOL (^handlerBlock)(NSDictionary *parameters);
25 |
26 | @end
27 |
28 |
29 | @implementation JLRRouteDefinition
30 |
31 | - (instancetype)initWithPattern:(NSString *)pattern priority:(NSUInteger)priority handlerBlock:(BOOL (^)(NSDictionary *parameters))handlerBlock
32 | {
33 | NSParameterAssert(pattern != nil);
34 |
35 | if ((self = [super init])) {
36 | self.pattern = pattern;
37 | self.priority = priority;
38 | self.handlerBlock = handlerBlock;
39 |
40 | if ([pattern length] > 0 && [pattern characterAtIndex:0] == '/') {
41 | pattern = [pattern substringFromIndex:1];
42 | }
43 |
44 | self.patternPathComponents = [pattern componentsSeparatedByString:@"/"];
45 | }
46 | return self;
47 | }
48 |
49 | - (NSString *)description
50 | {
51 | return [NSString stringWithFormat:@"<%@ %p> - %@ (priority: %@)", NSStringFromClass([self class]), self, self.pattern, @(self.priority)];
52 | }
53 |
54 | - (BOOL)isEqual:(id)object
55 | {
56 | if (object == self) {
57 | return YES;
58 | }
59 |
60 | if ([object isKindOfClass:[JLRRouteDefinition class]]) {
61 | return [self isEqualToRouteDefinition:(JLRRouteDefinition *)object];
62 | } else {
63 | return [super isEqual:object];
64 | }
65 | }
66 |
67 | - (BOOL)isEqualToRouteDefinition:(JLRRouteDefinition *)routeDefinition
68 | {
69 | if (!((self.pattern == nil && routeDefinition.pattern == nil) || [self.pattern isEqualToString:routeDefinition.pattern])) {
70 | return NO;
71 | }
72 |
73 | if (!((self.scheme == nil && routeDefinition.scheme == nil) || [self.scheme isEqualToString:routeDefinition.scheme])) {
74 | return NO;
75 | }
76 |
77 | if (!((self.patternPathComponents == nil && routeDefinition.patternPathComponents == nil) || [self.patternPathComponents isEqualToArray:routeDefinition.patternPathComponents])) {
78 | return NO;
79 | }
80 |
81 | if (self.priority != routeDefinition.priority) {
82 | return NO;
83 | }
84 |
85 | return YES;
86 | }
87 |
88 | - (NSUInteger)hash
89 | {
90 | return self.pattern.hash ^ @(self.priority).hash ^ self.scheme.hash ^ self.patternPathComponents.hash;
91 | }
92 |
93 | #pragma mark - Main API
94 |
95 | - (JLRRouteResponse *)routeResponseForRequest:(JLRRouteRequest *)request
96 | {
97 | BOOL patternContainsWildcard = [self.patternPathComponents containsObject:@"*"];
98 |
99 | if (request.pathComponents.count != self.patternPathComponents.count && !patternContainsWildcard) {
100 | // definitely not a match, nothing left to do
101 | return [JLRRouteResponse invalidMatchResponse];
102 | }
103 |
104 | NSDictionary *routeVariables = [self routeVariablesForRequest:request];
105 |
106 | if (routeVariables != nil) {
107 | // It's a match, set up the param dictionary and create a valid match response
108 | NSDictionary *matchParams = [self matchParametersForRequest:request routeVariables:routeVariables];
109 | return [JLRRouteResponse validMatchResponseWithParameters:matchParams];
110 | } else {
111 | // nil variables indicates no match, so return an invalid match response
112 | return [JLRRouteResponse invalidMatchResponse];
113 | }
114 | }
115 |
116 | - (BOOL)callHandlerBlockWithParameters:(NSDictionary *)parameters
117 | {
118 | if (self.handlerBlock == nil) {
119 | return YES;
120 | }
121 |
122 | return self.handlerBlock(parameters);
123 | }
124 |
125 | - (void)didBecomeRegisteredForScheme:(NSString *)scheme
126 | {
127 | NSAssert(self.scheme == nil, @"Route definitions should not be added to multiple schemes.");
128 | self.scheme = scheme;
129 | }
130 |
131 | #pragma mark - Parsing Route Variables
132 |
133 | - (NSDictionary *)routeVariablesForRequest:(JLRRouteRequest *)request
134 | {
135 | NSMutableDictionary *routeVariables = [NSMutableDictionary dictionary];
136 |
137 | BOOL isMatch = YES;
138 | NSUInteger index = 0;
139 |
140 | for (NSString *patternComponent in self.patternPathComponents) {
141 | NSString *URLComponent = nil;
142 | BOOL isPatternComponentWildcard = [patternComponent isEqualToString:@"*"];
143 |
144 | if (index < [request.pathComponents count]) {
145 | URLComponent = request.pathComponents[index];
146 | } else if (!isPatternComponentWildcard) {
147 | // URLComponent is not a wildcard and index is >= request.pathComponents.count, so bail
148 | isMatch = NO;
149 | break;
150 | }
151 |
152 | if ([patternComponent hasPrefix:@":"]) {
153 | // this is a variable, set it in the params
154 | NSAssert(URLComponent != nil, @"URLComponent cannot be nil");
155 | NSString *variableName = [self routeVariableNameForValue:patternComponent];
156 | NSString *variableValue = [self routeVariableValueForValue:URLComponent];
157 |
158 | // Consult the parsing utilities as well to do any other standard variable transformations
159 | BOOL decodePlusSymbols = ((request.options & JLRRouteRequestOptionDecodePlusSymbols) == JLRRouteRequestOptionDecodePlusSymbols);
160 | variableValue = [JLRParsingUtilities variableValueFrom:variableValue decodePlusSymbols:decodePlusSymbols];
161 |
162 | routeVariables[variableName] = variableValue;
163 | } else if (isPatternComponentWildcard) {
164 | // match wildcards
165 | NSUInteger minRequiredParams = index;
166 | if (request.pathComponents.count >= minRequiredParams) {
167 | // match: /a/b/c/* has to be matched by at least /a/b/c
168 | routeVariables[JLRouteWildcardComponentsKey] = [request.pathComponents subarrayWithRange:NSMakeRange(index, request.pathComponents.count - index)];
169 | isMatch = YES;
170 | } else {
171 | // not a match: /a/b/c/* cannot be matched by URL /a/b/
172 | isMatch = NO;
173 | }
174 | break;
175 | } else if (![patternComponent isEqualToString:URLComponent]) {
176 | // break if this is a static component and it isn't a match
177 | isMatch = NO;
178 | break;
179 | }
180 | index++;
181 | }
182 |
183 | if (!isMatch) {
184 | // Return nil to indicate that there was not a match
185 | routeVariables = nil;
186 | }
187 |
188 | return [routeVariables copy];
189 | }
190 |
191 | - (NSString *)routeVariableNameForValue:(NSString *)value
192 | {
193 | NSString *name = value;
194 |
195 | if (name.length > 1 && [name characterAtIndex:0] == ':') {
196 | // Strip off the ':' in front of param names
197 | name = [name substringFromIndex:1];
198 | }
199 |
200 | if (name.length > 1 && [name characterAtIndex:name.length - 1] == '#') {
201 | // Strip of trailing fragment
202 | name = [name substringToIndex:name.length - 1];
203 | }
204 |
205 | return name;
206 | }
207 |
208 | - (NSString *)routeVariableValueForValue:(NSString *)value
209 | {
210 | // Remove percent encoding
211 | NSString *var = [value stringByRemovingPercentEncoding];
212 |
213 | if (var.length > 1 && [var characterAtIndex:var.length - 1] == '#') {
214 | // Strip of trailing fragment
215 | var = [var substringToIndex:var.length - 1];
216 | }
217 |
218 | return var;
219 | }
220 |
221 | #pragma mark - Creating Match Parameters
222 |
223 | - (NSDictionary *)matchParametersForRequest:(JLRRouteRequest *)request routeVariables:(NSDictionary *)routeVariables
224 | {
225 | NSMutableDictionary *matchParams = [NSMutableDictionary dictionary];
226 |
227 | // Add the parsed query parameters ('?a=b&c=d'). Also includes fragment.
228 | BOOL decodePlusSymbols = ((request.options & JLRRouteRequestOptionDecodePlusSymbols) == JLRRouteRequestOptionDecodePlusSymbols);
229 | [matchParams addEntriesFromDictionary:[JLRParsingUtilities queryParams:request.queryParams decodePlusSymbols:decodePlusSymbols]];
230 |
231 | // Add the actual parsed route variables (the items in the route prefixed with ':').
232 | [matchParams addEntriesFromDictionary:routeVariables];
233 |
234 | // Add the additional parameters, if any were specified in the request.
235 | if (request.additionalParameters != nil) {
236 | [matchParams addEntriesFromDictionary:request.additionalParameters];
237 | }
238 |
239 | // Finally, add the base parameters. This is done last so that these cannot be overriden by using the same key in your route or query.
240 | [matchParams addEntriesFromDictionary:[self defaultMatchParametersForRequest:request]];
241 |
242 | return [matchParams copy];
243 | }
244 |
245 | - (NSDictionary *)defaultMatchParametersForRequest:(JLRRouteRequest *)request
246 | {
247 | return @{JLRoutePatternKey: self.pattern ?: [NSNull null], JLRouteURLKey: request.URL ?: [NSNull null], JLRouteSchemeKey: self.scheme ?: [NSNull null]};
248 | }
249 |
250 | #pragma mark - NSCopying
251 |
252 | - (id)copyWithZone:(NSZone *)zone
253 | {
254 | JLRRouteDefinition *copy = [[[self class] alloc] initWithPattern:self.pattern priority:self.priority handlerBlock:self.handlerBlock];
255 | copy.scheme = self.scheme;
256 | return copy;
257 | }
258 |
259 | @end
260 |
--------------------------------------------------------------------------------
/JLRoutes/JLRRouteHandler.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 |
18 | @protocol JLRRouteHandlerTarget;
19 |
20 |
21 | /**
22 | JLRRouteHandler is a helper class for creating handler blocks intended to be passed to an addRoute: call.
23 |
24 | This is specifically useful for cases in which you want a separate object or class to be the handler
25 | for a deeplink route. An example might be a view controller class that you want to instantiate and present
26 | in response to a deeplink route.
27 | */
28 |
29 | @interface JLRRouteHandler : NSObject
30 |
31 | /// Unavailable.
32 | - (instancetype)init NS_UNAVAILABLE;
33 |
34 | /// Unavailable.
35 | + (instancetype)new NS_UNAVAILABLE;
36 |
37 |
38 | /**
39 | Creates and returns a block that calls handleRouteWithParameters: on a weak target objet.
40 |
41 | The block returned from this method should be passed as the handler block of an addRoute: call.
42 |
43 | @param weakTarget The target object that should handle a matched route.
44 |
45 | @returns A new handler block for the provided weakTarget.
46 |
47 | @discussion There is no change of ownership of the target object, only a weak pointer (hence 'weakTarget') is captured in the block.
48 | If the object is deallocated, the handler will no longer be called (but the route will remain registered unless explicitly removed).
49 | */
50 |
51 | + (BOOL (^__nonnull)(NSDictionary *parameters))handlerBlockForWeakTarget:(__weak id )weakTarget;
52 |
53 |
54 | /**
55 | Creates and returns a block that creates a new instance of targetClass (which must conform to JLRRouteHandlerTarget), and then calls
56 | handleRouteWithParameters: on it. The created object is then passed as the parameter to the completion block.
57 |
58 | The block returned from this method should be passed as the handler block of an addRoute: call.
59 |
60 | @param targetClass The target class to create for handling the route request. Must conform to JLRRouteHandlerTarget.
61 | @param completionHandler The completion block to call after creating the new targetClass instance.
62 |
63 | @returns A new handler block for creating instances of targetClass.
64 |
65 | @discussion JLRoutes does not retain or own the created object. It's expected that the created object that is passed through the completion handler
66 | will be used and owned by the calling application.
67 | */
68 |
69 | + (BOOL (^__nonnull)(NSDictionary *parameters))handlerBlockForTargetClass:(Class)targetClass completion:(BOOL (^)(id createdObject))completionHandler;
70 |
71 | @end
72 |
73 |
74 | /**
75 | Classes conforming to the JLRRouteHandlerTarget protocol can be used as a route handler target.
76 | */
77 |
78 | @protocol JLRRouteHandlerTarget
79 |
80 | @optional
81 |
82 | /**
83 | Initialize an instance of the conforming class by passing matched route parameters from a JLRoutes route.
84 |
85 | @param parameters The match parameters passed to use when initializing the object. These are passed from a JLRoutes handler block.
86 |
87 | @returns An initialized instance of the conforming class.
88 | */
89 |
90 | - (instancetype)initWithRouteParameters:(NSDictionary *)parameters;
91 |
92 |
93 | /**
94 | Called for a successful route match.
95 |
96 | @param parameters The match parameters passed to the handler block.
97 |
98 | @returns YES if the route was handled, NO if matching a different route should be attempted.
99 | */
100 |
101 | - (BOOL)handleRouteWithParameters:(NSDictionary *)parameters;
102 |
103 | @end
104 |
105 |
106 | NS_ASSUME_NONNULL_END
107 |
--------------------------------------------------------------------------------
/JLRoutes/JLRRouteHandler.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import "JLRRouteHandler.h"
14 |
15 |
16 | @implementation JLRRouteHandler
17 |
18 | + (BOOL (^)(NSDictionary *parameters))handlerBlockForWeakTarget:(__weak id )weakTarget
19 | {
20 | NSParameterAssert([weakTarget respondsToSelector:@selector(handleRouteWithParameters:)]);
21 |
22 | return ^BOOL(NSDictionary *parameters) {
23 | return [weakTarget handleRouteWithParameters:parameters];
24 | };
25 | }
26 |
27 | + (BOOL (^)(NSDictionary *parameters))handlerBlockForTargetClass:(Class)targetClass completion:(BOOL (^)(id createdObject))completionHandler
28 | {
29 | NSParameterAssert([targetClass conformsToProtocol:@protocol(JLRRouteHandlerTarget)]);
30 | NSParameterAssert([targetClass instancesRespondToSelector:@selector(initWithRouteParameters:)]);
31 | NSParameterAssert(completionHandler != nil); // we want to force external ownership of the newly created object by handing it back.
32 |
33 | return ^BOOL(NSDictionary *parameters) {
34 | id createdObject = [[targetClass alloc] initWithRouteParameters:parameters];
35 | return completionHandler(createdObject);
36 | };
37 | }
38 |
39 | @end
40 |
--------------------------------------------------------------------------------
/JLRoutes/JLRRouteRequest.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 |
18 | /// Options bitmask generated from JLRoutes global options methods.
19 | typedef NS_OPTIONS(NSUInteger, JLRRouteRequestOptions) {
20 | /// No options specified.
21 | JLRRouteRequestOptionsNone = 0,
22 |
23 | /// If present, decoding plus symbols is enabled.
24 | JLRRouteRequestOptionDecodePlusSymbols = 1 << 0,
25 |
26 | /// If present, treating URL hosts as path components is enabled.
27 | JLRRouteRequestOptionTreatHostAsPathComponent = 1 << 1
28 | };
29 |
30 |
31 | /**
32 | JLRRouteRequest is a model representing a request to route a URL.
33 | It gets parsed into path components and query parameters, which are then used by JLRRouteDefinition to attempt a match.
34 | */
35 |
36 | @interface JLRRouteRequest : NSObject
37 |
38 | /// The URL being routed.
39 | @property (nonatomic, copy, readonly) NSURL *URL;
40 |
41 | /// The URL's path components.
42 | @property (nonatomic, strong, readonly) NSArray *pathComponents;
43 |
44 | /// The URL's query parameters.
45 | @property (nonatomic, strong, readonly) NSDictionary *queryParams;
46 |
47 | /// Route request options, generally configured from the framework global options.
48 | @property (nonatomic, assign, readonly) JLRRouteRequestOptions options;
49 |
50 | /// Additional parameters to pass through as part of the match parameters dictionary.
51 | @property (nonatomic, copy, nullable, readonly) NSDictionary *additionalParameters;
52 |
53 |
54 | ///-------------------------------
55 | /// @name Creating Route Requests
56 | ///-------------------------------
57 |
58 |
59 | /**
60 | Creates a new route request.
61 |
62 | @param URL The URL to route.
63 | @param options Options bitmask specifying parsing behavior.
64 | @param additionalParameters Additional parameters to include in any match dictionary created against this request.
65 |
66 | @returns The newly initialized route request.
67 | */
68 | - (instancetype)initWithURL:(NSURL *)URL options:(JLRRouteRequestOptions)options additionalParameters:(nullable NSDictionary *)additionalParameters NS_DESIGNATED_INITIALIZER;
69 |
70 | /// Unavailable, use initWithURL:options:additionalParameters: instead.
71 | - (instancetype)init NS_UNAVAILABLE;
72 |
73 | /// Unavailable, use initWithURL:options:additionalParameters: instead.
74 | + (instancetype)new NS_UNAVAILABLE;
75 |
76 | @end
77 |
78 |
79 | NS_ASSUME_NONNULL_END
80 |
--------------------------------------------------------------------------------
/JLRoutes/JLRRouteRequest.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import "JLRRouteRequest.h"
14 |
15 |
16 | @interface JLRRouteRequest ()
17 |
18 | @property (nonatomic, copy) NSURL *URL;
19 | @property (nonatomic, strong) NSArray *pathComponents;
20 | @property (nonatomic, strong) NSDictionary *queryParams;
21 | @property (nonatomic, assign) JLRRouteRequestOptions options;
22 | @property (nonatomic, copy) NSDictionary *additionalParameters;
23 |
24 | @end
25 |
26 |
27 | @implementation JLRRouteRequest
28 |
29 | - (instancetype)initWithURL:(NSURL *)URL options:(JLRRouteRequestOptions)options additionalParameters:(nullable NSDictionary *)additionalParameters
30 | {
31 | if ((self = [super init])) {
32 | self.URL = URL;
33 | self.options = options;
34 | self.additionalParameters = additionalParameters;
35 |
36 | BOOL treatsHostAsPathComponent = ((options & JLRRouteRequestOptionTreatHostAsPathComponent) == JLRRouteRequestOptionTreatHostAsPathComponent);
37 |
38 | NSURLComponents *components = [NSURLComponents componentsWithString:[self.URL absoluteString]];
39 |
40 | if (components.host.length > 0 && (treatsHostAsPathComponent || (![components.host isEqualToString:@"localhost"] && [components.host rangeOfString:@"."].location == NSNotFound))) {
41 | // convert the host to "/" so that the host is considered a path component
42 | NSString *host = [components.percentEncodedHost copy];
43 | components.host = @"/";
44 | components.percentEncodedPath = [host stringByAppendingPathComponent:(components.percentEncodedPath ?: @"")];
45 | }
46 |
47 | NSString *path = [components percentEncodedPath];
48 |
49 | // handle fragment if needed
50 | if (components.fragment != nil) {
51 | BOOL fragmentContainsQueryParams = NO;
52 | NSURLComponents *fragmentComponents = [NSURLComponents componentsWithString:components.percentEncodedFragment];
53 |
54 | if (fragmentComponents.query == nil && fragmentComponents.path != nil) {
55 | fragmentComponents.query = fragmentComponents.path;
56 | }
57 |
58 | if (fragmentComponents.queryItems.count > 0) {
59 | // determine if this fragment is only valid query params and nothing else
60 | fragmentContainsQueryParams = fragmentComponents.queryItems.firstObject.value.length > 0;
61 | }
62 |
63 | if (fragmentContainsQueryParams) {
64 | // include fragment query params in with the standard set
65 | components.queryItems = [(components.queryItems ?: @[]) arrayByAddingObjectsFromArray:fragmentComponents.queryItems];
66 | }
67 |
68 | if (fragmentComponents.path != nil && (!fragmentContainsQueryParams || ![fragmentComponents.path isEqualToString:fragmentComponents.query])) {
69 | // handle fragment by include fragment path as part of the main path
70 | path = [path stringByAppendingString:[NSString stringWithFormat:@"#%@", fragmentComponents.percentEncodedPath]];
71 | }
72 | }
73 |
74 | // strip off leading slash so that we don't have an empty first path component
75 | if (path.length > 0 && [path characterAtIndex:0] == '/') {
76 | path = [path substringFromIndex:1];
77 | }
78 |
79 | // strip off trailing slash for the same reason
80 | if (path.length > 0 && [path characterAtIndex:path.length - 1] == '/') {
81 | path = [path substringToIndex:path.length - 1];
82 | }
83 |
84 | // split apart into path components
85 | self.pathComponents = [path componentsSeparatedByString:@"/"];
86 |
87 | // convert query items into a dictionary
88 | NSArray *queryItems = [components queryItems] ?: @[];
89 | NSMutableDictionary *queryParams = [NSMutableDictionary dictionary];
90 | for (NSURLQueryItem *item in queryItems) {
91 | if (item.value == nil) {
92 | continue;
93 | }
94 |
95 | if (queryParams[item.name] == nil) {
96 | // first time seeing a param with this name, set it
97 | queryParams[item.name] = item.value;
98 | } else if ([queryParams[item.name] isKindOfClass:[NSArray class]]) {
99 | // already an array of these items, append it
100 | NSArray *values = (NSArray *)(queryParams[item.name]);
101 | queryParams[item.name] = [values arrayByAddingObject:item.value];
102 | } else {
103 | // existing non-array value for this key, create an array
104 | id existingValue = queryParams[item.name];
105 | queryParams[item.name] = @[existingValue, item.value];
106 | }
107 | }
108 |
109 | self.queryParams = [queryParams copy];
110 | }
111 | return self;
112 | }
113 |
114 | - (NSString *)description
115 | {
116 | return [NSString stringWithFormat:@"<%@ %p> - URL: %@", NSStringFromClass([self class]), self, [self.URL absoluteString]];
117 | }
118 |
119 | @end
120 |
--------------------------------------------------------------------------------
/JLRoutes/JLRRouteResponse.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 |
18 | /**
19 | JLRRouteResponse is the response from attempting to route a JLRRouteRequest.
20 | */
21 |
22 | @interface JLRRouteResponse : NSObject
23 |
24 | /// Indicates if the response is a match or not.
25 | @property (nonatomic, assign, readonly, getter=isMatch) BOOL match;
26 |
27 | /// The match parameters (or nil for an invalid response).
28 | @property (nonatomic, copy, readonly, nullable) NSDictionary *parameters;
29 |
30 | /// Check for route response equality
31 | - (BOOL)isEqualToRouteResponse:(JLRRouteResponse *)response;
32 |
33 |
34 | ///-------------------------------
35 | /// @name Creating Responses
36 | ///-------------------------------
37 |
38 |
39 | /// Creates an invalid match response.
40 | + (instancetype)invalidMatchResponse;
41 |
42 | /// Creates a valid match response with the given parameters.
43 | + (instancetype)validMatchResponseWithParameters:(NSDictionary *)parameters;
44 |
45 | /// Unavailable, please use +invalidMatchResponse or +validMatchResponseWithParameters: instead.
46 | - (instancetype)init NS_UNAVAILABLE;
47 |
48 | /// Unavailable, please use +invalidMatchResponse or +validMatchResponseWithParameters: instead.
49 | + (instancetype)new NS_UNAVAILABLE;
50 |
51 | @end
52 |
53 |
54 | NS_ASSUME_NONNULL_END
55 |
--------------------------------------------------------------------------------
/JLRoutes/JLRRouteResponse.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import "JLRRouteResponse.h"
14 |
15 |
16 | @interface JLRRouteResponse ()
17 |
18 | @property (nonatomic, assign, getter=isMatch) BOOL match;
19 | @property (nonatomic, copy) NSDictionary *parameters;
20 |
21 | @end
22 |
23 |
24 | @implementation JLRRouteResponse
25 |
26 | + (instancetype)invalidMatchResponse
27 | {
28 | JLRRouteResponse *response = [[[self class] alloc] init];
29 | response.match = NO;
30 | return response;
31 | }
32 |
33 | + (instancetype)validMatchResponseWithParameters:(NSDictionary *)parameters
34 | {
35 | JLRRouteResponse *response = [[[self class] alloc] init];
36 | response.match = YES;
37 | response.parameters = parameters;
38 | return response;
39 | }
40 |
41 | - (NSString *)description
42 | {
43 | return [NSString stringWithFormat:@"<%@ %p> - match: %@, params: %@", NSStringFromClass([self class]), self, (self.match ? @"YES" : @"NO"), self.parameters];
44 | }
45 |
46 | - (BOOL)isEqual:(id)object
47 | {
48 | if (object == self) {
49 | return YES;
50 | }
51 |
52 | if ([object isKindOfClass:[self class]]) {
53 | return [self isEqualToRouteResponse:(JLRRouteResponse *)object];
54 | } else {
55 | return [super isEqual:object];
56 | }
57 | }
58 |
59 | - (BOOL)isEqualToRouteResponse:(JLRRouteResponse *)response
60 | {
61 | if (self.isMatch != response.isMatch) {
62 | return NO;
63 | }
64 |
65 | if (!((self.parameters == nil && response.parameters == nil) || [self.parameters isEqualToDictionary:response.parameters])) {
66 | return NO;
67 | }
68 |
69 | return YES;
70 | }
71 |
72 | - (NSUInteger)hash
73 | {
74 | return @(self.match).hash ^ self.parameters.hash;
75 | }
76 |
77 | - (id)copyWithZone:(NSZone *)zone
78 | {
79 | JLRRouteResponse *copy = [[[self class] alloc] init];
80 | copy.match = self.isMatch;
81 | copy.parameters = self.parameters;
82 | return copy;
83 | }
84 |
85 | @end
86 |
--------------------------------------------------------------------------------
/JLRoutes/JLRoutes.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import
14 |
15 | #import "JLRRouteDefinition.h"
16 | #import "JLRRouteHandler.h"
17 | #import "JLRRouteRequest.h"
18 | #import "JLRRouteResponse.h"
19 | #import "JLRParsingUtilities.h"
20 |
21 | NS_ASSUME_NONNULL_BEGIN
22 |
23 | /// The matching route pattern, passed in the handler parameters.
24 | extern NSString *const JLRoutePatternKey;
25 |
26 | /// The original URL that was routed, passed in the handler parameters.
27 | extern NSString *const JLRouteURLKey;
28 |
29 | /// The matching route scheme, passed in the handler parameters.
30 | extern NSString *const JLRouteSchemeKey;
31 |
32 | /// The wildcard components (if present) of the matching route, passed in the handler parameters.
33 | extern NSString *const JLRouteWildcardComponentsKey;
34 |
35 | /// The global routes namespace.
36 | /// @see JLRoutes +globalRoutes
37 | extern NSString *const JLRoutesGlobalRoutesScheme;
38 |
39 |
40 |
41 | /**
42 | The JLRoutes class is the main entry-point into the JLRoutes framework. Used for accessing schemes, managing routes, and routing URLs.
43 | */
44 |
45 | @interface JLRoutes : NSObject
46 |
47 | /// Controls whether or not this router will try to match a URL with global routes if it can't be matched in the current namespace. Default is NO.
48 | @property (nonatomic, assign) BOOL shouldFallbackToGlobalRoutes;
49 |
50 | /// Called any time routeURL returns NO. Respects shouldFallbackToGlobalRoutes.
51 | @property (nonatomic, copy, nullable) void (^unmatchedURLHandler)(JLRoutes *routes, NSURL *__nullable URL, NSDictionary *__nullable parameters);
52 |
53 |
54 | ///-------------------------------
55 | /// @name Routing Schemes
56 | ///-------------------------------
57 |
58 |
59 | /// Returns the global routing scheme
60 | + (instancetype)globalRoutes;
61 |
62 | /// Returns a routing namespace for the given scheme
63 | + (instancetype)routesForScheme:(NSString *)scheme;
64 |
65 | /// Unregister and delete an entire scheme namespace
66 | + (void)unregisterRouteScheme:(NSString *)scheme;
67 |
68 | /// Unregister all routes
69 | + (void)unregisterAllRouteSchemes;
70 |
71 |
72 | ///-------------------------------
73 | /// @name Managing Routes
74 | ///-------------------------------
75 |
76 |
77 | /// Add a route by directly inserted the route definition. This may be a subclass of JLRRouteDefinition to provide customized routing logic.
78 | - (void)addRoute:(JLRRouteDefinition *)routeDefinition;
79 |
80 | /// Registers a routePattern with default priority (0) in the receiving scheme.
81 | - (void)addRoute:(NSString *)routePattern handler:(BOOL (^__nullable)(NSDictionary *parameters))handlerBlock;
82 |
83 | /// Registers a routePattern in the global scheme namespace with a handlerBlock to call when the route pattern is matched by a URL.
84 | /// The block returns a BOOL representing if the handlerBlock actually handled the route or not. If
85 | /// a block returns NO, JLRoutes will continue trying to find a matching route.
86 | - (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^__nullable)(NSDictionary *parameters))handlerBlock;
87 |
88 | /// Registers multiple routePatterns for one handler with default priority (0) in the receiving scheme.
89 | - (void)addRoutes:(NSArray *)routePatterns handler:(BOOL (^__nullable)(NSDictionary *parameters))handlerBlock;
90 |
91 | /// Removes the route from the receiving scheme.
92 | - (void)removeRoute:(JLRRouteDefinition *)routeDefinition;
93 |
94 | /// Removes the first route matching routePattern from the receiving scheme.
95 | - (void)removeRouteWithPattern:(NSString *)routePattern;
96 |
97 | /// Removes all routes from the receiving scheme.
98 | - (void)removeAllRoutes;
99 |
100 | /// Registers a routePattern with default priority (0) using dictionary-style subscripting.
101 | - (void)setObject:(nullable id)handlerBlock forKeyedSubscript:(NSString *)routePatten;
102 |
103 | /// Return all registered routes in the receiving scheme.
104 | /// @see allRoutes
105 | - (NSArray *)routes;
106 |
107 | /// Return all registered routes across all schemes, keyed by scheme
108 | /// @see routes
109 | + (NSDictionary *> *)allRoutes;
110 |
111 |
112 | ///-------------------------------
113 | /// @name Routing URLs
114 | ///-------------------------------
115 |
116 |
117 | /// Returns YES if the provided URL will successfully match against any registered route, NO if not.
118 | + (BOOL)canRouteURL:(nullable NSURL *)URL;
119 |
120 | /// Returns YES if the provided URL will successfully match against any registered route for the current scheme, NO if not.
121 | - (BOOL)canRouteURL:(nullable NSURL *)URL;
122 |
123 | /// Routes a URL, calling handler blocks for patterns that match the URL until one returns YES.
124 | /// If no matching route is found, the unmatchedURLHandler will be called (if set).
125 | + (BOOL)routeURL:(nullable NSURL *)URL;
126 |
127 | /// Routes a URL within a particular scheme, calling handler blocks for patterns that match the URL until one returns YES.
128 | /// If no matching route is found, the unmatchedURLHandler will be called (if set).
129 | - (BOOL)routeURL:(nullable NSURL *)URL;
130 |
131 | /// Routes a URL in any routes scheme, calling handler blocks (for patterns that match URL) until one returns YES.
132 | /// Additional parameters get passed through to the matched route block.
133 | + (BOOL)routeURL:(nullable NSURL *)URL withParameters:(nullable NSDictionary *)parameters;
134 |
135 | /// Routes a URL in a specific scheme, calling handler blocks (for patterns that match URL) until one returns YES.
136 | /// Additional parameters get passed through to the matched route block.
137 | - (BOOL)routeURL:(nullable NSURL *)URL withParameters:(nullable NSDictionary *)parameters;
138 |
139 | @end
140 |
141 |
142 | // Global settings to use for parsing and routing.
143 |
144 | @interface JLRoutes (GlobalOptions)
145 |
146 | ///----------------------------------
147 | /// @name Configuring Global Options
148 | ///----------------------------------
149 |
150 | /// Configures verbose logging. Defaults to NO.
151 | + (void)setVerboseLoggingEnabled:(BOOL)loggingEnabled;
152 |
153 | /// Returns current verbose logging enabled state. Defaults to NO.
154 | + (BOOL)isVerboseLoggingEnabled;
155 |
156 | /// Configures if '+' should be replaced with spaces in parsed values. Defaults to YES.
157 | + (void)setShouldDecodePlusSymbols:(BOOL)shouldDecode;
158 |
159 | /// Returns if '+' should be replaced with spaces in parsed values. Defaults to YES.
160 | + (BOOL)shouldDecodePlusSymbols;
161 |
162 | /// Configures if URL host is always considered to be a path component. Defaults to NO.
163 | + (void)setAlwaysTreatsHostAsPathComponent:(BOOL)treatsHostAsPathComponent;
164 |
165 | /// Returns if URL host is always considered to be a path component. Defaults to NO.
166 | + (BOOL)alwaysTreatsHostAsPathComponent;
167 |
168 | /// Configures the default class to use when creating route definitions. Defaults to JLRRouteDefinition.
169 | + (void)setDefaultRouteDefinitionClass:(Class)routeDefinitionClass;
170 |
171 | /// Returns the default class to use when creating route definitions. Defaults to JLRRouteDefinition.
172 | + (Class)defaultRouteDefinitionClass;
173 |
174 | @end
175 |
176 |
177 |
178 | #pragma mark - Deprecated
179 |
180 | extern NSString *const kJLRoutePatternKey DEPRECATED_MSG_ATTRIBUTE("Use JLRoutePatternKey instead.");
181 | extern NSString *const kJLRouteURLKey DEPRECATED_MSG_ATTRIBUTE("Use JLRouteURLKey instead.");
182 | extern NSString *const kJLRouteSchemeKey DEPRECATED_MSG_ATTRIBUTE("Use JLRouteSchemeKey instead.");
183 | extern NSString *const kJLRouteWildcardComponentsKey DEPRECATED_MSG_ATTRIBUTE("Use JLRouteWildcardComponentsKey instead.");
184 | extern NSString *const kJLRoutesGlobalRoutesScheme DEPRECATED_MSG_ATTRIBUTE("Use JLRoutesGlobalRoutesScheme instead.");
185 | extern NSString *const kJLRouteNamespaceKey DEPRECATED_MSG_ATTRIBUTE("Use JLRouteSchemeKey instead.");
186 | extern NSString *const kJLRoutesGlobalNamespaceKey DEPRECATED_MSG_ATTRIBUTE("Use JLRoutesGlobalRoutesScheme instead.");
187 |
188 |
189 | @interface JLRoutes (Deprecated)
190 |
191 | ///----------------------------------
192 | /// @name Deprecated Methods
193 | ///----------------------------------
194 |
195 | /// Use the matching instance method on +globalRoutes instead.
196 | + (void)addRoute:(NSString *)routePattern handler:(BOOL (^__nullable)(NSDictionary *parameters))handlerBlock DEPRECATED_MSG_ATTRIBUTE("Use the matching instance method on +globalRoutes instead.");
197 |
198 | /// Use the matching instance method on +globalRoutes instead.
199 | + (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^__nullable)(NSDictionary *parameters))handlerBlock DEPRECATED_MSG_ATTRIBUTE("Use the matching instance method on +globalRoutes instead.");
200 |
201 | /// Use the matching instance method on +globalRoutes instead.
202 | + (void)addRoutes:(NSArray *)routePatterns handler:(BOOL (^__nullable)(NSDictionary *parameters))handlerBlock DEPRECATED_MSG_ATTRIBUTE("Use the matching instance method on +globalRoutes instead.");
203 |
204 | /// Use the matching instance method on +globalRoutes instead.
205 | + (void)removeRoute:(NSString *)routePattern DEPRECATED_MSG_ATTRIBUTE("Use the matching instance method on +globalRoutes instead.");
206 |
207 | /// Use the matching instance method on +globalRoutes instead.
208 | + (void)removeAllRoutes DEPRECATED_MSG_ATTRIBUTE("Use the matching instance method on +globalRoutes instead.");
209 |
210 | /// Use +canRouteURL: instead.
211 | + (BOOL)canRouteURL:(nullable NSURL *)URL withParameters:(nullable NSDictionary *)parameters DEPRECATED_MSG_ATTRIBUTE("Use +canRouteURL: instead.");
212 |
213 | /// Use +canRouteURL: instead.
214 | - (BOOL)canRouteURL:(nullable NSURL *)URL withParameters:(nullable NSDictionary *)parameters DEPRECATED_MSG_ATTRIBUTE("Use -canRouteURL: instead.");
215 |
216 | @end
217 |
218 |
219 | NS_ASSUME_NONNULL_END
220 |
--------------------------------------------------------------------------------
/JLRoutes/JLRoutes.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import "JLRoutes.h"
14 | #import "JLRRouteDefinition.h"
15 | #import "JLRParsingUtilities.h"
16 |
17 |
18 | NSString *const JLRoutePatternKey = @"JLRoutePattern";
19 | NSString *const JLRouteURLKey = @"JLRouteURL";
20 | NSString *const JLRouteSchemeKey = @"JLRouteScheme";
21 | NSString *const JLRouteWildcardComponentsKey = @"JLRouteWildcardComponents";
22 | NSString *const JLRoutesGlobalRoutesScheme = @"JLRoutesGlobalRoutesScheme";
23 |
24 |
25 | static NSMutableDictionary *JLRGlobal_routeControllersMap = nil;
26 |
27 |
28 | // global options (configured in +initialize)
29 | static BOOL JLRGlobal_verboseLoggingEnabled;
30 | static BOOL JLRGlobal_shouldDecodePlusSymbols;
31 | static BOOL JLRGlobal_alwaysTreatsHostAsPathComponent;
32 | static Class JLRGlobal_routeDefinitionClass;
33 |
34 |
35 | @interface JLRoutes ()
36 |
37 | @property (nonatomic, strong) NSMutableArray *mutableRoutes;
38 | @property (nonatomic, strong) NSString *scheme;
39 |
40 | - (JLRRouteRequestOptions)_routeRequestOptions;
41 |
42 | @end
43 |
44 |
45 | #pragma mark -
46 |
47 | @implementation JLRoutes
48 |
49 | + (void)initialize
50 | {
51 | if (self == [JLRoutes class]) {
52 | // Set default global options
53 | JLRGlobal_verboseLoggingEnabled = NO;
54 | JLRGlobal_shouldDecodePlusSymbols = YES;
55 | JLRGlobal_alwaysTreatsHostAsPathComponent = NO;
56 | JLRGlobal_routeDefinitionClass = [JLRRouteDefinition class];
57 | }
58 | }
59 |
60 | - (instancetype)init
61 | {
62 | if ((self = [super init])) {
63 | self.mutableRoutes = [NSMutableArray array];
64 | }
65 | return self;
66 | }
67 |
68 | - (NSString *)description
69 | {
70 | return [self.mutableRoutes description];
71 | }
72 |
73 | + (NSDictionary *> *)allRoutes;
74 | {
75 | NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
76 |
77 | for (NSString *namespace in [JLRGlobal_routeControllersMap copy]) {
78 | JLRoutes *routesController = JLRGlobal_routeControllersMap[namespace];
79 | dictionary[namespace] = [routesController.mutableRoutes copy];
80 | }
81 |
82 | return [dictionary copy];
83 | }
84 |
85 |
86 | #pragma mark - Routing Schemes
87 |
88 | + (instancetype)globalRoutes
89 | {
90 | return [self routesForScheme:JLRoutesGlobalRoutesScheme];
91 | }
92 |
93 | + (instancetype)routesForScheme:(NSString *)scheme
94 | {
95 | JLRoutes *routesController = nil;
96 |
97 | static dispatch_once_t onceToken;
98 | dispatch_once(&onceToken, ^{
99 | JLRGlobal_routeControllersMap = [[NSMutableDictionary alloc] init];
100 | });
101 |
102 | if (!JLRGlobal_routeControllersMap[scheme]) {
103 | routesController = [[self alloc] init];
104 | routesController.scheme = scheme;
105 | JLRGlobal_routeControllersMap[scheme] = routesController;
106 | }
107 |
108 | routesController = JLRGlobal_routeControllersMap[scheme];
109 |
110 | return routesController;
111 | }
112 |
113 | + (void)unregisterRouteScheme:(NSString *)scheme
114 | {
115 | [JLRGlobal_routeControllersMap removeObjectForKey:scheme];
116 | }
117 |
118 | + (void)unregisterAllRouteSchemes
119 | {
120 | [JLRGlobal_routeControllersMap removeAllObjects];
121 | }
122 |
123 |
124 | #pragma mark - Registering Routes
125 |
126 | - (void)addRoute:(JLRRouteDefinition *)routeDefinition
127 | {
128 | [self _registerRoute:routeDefinition];
129 | }
130 |
131 | - (void)addRoute:(NSString *)routePattern handler:(BOOL (^)(NSDictionary *parameters))handlerBlock
132 | {
133 | [self addRoute:routePattern priority:0 handler:handlerBlock];
134 | }
135 |
136 | - (void)addRoutes:(NSArray *)routePatterns handler:(BOOL (^)(NSDictionary *parameters))handlerBlock
137 | {
138 | for (NSString *routePattern in routePatterns) {
139 | [self addRoute:routePattern handler:handlerBlock];
140 | }
141 | }
142 |
143 | - (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary *parameters))handlerBlock
144 | {
145 | NSArray *optionalRoutePatterns = [JLRParsingUtilities expandOptionalRoutePatternsForPattern:routePattern];
146 | JLRRouteDefinition *route = [[JLRGlobal_routeDefinitionClass alloc] initWithPattern:routePattern priority:priority handlerBlock:handlerBlock];
147 |
148 | if (optionalRoutePatterns.count > 0) {
149 | // there are optional params, parse and add them
150 | for (NSString *pattern in optionalRoutePatterns) {
151 | JLRRouteDefinition *optionalRoute = [[JLRGlobal_routeDefinitionClass alloc] initWithPattern:pattern priority:priority handlerBlock:handlerBlock];
152 | [self _registerRoute:optionalRoute];
153 | [self _verboseLog:@"Automatically created optional route: %@", optionalRoute];
154 | }
155 | return;
156 | }
157 |
158 | [self _registerRoute:route];
159 | }
160 |
161 | - (void)removeRoute:(JLRRouteDefinition *)routeDefinition
162 | {
163 | [self.mutableRoutes removeObject:routeDefinition];
164 | }
165 |
166 | - (void)removeRouteWithPattern:(NSString *)routePattern
167 | {
168 | NSInteger routeIndex = NSNotFound;
169 | NSInteger index = 0;
170 |
171 | for (JLRRouteDefinition *route in [self.mutableRoutes copy]) {
172 | if ([route.pattern isEqualToString:routePattern]) {
173 | routeIndex = index;
174 | break;
175 | }
176 | index++;
177 | }
178 |
179 | if (routeIndex != NSNotFound) {
180 | [self.mutableRoutes removeObjectAtIndex:(NSUInteger)routeIndex];
181 | }
182 | }
183 |
184 | - (void)removeAllRoutes
185 | {
186 | [self.mutableRoutes removeAllObjects];
187 | }
188 |
189 | - (void)setObject:(id)handlerBlock forKeyedSubscript:(NSString *)routePatten
190 | {
191 | [self addRoute:routePatten handler:handlerBlock];
192 | }
193 |
194 | - (NSArray *)routes;
195 | {
196 | return [self.mutableRoutes copy];
197 | }
198 |
199 | #pragma mark - Routing URLs
200 |
201 | + (BOOL)canRouteURL:(NSURL *)URL
202 | {
203 | return [[self _routesControllerForURL:URL] canRouteURL:URL];
204 | }
205 |
206 | - (BOOL)canRouteURL:(NSURL *)URL
207 | {
208 | return [self _routeURL:URL withParameters:nil executeRouteBlock:NO];
209 | }
210 |
211 | + (BOOL)routeURL:(NSURL *)URL
212 | {
213 | return [[self _routesControllerForURL:URL] routeURL:URL];
214 | }
215 |
216 | - (BOOL)routeURL:(NSURL *)URL
217 | {
218 | return [self _routeURL:URL withParameters:nil executeRouteBlock:YES];
219 | }
220 |
221 | + (BOOL)routeURL:(NSURL *)URL withParameters:(NSDictionary *)parameters
222 | {
223 | return [[self _routesControllerForURL:URL] routeURL:URL withParameters:parameters];
224 | }
225 |
226 | - (BOOL)routeURL:(NSURL *)URL withParameters:(NSDictionary *)parameters
227 | {
228 | return [self _routeURL:URL withParameters:parameters executeRouteBlock:YES];
229 | }
230 |
231 |
232 | #pragma mark - Private
233 |
234 | + (instancetype)_routesControllerForURL:(NSURL *)URL
235 | {
236 | if (URL == nil) {
237 | return nil;
238 | }
239 |
240 | return JLRGlobal_routeControllersMap[URL.scheme] ?: [JLRoutes globalRoutes];
241 | }
242 |
243 | - (void)_registerRoute:(JLRRouteDefinition *)route
244 | {
245 | if (route.priority == 0 || self.mutableRoutes.count == 0) {
246 | [self.mutableRoutes addObject:route];
247 | } else {
248 | NSUInteger index = 0;
249 | BOOL addedRoute = NO;
250 |
251 | // search through existing routes looking for a lower priority route than this one
252 | for (JLRRouteDefinition *existingRoute in [self.mutableRoutes copy]) {
253 | if (existingRoute.priority < route.priority) {
254 | // if found, add the route after it
255 | [self.mutableRoutes insertObject:route atIndex:index];
256 | addedRoute = YES;
257 | break;
258 | }
259 | index++;
260 | }
261 |
262 | // if we weren't able to find a lower priority route, this is the new lowest priority route (or same priority as self.routes.lastObject) and should just be added
263 | if (!addedRoute) {
264 | [self.mutableRoutes addObject:route];
265 | }
266 | }
267 |
268 | [route didBecomeRegisteredForScheme:self.scheme];
269 | }
270 |
271 | - (BOOL)_routeURL:(NSURL *)URL withParameters:(NSDictionary *)parameters executeRouteBlock:(BOOL)executeRouteBlock
272 | {
273 | if (!URL) {
274 | return NO;
275 | }
276 |
277 | [self _verboseLog:@"Trying to route URL %@", URL];
278 |
279 | BOOL didRoute = NO;
280 |
281 | JLRRouteRequestOptions options = [self _routeRequestOptions];
282 | JLRRouteRequest *request = [[JLRRouteRequest alloc] initWithURL:URL options:options additionalParameters:parameters];
283 |
284 | for (JLRRouteDefinition *route in [self.mutableRoutes copy]) {
285 | // check each route for a matching response
286 | JLRRouteResponse *response = [route routeResponseForRequest:request];
287 | if (!response.isMatch) {
288 | continue;
289 | }
290 |
291 | [self _verboseLog:@"Successfully matched %@", route];
292 |
293 | if (!executeRouteBlock) {
294 | // if we shouldn't execute but it was a match, we're done now
295 | return YES;
296 | }
297 |
298 | [self _verboseLog:@"Match parameters are %@", response.parameters];
299 |
300 | // Call the handler block
301 | didRoute = [route callHandlerBlockWithParameters:response.parameters];
302 |
303 | if (didRoute) {
304 | // if it was routed successfully, we're done - otherwise, continue trying to route
305 | break;
306 | }
307 | }
308 |
309 | if (!didRoute) {
310 | [self _verboseLog:@"Could not find a matching route"];
311 | }
312 |
313 | // if we couldn't find a match and this routes controller specifies to fallback and its also not the global routes controller, then...
314 | if (!didRoute && self.shouldFallbackToGlobalRoutes && ![self _isGlobalRoutesController]) {
315 | [self _verboseLog:@"Falling back to global routes..."];
316 | didRoute = [[JLRoutes globalRoutes] _routeURL:URL withParameters:parameters executeRouteBlock:executeRouteBlock];
317 | }
318 |
319 | // if, after everything, we did not route anything and we have an unmatched URL handler, then call it
320 | if (!didRoute && executeRouteBlock && self.unmatchedURLHandler) {
321 | [self _verboseLog:@"Falling back to the unmatched URL handler"];
322 | self.unmatchedURLHandler(self, URL, parameters);
323 | }
324 |
325 | return didRoute;
326 | }
327 |
328 | - (BOOL)_isGlobalRoutesController
329 | {
330 | return [self.scheme isEqualToString:JLRoutesGlobalRoutesScheme];
331 | }
332 |
333 | - (void)_verboseLog:(NSString *)format, ...
334 | {
335 | if (!JLRGlobal_verboseLoggingEnabled || format.length == 0) {
336 | return;
337 | }
338 |
339 | va_list argsList;
340 | va_start(argsList, format);
341 | #pragma clang diagnostic push
342 | #pragma clang diagnostic ignored "-Wformat-nonliteral"
343 | NSString *formattedLogMessage = [[NSString alloc] initWithFormat:format arguments:argsList];
344 | #pragma clang diagnostic pop
345 |
346 | va_end(argsList);
347 | NSLog(@"[JLRoutes]: %@", formattedLogMessage);
348 | }
349 |
350 | - (JLRRouteRequestOptions)_routeRequestOptions
351 | {
352 | JLRRouteRequestOptions options = JLRRouteRequestOptionsNone;
353 |
354 | if (JLRGlobal_shouldDecodePlusSymbols) {
355 | options |= JLRRouteRequestOptionDecodePlusSymbols;
356 | }
357 | if (JLRGlobal_alwaysTreatsHostAsPathComponent) {
358 | options |= JLRRouteRequestOptionTreatHostAsPathComponent;
359 | }
360 |
361 | return options;
362 | }
363 |
364 | @end
365 |
366 |
367 | #pragma mark - Global Options
368 |
369 | @implementation JLRoutes (GlobalOptions)
370 |
371 | + (void)setVerboseLoggingEnabled:(BOOL)loggingEnabled
372 | {
373 | JLRGlobal_verboseLoggingEnabled = loggingEnabled;
374 | }
375 |
376 | + (BOOL)isVerboseLoggingEnabled
377 | {
378 | return JLRGlobal_verboseLoggingEnabled;
379 | }
380 |
381 | + (void)setShouldDecodePlusSymbols:(BOOL)shouldDecode
382 | {
383 | JLRGlobal_shouldDecodePlusSymbols = shouldDecode;
384 | }
385 |
386 | + (BOOL)shouldDecodePlusSymbols
387 | {
388 | return JLRGlobal_shouldDecodePlusSymbols;
389 | }
390 |
391 | + (void)setAlwaysTreatsHostAsPathComponent:(BOOL)treatsHostAsPathComponent
392 | {
393 | JLRGlobal_alwaysTreatsHostAsPathComponent = treatsHostAsPathComponent;
394 | }
395 |
396 | + (BOOL)alwaysTreatsHostAsPathComponent
397 | {
398 | return JLRGlobal_alwaysTreatsHostAsPathComponent;
399 | }
400 |
401 | + (void)setDefaultRouteDefinitionClass:(Class)routeDefinitionClass
402 | {
403 | NSParameterAssert([routeDefinitionClass isSubclassOfClass:[JLRRouteDefinition class]]);
404 | JLRGlobal_routeDefinitionClass = routeDefinitionClass;
405 | }
406 |
407 | + (Class)defaultRouteDefinitionClass
408 | {
409 | return JLRGlobal_routeDefinitionClass;
410 | }
411 |
412 | @end
413 |
414 |
415 | #pragma mark - Deprecated
416 |
417 | NSString *const kJLRoutePatternKey = @"JLRoutePattern";
418 | NSString *const kJLRouteURLKey = @"JLRouteURL";
419 | NSString *const kJLRouteSchemeKey = @"JLRouteScheme";
420 | NSString *const kJLRouteWildcardComponentsKey = @"JLRouteWildcardComponents";
421 | NSString *const kJLRoutesGlobalRoutesScheme = @"JLRoutesGlobalRoutesScheme";
422 | NSString *const kJLRouteNamespaceKey = @"JLRouteScheme";
423 | NSString *const kJLRoutesGlobalNamespaceKey = @"JLRoutesGlobalRoutesScheme";
424 |
425 |
426 | @implementation JLRoutes (Deprecated)
427 |
428 | + (void)addRoute:(NSString *)routePattern handler:(BOOL (^)(NSDictionary *parameters))handlerBlock
429 | {
430 | [[self globalRoutes] addRoute:routePattern handler:handlerBlock];
431 | }
432 |
433 | + (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary *parameters))handlerBlock
434 | {
435 | [[self globalRoutes] addRoute:routePattern priority:priority handler:handlerBlock];
436 | }
437 |
438 | + (void)addRoutes:(NSArray *)routePatterns handler:(BOOL (^)(NSDictionary *parameters))handlerBlock
439 | {
440 | [[self globalRoutes] addRoutes:routePatterns handler:handlerBlock];
441 | }
442 |
443 | + (void)removeRoute:(NSString *)routePattern
444 | {
445 | [[self globalRoutes] removeRouteWithPattern:routePattern];
446 | }
447 |
448 | + (void)removeAllRoutes
449 | {
450 | [[self globalRoutes] removeAllRoutes];
451 | }
452 |
453 | + (BOOL)canRouteURL:(NSURL *)URL withParameters:(NSDictionary *)parameters
454 | {
455 | return [[self globalRoutes] canRouteURL:URL];
456 | }
457 |
458 | - (BOOL)canRouteURL:(NSURL *)URL withParameters:(NSDictionary *)parameters
459 | {
460 | return [self canRouteURL:URL];
461 | }
462 |
463 | @end
464 |
--------------------------------------------------------------------------------
/JLRoutesTests/Info.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 |
--------------------------------------------------------------------------------
/JLRoutesTests/JLRoutesTests.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Joel Levin
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 | */
12 |
13 | #import
14 | #import "JLRoutes.h"
15 | #import "JLRRouteDefinition.h"
16 | #import "JLRRouteHandler.h"
17 |
18 |
19 | #define JLValidateParameterCount(expectedCount)\
20 | XCTAssertNotNil(self.lastMatch, @"Matched something");\
21 | XCTAssertEqual((NSInteger)[self.lastMatch count] - 3, (NSInteger)expectedCount, @"Expected parameter count")
22 |
23 | #define JLValidateParameterCountIncludingWildcard(expectedCount)\
24 | XCTAssertNotNil(self.lastMatch, @"Matched something");\
25 | XCTAssertEqual((NSInteger)[self.lastMatch count] - 4, (NSInteger)expectedCount, @"Expected parameter count")
26 |
27 | #define JLValidateParameter(parameter) {\
28 | NSString *key = [[parameter allKeys] lastObject];\
29 | NSString *value = [[parameter allValues] lastObject];\
30 | XCTAssertEqualObjects(self.lastMatch[key], value, @"Exact parameter pair not found");}
31 |
32 | #define JLValidateAnyRouteMatched()\
33 | XCTAssertTrue(self.didRoute, @"Expected any route to match")
34 |
35 | #define JLValidateNoLastMatch()\
36 | XCTAssertFalse(self.didRoute, @"Expected not to route successfully")
37 |
38 | #define JLValidatePattern(pattern)\
39 | XCTAssertEqualObjects(self.lastMatch[JLRoutePatternKey], pattern, @"Pattern did not match")
40 |
41 | #define JLValidateScheme(scheme)\
42 | XCTAssertEqualObjects(self.lastMatch[JLRouteSchemeKey], scheme, @"Scheme did not match")
43 |
44 |
45 | #pragma mark -
46 |
47 |
48 | @interface JLRMockTargetObject : NSObject
49 |
50 | @property (nonatomic, copy) NSDictionary *routeParams;
51 |
52 | @end
53 |
54 |
55 | @interface JLRMockRouteDefinition : JLRRouteDefinition
56 | @end
57 |
58 |
59 | @interface JLRoutesTests : XCTestCase
60 |
61 | @property (assign) BOOL didRoute;
62 | @property (strong) NSDictionary *lastMatch;
63 |
64 | + (BOOL (^)(NSDictionary *))defaultRouteHandler;
65 |
66 | - (void)route:(NSString *)URLString;
67 |
68 | @end
69 |
70 |
71 | static JLRoutesTests *testsInstance = nil;
72 |
73 |
74 | @implementation JLRoutesTests
75 |
76 | + (void)setUp
77 | {
78 | [super setUp];
79 | [JLRoutes setVerboseLoggingEnabled:YES];
80 | }
81 |
82 | - (void)setUp
83 | {
84 | [super setUp];
85 |
86 | testsInstance = self;
87 |
88 | // reset settings
89 | [JLRoutes setShouldDecodePlusSymbols:YES];
90 | [JLRoutes setAlwaysTreatsHostAsPathComponent:NO];
91 | [JLRoutes setDefaultRouteDefinitionClass:[JLRRouteDefinition class]];
92 | }
93 |
94 | - (void)tearDown
95 | {
96 | [super tearDown];
97 | [JLRoutes unregisterAllRouteSchemes];
98 | }
99 |
100 | #pragma mark - Tests
101 |
102 | - (void)testRoutesArray
103 | {
104 | id defaultHandler = [[self class] defaultRouteHandler];
105 |
106 | [[JLRoutes globalRoutes] addRoute:@"/global1" handler:defaultHandler];
107 | [[JLRoutes globalRoutes] addRoute:@"/global2" handler:defaultHandler];
108 | [[JLRoutes globalRoutes] addRoute:@"/global3" handler:defaultHandler];
109 |
110 | NSArray *globalRoutes = [[JLRoutes globalRoutes] routes];
111 |
112 | XCTAssertEqual(globalRoutes.count, 3UL);
113 | XCTAssertEqualObjects(globalRoutes[0].pattern, @"/global1");
114 | XCTAssertEqualObjects(globalRoutes[1].pattern, @"/global2");
115 | XCTAssertEqualObjects(globalRoutes[2].pattern, @"/global3");
116 |
117 | [[JLRoutes routesForScheme:@"scheme"] addRoute:@"/scheme1" handler:defaultHandler];
118 | [[JLRoutes routesForScheme:@"scheme"] addRoute:@"/scheme2" handler:defaultHandler];
119 |
120 | NSArray *schemeRoutes = [[JLRoutes routesForScheme:@"scheme"] routes];
121 |
122 | XCTAssertEqual(schemeRoutes.count, 2UL);
123 | XCTAssertEqualObjects(schemeRoutes[0].pattern, @"/scheme1");
124 | XCTAssertEqualObjects(schemeRoutes[1].pattern, @"/scheme2");
125 |
126 | NSArray *nonexistant = [[JLRoutes routesForScheme:@"foo"] routes];
127 | XCTAssertEqual(nonexistant.count, 0UL);
128 | }
129 |
130 | - (void)testAllRoutes
131 | {
132 | id defaultHandler = [[self class] defaultRouteHandler];
133 |
134 | [[JLRoutes globalRoutes] addRoute:@"/global1" handler:defaultHandler];
135 | [[JLRoutes globalRoutes] addRoute:@"/global2" handler:defaultHandler];
136 | [[JLRoutes globalRoutes] addRoute:@"/global3" handler:defaultHandler];
137 | [[JLRoutes routesForScheme:@"scheme"] addRoute:@"/scheme1" handler:defaultHandler];
138 | [[JLRoutes routesForScheme:@"scheme"] addRoute:@"/scheme2" handler:defaultHandler];
139 |
140 | NSDictionary *> *allRoutes = [JLRoutes allRoutes];
141 |
142 | NSArray *globalRoutes = allRoutes[JLRoutesGlobalRoutesScheme];
143 | XCTAssertEqual(globalRoutes.count, 3UL);
144 | XCTAssertEqualObjects(globalRoutes[0].pattern, @"/global1");
145 | XCTAssertEqualObjects(globalRoutes[1].pattern, @"/global2");
146 | XCTAssertEqualObjects(globalRoutes[2].pattern, @"/global3");
147 |
148 | NSArray *schemeRoutes = allRoutes[@"scheme"];
149 | XCTAssertEqual(schemeRoutes.count, 2UL);
150 | XCTAssertEqualObjects(schemeRoutes[0].pattern, @"/scheme1");
151 | XCTAssertEqualObjects(schemeRoutes[1].pattern, @"/scheme2");
152 | }
153 |
154 | - (void)testRouting
155 | {
156 | id defaultHandler = [[self class] defaultRouteHandler];
157 |
158 | [[JLRoutes globalRoutes] addRoute:@"/test" handler:defaultHandler];
159 | [[JLRoutes globalRoutes] addRoute:@"/user/view/:userID" handler:defaultHandler];
160 | [[JLRoutes globalRoutes] addRoute:@"/:object/:action/:primaryKey" handler:defaultHandler];
161 | [[JLRoutes globalRoutes] addRoute:@"/" handler:defaultHandler];
162 | [[JLRoutes globalRoutes] addRoute:@"/:" handler:defaultHandler];
163 | [[JLRoutes globalRoutes] addRoute:@"/interleaving/:param1/foo/:param2" handler:defaultHandler];
164 | [[JLRoutes globalRoutes] addRoute:@"/xyz/wildcard/*" handler:defaultHandler];
165 | [[JLRoutes globalRoutes] addRoute:@"/route/:param/*" handler:defaultHandler];
166 | [[JLRoutes globalRoutes] addRoute:@"/required/:requiredParam(/optional/:optionalParam)(/moreOptional/:moreOptionalParam)" handler:defaultHandler];
167 |
168 | [self route:nil];
169 | JLValidateNoLastMatch();
170 |
171 | [self route:@"tests:/"];
172 | JLValidateAnyRouteMatched();
173 | JLValidatePattern(@"/");
174 | JLValidateParameterCount(0);
175 |
176 | [self route:@"tests://"];
177 | JLValidateAnyRouteMatched();
178 | JLValidatePattern(@"/");
179 | JLValidateParameterCount(0);
180 |
181 | [self route:@"tests://test?"];
182 | JLValidateAnyRouteMatched();
183 | JLValidateParameterCount(0);
184 | JLValidatePattern(@"/test");
185 |
186 | [self route:@"tests://test/"];
187 | JLValidateAnyRouteMatched();
188 | JLValidateParameterCount(0);
189 | JLValidatePattern(@"/test");
190 |
191 | [self route:@"tests://test"];
192 | JLValidateAnyRouteMatched();
193 | JLValidateParameterCount(0);
194 |
195 | [self route:@"tests://?key=value"];
196 | JLValidateAnyRouteMatched();
197 | JLValidateParameterCount(1);
198 | JLValidateParameter(@{@"key": @"value"});
199 |
200 | [self route:@"tests://user/view/joeldev"];
201 | JLValidateAnyRouteMatched();
202 | JLValidateParameterCount(1);
203 | JLValidateParameter(@{@"userID": @"joeldev"});
204 |
205 | [self route:@"tests://user/view/joeldev/"];
206 | JLValidateAnyRouteMatched();
207 | JLValidateParameterCount(1);
208 | JLValidateParameter(@{@"userID": @"joeldev"});
209 |
210 | [self route:@"tests://user/view/joel%20levin"];
211 | JLValidateAnyRouteMatched();
212 | JLValidateParameterCount(1);
213 | JLValidateParameter(@{@"userID": @"joel levin"});
214 |
215 | [self route:@"tests://user/view/joeldev?foo=bar&thing=stuff"];
216 | JLValidateAnyRouteMatched();
217 | JLValidateParameterCount(3);
218 | JLValidateParameter(@{@"userID": @"joeldev"});
219 | JLValidateParameter(@{@"foo" : @"bar"});
220 | JLValidateParameter(@{@"thing" : @"stuff"});
221 |
222 | [self route:@"tests://user/view/joeldev#foo=bar&thing=stuff"];
223 | JLValidateAnyRouteMatched();
224 | JLValidateParameterCount(3);
225 | JLValidateParameter(@{@"userID": @"joeldev"});
226 | JLValidateParameter(@{@"foo" : @"bar"});
227 | JLValidateParameter(@{@"thing" : @"stuff"});
228 |
229 | [self route:@"tests://user/view/joeldev?userID=evilPerson"];
230 | JLValidateAnyRouteMatched();
231 | JLValidateParameterCount(1);
232 | JLValidateParameter(@{@"userID": @"joeldev"});
233 |
234 | [self route:@"tests://post/edit/123"];
235 | JLValidateAnyRouteMatched();
236 | JLValidateParameterCount(3);
237 | JLValidateParameter(@{@"object": @"post"});
238 | JLValidateParameter(@{@"action": @"edit"});
239 | JLValidateParameter(@{@"primaryKey": @"123"});
240 |
241 | [self route:@"tests://interleaving/paramvalue1/foo/paramvalue2"];
242 | JLValidateAnyRouteMatched();
243 | JLValidateParameterCount(2);
244 | JLValidateParameter(@{@"param1": @"paramvalue1"});
245 | JLValidateParameter(@{@"param2": @"paramvalue2"});
246 |
247 | [self route:@"tests://xyz/wildcard"];
248 | JLValidateAnyRouteMatched();
249 | JLValidateParameterCountIncludingWildcard(0);
250 |
251 | [self route:@"tests://xyz/wildcard/matches/with/extra/path/components"];
252 | JLValidateAnyRouteMatched();
253 | JLValidateParameterCount(1);
254 | NSArray *wildcardMatches = @[@"matches", @"with", @"extra", @"path", @"components"];
255 | JLValidateParameter(@{JLRouteWildcardComponentsKey: wildcardMatches});
256 |
257 | [self route:@"tests://route/matches/with/wildcard"];
258 | JLValidateAnyRouteMatched();
259 | JLValidateParameterCount(2);
260 | JLValidateParameter(@{@"param": @"matches"});
261 | NSArray *parameterWildcardMatches = @[@"with", @"wildcard"];
262 | JLValidateParameter(@{JLRouteWildcardComponentsKey: parameterWildcardMatches});
263 |
264 | [self route:@"tests://doesnt/exist/and/wont/match"];
265 | JLValidateNoLastMatch();
266 |
267 | [self routeURL:[NSURL URLWithString:@"/test" relativeToURL:[NSURL URLWithString:@"http://localhost"]] withParameters:nil];
268 | JLValidateAnyRouteMatched();
269 | JLValidatePattern(@"/test");
270 | JLValidateParameterCount(0);
271 |
272 | [self route:@"tests://required/mustExist"];
273 | JLValidateAnyRouteMatched();
274 | JLValidateParameterCount(1);
275 | JLValidateParameter(@{@"requiredParam": @"mustExist"});
276 |
277 | [self route:@"tests://required/mustExist/optional/mightExist"];
278 | JLValidateAnyRouteMatched();
279 | JLValidateParameterCount(2);
280 | JLValidateParameter(@{@"requiredParam": @"mustExist"});
281 | JLValidateParameter(@{@"optionalParam": @"mightExist"});
282 |
283 | [self route:@"tests://required/mustExist/optional/mightExist/moreOptional/mightExistToo"];
284 | JLValidateAnyRouteMatched();
285 | JLValidateParameterCount(3);
286 | JLValidateParameter(@{@"requiredParam": @"mustExist"});
287 | JLValidateParameter(@{@"optionalParam": @"mightExist"});
288 | JLValidateParameter(@{@"moreOptionalParam": @"mightExistToo"});
289 | }
290 |
291 | - (void)testRoutingWithParameters
292 | {
293 | id defaultHandler = [[self class] defaultRouteHandler];
294 |
295 | [[JLRoutes globalRoutes] addRoute:@"/foo/:routeParam" handler:defaultHandler];
296 |
297 | [self route:@"/foo/bar" withParameters:@{@"stringParam": @"stringValue", @"nonStringParam": @(123)}];
298 | JLValidateAnyRouteMatched();
299 | JLValidateParameterCount(3);
300 | JLValidateParameter(@{@"routeParam": @"bar"});
301 | JLValidateParameter(@{@"stringParam": @"stringValue"});
302 | JLValidateParameter(@{@"nonStringParam": @(123)});
303 | }
304 |
305 | - (void)testFragmentRouting
306 | {
307 | id defaultHandler = [[self class] defaultRouteHandler];
308 |
309 | [[JLRoutes globalRoutes] addRoute:@"/user#/view/:userID" handler:defaultHandler];
310 | [[JLRoutes globalRoutes] addRoute:@"/:object#/:action/:primaryKey" handler:defaultHandler];
311 | [[JLRoutes globalRoutes] addRoute:@"/interleaving/:param1#/foo/:param2" handler:defaultHandler];
312 | [[JLRoutes globalRoutes] addRoute:@"/xyz/wildcard#/*" handler:defaultHandler];
313 | [[JLRoutes globalRoutes] addRoute:@"/route#/:param/*" handler:defaultHandler];
314 | [[JLRoutes globalRoutes] addRoute:@"/required#/:requiredParam(/optional/:optionalParam)(/moreOptional/:moreOptionalParam)" handler:defaultHandler];
315 |
316 | [self route:nil];
317 | JLValidateNoLastMatch();
318 |
319 | [self route:@"tests://user#/view/joeldev"];
320 | JLValidateAnyRouteMatched();
321 | JLValidateParameterCount(1);
322 | JLValidateParameter(@{@"userID": @"joeldev"});
323 |
324 | [self route:@"tests://user#/view/joeldev/"];
325 | JLValidateAnyRouteMatched();
326 | JLValidateParameterCount(1);
327 | JLValidateParameter(@{@"userID": @"joeldev"});
328 |
329 | [self route:@"tests://user#/view/joel%20levin"];
330 | JLValidateAnyRouteMatched();
331 | JLValidateParameterCount(1);
332 | JLValidateParameter(@{@"userID": @"joel levin"});
333 |
334 | [self route:@"tests://user#/view/joeldev?foo=bar&thing=stuff"];
335 | JLValidateAnyRouteMatched();
336 | JLValidateParameterCount(3);
337 | JLValidateParameter(@{@"userID": @"joeldev"});
338 | JLValidateParameter(@{@"foo" : @"bar"});
339 | JLValidateParameter(@{@"thing" : @"stuff"});
340 |
341 | [self route:@"tests://user#/view/joeldev?userID=evilPerson"];
342 | JLValidateAnyRouteMatched();
343 | JLValidateParameterCount(1);
344 | JLValidateParameter(@{@"userID": @"joeldev"});
345 |
346 | [self route:@"tests://user#/view/joeldev?userID=evilPerson&search=evilSearch&evilThing=evil"];
347 | JLValidateAnyRouteMatched();
348 | JLValidateParameterCount(3);
349 | JLValidateParameter(@{@"userID": @"joeldev"});
350 | JLValidateParameter(@{@"search": @"evilSearch"});
351 |
352 | [self route:@"tests://user?search=niceSearch&go=home#/view/joeldev?userID=evilPerson&&evilThing=evil"];
353 | JLValidateAnyRouteMatched();
354 | JLValidateParameterCount(4);
355 | JLValidateParameter(@{@"userID": @"joeldev"});
356 | JLValidateParameter(@{@"go": @"home"});
357 |
358 | [self route:@"tests://post#/edit/123"];
359 | JLValidateAnyRouteMatched();
360 | JLValidateParameterCount(3);
361 | JLValidateParameter(@{@"object": @"post"});
362 | JLValidateParameter(@{@"action": @"edit"});
363 | JLValidateParameter(@{@"primaryKey": @"123"});
364 |
365 | [self route:@"tests://interleaving/paramvalue1#/foo/paramvalue2"];
366 | JLValidateAnyRouteMatched();
367 | JLValidateParameterCount(2);
368 | JLValidateParameter(@{@"param1": @"paramvalue1"});
369 | JLValidateParameter(@{@"param2": @"paramvalue2"});
370 |
371 | [self route:@"tests://xyz/wildcard#"];
372 | JLValidateAnyRouteMatched();
373 | JLValidateParameterCountIncludingWildcard(0);
374 |
375 | [self route:@"tests://xyz/wildcard#/matches/with/extra/path/components"];
376 | JLValidateAnyRouteMatched();
377 | JLValidateParameterCount(1);
378 | NSArray *wildcardMatches = @[@"matches", @"with", @"extra", @"path", @"components"];
379 | JLValidateParameter(@{JLRouteWildcardComponentsKey: wildcardMatches});
380 |
381 | [self route:@"tests://route#/matches/with/wildcard"];
382 | JLValidateAnyRouteMatched();
383 | JLValidateParameterCount(2);
384 | JLValidateParameter(@{@"param": @"matches"});
385 | NSArray *parameterWildcardMatches = @[@"with", @"wildcard"];
386 | JLValidateParameter(@{JLRouteWildcardComponentsKey: parameterWildcardMatches});
387 |
388 | [self route:@"tests://doesnt/exist#/and/wont/match"];
389 | JLValidateNoLastMatch();
390 |
391 | [self route:@"tests://required#/mustExist"];
392 | JLValidateAnyRouteMatched();
393 | JLValidateParameterCount(1);
394 | JLValidateParameter(@{@"requiredParam": @"mustExist"});
395 |
396 | [self route:@"tests://required#/mustExist/optional/mightExist"];
397 | JLValidateAnyRouteMatched();
398 | JLValidateParameterCount(2);
399 | JLValidateParameter(@{@"requiredParam": @"mustExist"});
400 | JLValidateParameter(@{@"optionalParam": @"mightExist"});
401 |
402 | [self route:@"tests://required#/mustExist/optional/mightExist/moreOptional/mightExistToo"];
403 | JLValidateAnyRouteMatched();
404 | JLValidateParameterCount(3);
405 | JLValidateParameter(@{@"requiredParam": @"mustExist"});
406 | JLValidateParameter(@{@"optionalParam": @"mightExist"});
407 | JLValidateParameter(@{@"moreOptionalParam": @"mightExistToo"});
408 | }
409 |
410 | - (void)testMultipleRoutePatterns
411 | {
412 | [[JLRoutes globalRoutes] addRoutes:@[@"/multiple1", @"/multiple2"] handler:[[self class] defaultRouteHandler]];
413 |
414 | [self route:@"tests://multiple1"];
415 | JLValidateAnyRouteMatched();
416 | JLValidateParameterCount(0);
417 |
418 | [self route:@"tests://multiple2"];
419 | JLValidateAnyRouteMatched();
420 | JLValidateParameterCount(0);
421 | }
422 |
423 | - (void)testPriority
424 | {
425 | id defaultHandler = [[self class] defaultRouteHandler];
426 |
427 | [[JLRoutes globalRoutes] addRoute:@"/test/priority/:level" handler:defaultHandler];
428 | [[JLRoutes globalRoutes] addRoute:@"/test/priority/high" priority:20 handler:defaultHandler];
429 |
430 | // this should match the /test/priority/high route even though there's one before it that would match if priority wasn't being set
431 | [self route:@"tests://test/priority/high"];
432 | JLValidateAnyRouteMatched();
433 | JLValidatePattern(@"/test/priority/high");
434 |
435 | // test for adding only routes with non-zero priority (https://github.com/joeldev/JLRoutes/issues/46)
436 | [[JLRoutes routesForScheme:@"priorityTest"] addRoute:@"/:foo/bar/:baz" priority:20 handler:[[self class] defaultRouteHandler]];
437 | [[JLRoutes routesForScheme:@"priorityTest"] addRoute:@"/:foo/things/:baz" priority:10 handler:[[self class] defaultRouteHandler]];
438 | [[JLRoutes routesForScheme:@"priorityTest"] addRoute:@"/:foo/:baz" priority:1 handler:[[self class] defaultRouteHandler]];
439 |
440 | [self route:@"priorityTest://stuff/things/foo"];
441 | JLValidateAnyRouteMatched();
442 |
443 | [self route:@"priorityTest://one/two"];
444 | JLValidateAnyRouteMatched();
445 |
446 | [self route:@"priorityTest://stuff/bar/baz"];
447 | JLValidateAnyRouteMatched();
448 | }
449 |
450 | - (void)testBlockReturnValue
451 | {
452 | [[JLRoutes globalRoutes] addRoute:@"/return/:value" handler:^BOOL(NSDictionary *parameters) {
453 | testsInstance.lastMatch = parameters;
454 | NSString *value = parameters[@"value"];
455 | return [value isEqualToString:@"yes"];
456 | }];
457 |
458 | // even though this matches a route, the block returns NO here so there won't be a valid match
459 | [self route:@"tests://return/no"];
460 | JLValidateNoLastMatch();
461 |
462 | // this one is the same route but will return yes, causing it to be flagged as a match
463 | [self route:@"tests://return/yes"];
464 | JLValidateAnyRouteMatched();
465 | }
466 |
467 | - (void)testSchemes
468 | {
469 | id defaultHandler = [[self class] defaultRouteHandler];
470 |
471 | [[JLRoutes globalRoutes] addRoute:@"/test" handler:defaultHandler];
472 | [[JLRoutes routesForScheme:@"namespaceTest1"] addRoute:@"/test" handler:defaultHandler];
473 | [[JLRoutes routesForScheme:@"namespaceTest2"] addRoute:@"/test" handler:defaultHandler];
474 |
475 | // test that the same route can be handled differently for three different scheme namespaces
476 | [self route:@"tests://test"];
477 | JLValidateAnyRouteMatched();
478 | JLValidateScheme(JLRoutesGlobalRoutesScheme);
479 |
480 | [self route:@"namespaceTest1://test"];
481 | JLValidateAnyRouteMatched();
482 | JLValidateScheme(@"namespaceTest1");
483 |
484 | [self route:@"namespaceTest2://test"];
485 | JLValidateAnyRouteMatched();
486 | JLValidateScheme(@"namespaceTest2");
487 | }
488 |
489 | - (void)testFallbackToGlobal
490 | {
491 | id defaultHandler = [[self class] defaultRouteHandler];
492 |
493 | [[JLRoutes globalRoutes] addRoute:@"/user/view/:userID" handler:defaultHandler];
494 | [[JLRoutes routesForScheme:@"namespaceTest1"] addRoute:@"/test" handler:defaultHandler];
495 | [[JLRoutes routesForScheme:@"namespaceTest2"] addRoute:@"/test" handler:defaultHandler];
496 | [JLRoutes routesForScheme:@"namespaceTest2"].shouldFallbackToGlobalRoutes = YES;
497 |
498 | // first case, fallback is off and so this should fail because this route isnt declared as part of namespaceTest1
499 | [self route:@"namespaceTest1://user/view/joeldev"];
500 | JLValidateNoLastMatch();
501 |
502 | // fallback is on, so this should route
503 | [self route:@"namespaceTest2://user/view/joeldev"];
504 | JLValidateAnyRouteMatched();
505 | JLValidateScheme(JLRoutesGlobalRoutesScheme);
506 | JLValidateParameterCount(1);
507 | JLValidateParameter(@{@"userID" : @"joeldev"});
508 | }
509 |
510 | - (void)testForRouteExistence
511 | {
512 | // This should return yes and no for whether we have a matching route.
513 |
514 | NSURL *shouldHaveRouteURL = [NSURL URLWithString:@"tests:/test"];
515 | NSURL *shouldNotHaveRouteURL = [NSURL URLWithString:@"tests:/dfjkbsdkjfbskjdfb/sdasd"];
516 |
517 | [[JLRoutes globalRoutes] addRoute:@"/test" handler:[[self class] defaultRouteHandler]];
518 |
519 | XCTAssertTrue([[JLRoutes globalRoutes] canRouteURL:shouldHaveRouteURL], @"Should state it can route known URL");
520 | XCTAssertFalse([[JLRoutes globalRoutes] canRouteURL:shouldNotHaveRouteURL], @"Should not state it can route unknown URL");
521 | }
522 |
523 | - (void)testSubscripting
524 | {
525 | JLRoutes.globalRoutes[@"/subscripting"] = [[self class] defaultRouteHandler];
526 |
527 | NSURL *shouldHaveRouteURL = [NSURL URLWithString:@"subscripting"];
528 |
529 | XCTAssertTrue([[JLRoutes globalRoutes] canRouteURL:shouldHaveRouteURL], @"Should state it can route known URL");
530 | }
531 |
532 | - (void)testNonSingletonUsage
533 | {
534 | JLRoutes *routes = [JLRoutes new];
535 | NSURL *trivialURL = [NSURL URLWithString:@"/success"];
536 | [routes addRoute:[trivialURL absoluteString] handler:nil];
537 | XCTAssertTrue([routes routeURL:trivialURL], @"Non-singleton instance should route known URL");
538 | }
539 |
540 | - (void)testRouteRemoval
541 | {
542 | id defaultHandler = [[self class] defaultRouteHandler];
543 |
544 | [[JLRoutes globalRoutes] addRoute:@"/:" handler:defaultHandler];
545 | [[JLRoutes routesForScheme:@"namespaceTest3"] addRoute:@"/test1" handler:defaultHandler];
546 | [[JLRoutes routesForScheme:@"namespaceTest3"] addRoute:@"/test2" handler:defaultHandler];
547 |
548 | [self route:@"namespaceTest3://test1"];
549 | JLValidateAnyRouteMatched();
550 |
551 | [[JLRoutes routesForScheme:@"namespaceTest3"] removeRouteWithPattern:@"/test1"];
552 | [self route:@"namespaceTest3://test1"];
553 | JLValidateNoLastMatch();
554 |
555 | [self route:@"namespaceTest3://test2"];
556 | JLValidateAnyRouteMatched();
557 | JLValidateScheme(@"namespaceTest3");
558 |
559 | [JLRoutes unregisterRouteScheme:@"namespaceTest3"];
560 |
561 | // this will get matched by our "/:" route in the global namespace - we just want to make sure it doesn't get matched by namespaceTest3
562 | [self route:@"namespaceTest3://test2"];
563 | JLValidateAnyRouteMatched();
564 | JLValidateScheme(JLRoutesGlobalRoutesScheme);
565 | }
566 |
567 | - (void)testPercentEncoding
568 | {
569 | /*
570 | from http://en.wikipedia.org/wiki/Percent-encoding
571 | ! # $ & ' ( ) * + , / : ; = ? @ [ ]
572 | %21 %23 %24 %26 %27 %28 %29 %2A %2B %2C %2F %3A %3B %3D %3F %40 %5B %5D
573 | */
574 |
575 | // NOTE: %2F is not supported.
576 | // [URL pathComponents] automatically expands values with %2F as if it was just a regular slash.
577 |
578 | [[JLRoutes globalRoutes] addRoute:@"/user/view/:userID" handler:[[self class] defaultRouteHandler]];
579 |
580 | [JLRoutes setShouldDecodePlusSymbols:NO];
581 |
582 | [self route:@"tests://user/view/joel%21levin"];
583 | JLValidateAnyRouteMatched();
584 | JLValidateParameterCount(1);
585 | JLValidateParameter(@{@"userID": @"joel!levin"});
586 |
587 | [self route:@"tests://user/view/joel%23levin"];
588 | JLValidateAnyRouteMatched();
589 | JLValidateParameterCount(1);
590 | JLValidateParameter(@{@"userID": @"joel#levin"});
591 |
592 | [self route:@"tests://user/view/joel%24levin"];
593 | JLValidateAnyRouteMatched();
594 | JLValidateParameterCount(1);
595 | JLValidateParameter(@{@"userID": @"joel$levin"});
596 |
597 | [self route:@"tests://user/view/joel%26levin"];
598 | JLValidateAnyRouteMatched();
599 | JLValidateParameterCount(1);
600 | JLValidateParameter(@{@"userID": @"joel&levin"});
601 |
602 | [self route:@"tests://user/view/joel%27levin"];
603 | JLValidateAnyRouteMatched();
604 | JLValidateParameterCount(1);
605 | JLValidateParameter(@{@"userID": @"joel'levin"});
606 |
607 | [self route:@"tests://user/view/joel%28levin"];
608 | JLValidateAnyRouteMatched();
609 | JLValidateParameterCount(1);
610 | JLValidateParameter(@{@"userID": @"joel(levin"});
611 |
612 | [self route:@"tests://user/view/joel%29levin"];
613 | JLValidateAnyRouteMatched();
614 | JLValidateParameterCount(1);
615 | JLValidateParameter(@{@"userID": @"joel)levin"});
616 |
617 | [self route:@"tests://user/view/joel%2Alevin"];
618 | JLValidateAnyRouteMatched();
619 | JLValidateParameterCount(1);
620 | JLValidateParameter(@{@"userID": @"joel*levin"});
621 |
622 | [self route:@"tests://user/view/joel%2Blevin"];
623 | JLValidateAnyRouteMatched();
624 | JLValidateParameterCount(1);
625 | JLValidateParameter(@{@"userID": @"joel+levin"});
626 |
627 | [self route:@"tests://user/view/joel%2Clevin"];
628 | JLValidateAnyRouteMatched();
629 | JLValidateParameterCount(1);
630 | JLValidateParameter(@{@"userID": @"joel,levin"});
631 |
632 | [self route:@"tests://user/view/joel%3Alevin"];
633 | JLValidateAnyRouteMatched();
634 | JLValidateParameterCount(1);
635 | JLValidateParameter(@{@"userID": @"joel:levin"});
636 |
637 | [self route:@"tests://user/view/joel%3Blevin"];
638 | JLValidateAnyRouteMatched();
639 | JLValidateParameterCount(1);
640 | JLValidateParameter(@{@"userID": @"joel;levin"});
641 |
642 | [self route:@"tests://user/view/joel%3Dlevin"];
643 | JLValidateAnyRouteMatched();
644 | JLValidateParameterCount(1);
645 | JLValidateParameter(@{@"userID": @"joel=levin"});
646 |
647 | [self route:@"tests://user/view/joel%3Flevin"];
648 | JLValidateAnyRouteMatched();
649 | JLValidateParameterCount(1);
650 | JLValidateParameter(@{@"userID": @"joel?levin"});
651 |
652 | [self route:@"tests://user/view/joel%40levin"];
653 | JLValidateAnyRouteMatched();
654 | JLValidateParameterCount(1);
655 | JLValidateParameter(@{@"userID": @"joel@levin"});
656 |
657 | [self route:@"tests://user/view/joel%5Blevin"];
658 | JLValidateAnyRouteMatched();
659 | JLValidateParameterCount(1);
660 | JLValidateParameter(@{@"userID": @"joel[levin"});
661 |
662 | [self route:@"tests://user/view/joel%5Dlevin"];
663 | JLValidateAnyRouteMatched();
664 | JLValidateParameterCount(1);
665 | JLValidateParameter(@{@"userID": @"joel]levin"});
666 | }
667 |
668 | - (void)testDecodePlusSymbols
669 | {
670 | [[JLRoutes globalRoutes] addRoute:@"/user/view/:userID" handler:[[self class] defaultRouteHandler]];
671 |
672 | [JLRoutes setShouldDecodePlusSymbols:YES];
673 |
674 | [self route:@"tests://user/view/joel%2Blevin"];
675 | JLValidateAnyRouteMatched();
676 | JLValidateParameterCount(1);
677 | JLValidateParameter(@{@"userID": @"joel levin"});
678 |
679 | [self route:@"tests://user/view/joel+levin"];
680 | JLValidateAnyRouteMatched();
681 | JLValidateParameterCount(1);
682 | JLValidateParameter(@{@"userID": @"joel levin"});
683 |
684 | [self route:@"tests://user/view/test?name=joel+levin"];
685 | JLValidateAnyRouteMatched();
686 | JLValidateParameterCount(2);
687 | JLValidateParameter(@{@"name": @"joel levin"});
688 |
689 | [self route:@"tests://user/view/test?people=joel+levin&people=foo+bar"];
690 | JLValidateAnyRouteMatched();
691 | JLValidateParameterCount(2);
692 | JLValidateParameter((@{@"people": @[@"joel levin", @"foo bar"]}));
693 |
694 | [JLRoutes setShouldDecodePlusSymbols:NO];
695 |
696 | [self route:@"tests://user/view/joel%2Blevin"];
697 | JLValidateAnyRouteMatched();
698 | JLValidateParameterCount(1);
699 | JLValidateParameter(@{@"userID": @"joel+levin"});
700 |
701 | [self route:@"tests://user/view/joel+levin"];
702 | JLValidateAnyRouteMatched();
703 | JLValidateParameterCount(1);
704 | JLValidateParameter(@{@"userID": @"joel+levin"});
705 |
706 | [self route:@"tests://user/view/test?name=joel+levin"];
707 | JLValidateAnyRouteMatched();
708 | JLValidateParameterCount(2);
709 | JLValidateParameter(@{@"name": @"joel+levin"});
710 |
711 | [self route:@"tests://user/view/test?people=joel+levin&people=foo+bar"];
712 | JLValidateAnyRouteMatched();
713 | JLValidateParameterCount(2);
714 | JLValidateParameter((@{@"people": @[@"joel+levin", @"foo+bar"]}));
715 | }
716 |
717 | - (void)testVariableEmptyFollowedByWildcard
718 | {
719 | [[JLRoutes routesForScheme:@"wildcardTests"] addRoute:@"list/:variable/detail/:variable2/*" handler:nil];
720 |
721 | [self route:@"wildcardTests://list/variable/detail/"];
722 | JLValidateNoLastMatch();
723 |
724 | [self route:@"wildcardTests://list/variable/detail/variable2"];
725 | JLValidateAnyRouteMatched();
726 | }
727 |
728 | - (void)testOptionalRoutesAtEnd
729 | {
730 | [[JLRoutes globalRoutes] addRoute:@"/path/:thing(/new)(/anotherpath/:anotherthing)" handler:[[self class] defaultRouteHandler]];
731 |
732 | XCTAssert([[JLRoutes globalRoutes] routes].count == 4);
733 |
734 | [self route:@"foo://path/abc/new/anotherpath/def"];
735 | JLValidateAnyRouteMatched();
736 | JLValidateParameter(@{@"thing": @"abc"});
737 | JLValidateParameter(@{@"anotherthing": @"def"});
738 |
739 | [self route:@"foo://path/foo/anotherpath/bar"];
740 | JLValidateAnyRouteMatched();
741 | JLValidateParameter(@{@"thing": @"foo"});
742 | JLValidateParameter(@{@"anotherthing": @"bar"});
743 |
744 | [self route:@"foo://path/yyy/new"];
745 | JLValidateAnyRouteMatched();
746 | JLValidateParameter(@{@"thing": @"yyy"});
747 |
748 | [self route:@"foo://path/zzz"];
749 | JLValidateAnyRouteMatched();
750 | JLValidateParameter(@{@"thing": @"zzz"});
751 |
752 | [self route:@"foo://path/zzz/anotherpath"];
753 | JLValidateNoLastMatch();
754 | }
755 |
756 | - (void)testOptionalRoutesAtStart
757 | {
758 | [[JLRoutes globalRoutes] addRoute:@"/(rest/)(app/):object/:id" handler:[[self class] defaultRouteHandler]];
759 |
760 | XCTAssert([[JLRoutes globalRoutes] routes].count == 4);
761 |
762 | [self route:@"foo://rest/app/aaa/bbb"];
763 | JLValidateAnyRouteMatched();
764 | JLValidateParameter(@{@"object": @"aaa"});
765 | JLValidateParameter(@{@"id": @"bbb"});
766 |
767 | [self route:@"foo://app/aaa/bbb"];
768 | JLValidateAnyRouteMatched();
769 | JLValidateParameter(@{@"object": @"aaa"});
770 | JLValidateParameter(@{@"id": @"bbb"});
771 |
772 | [self route:@"foo://rest/aaa/bbb"];
773 | JLValidateAnyRouteMatched();
774 | JLValidateParameter(@{@"object": @"aaa"});
775 | JLValidateParameter(@{@"id": @"bbb"});
776 |
777 | [self route:@"foo://aaa/bbb"];
778 | JLValidateAnyRouteMatched();
779 | JLValidateParameter(@{@"object": @"aaa"});
780 | JLValidateParameter(@{@"id": @"bbb"});
781 | }
782 |
783 | - (void)testOptionalRoutesInterleaved
784 | {
785 | [[JLRoutes globalRoutes] addRoute:@"/(rest/):object/(app/):id" handler:[[self class] defaultRouteHandler]];
786 |
787 | XCTAssert([[JLRoutes globalRoutes] routes].count == 4);
788 |
789 | [self route:@"foo://rest/aaa/app/bbb"];
790 | JLValidateAnyRouteMatched();
791 | JLValidateParameter(@{@"object": @"aaa"});
792 | JLValidateParameter(@{@"id": @"bbb"});
793 |
794 | [self route:@"foo://aaa/app/bbb"];
795 | JLValidateAnyRouteMatched();
796 | JLValidateParameter(@{@"object": @"aaa"});
797 | JLValidateParameter(@{@"id": @"bbb"});
798 |
799 | [self route:@"foo://rest/aaa/bbb"];
800 | JLValidateAnyRouteMatched();
801 | JLValidateParameter(@{@"object": @"aaa"});
802 | JLValidateParameter(@{@"id": @"bbb"});
803 |
804 | [self route:@"foo://aaa/bbb"];
805 | JLValidateAnyRouteMatched();
806 | JLValidateParameter(@{@"object": @"aaa"});
807 | JLValidateParameter(@{@"id": @"bbb"});
808 | }
809 |
810 | - (void)testPassingURLStringsAsParams
811 | {
812 | [[JLRoutes globalRoutes] addRoute:@"/web/:URLString" handler:[[self class] defaultRouteHandler]];
813 | [[JLRoutes globalRoutes] addRoute:@"/web" handler:[[self class] defaultRouteHandler]];
814 |
815 | [self route:@"tests://web/http%3A%2F%2Ffoobar.com%2Fbaz"];
816 | JLValidateAnyRouteMatched();
817 | JLValidateParameter(@{@"URLString": @"http://foobar.com/baz"});
818 |
819 | [self route:@"tests://web?URLString=http%3A%2F%2Ffoobar.com%2Fbaz"];
820 | JLValidateAnyRouteMatched();
821 | JLValidateParameter(@{@"URLString": @"http://foobar.com/baz"});
822 | }
823 |
824 | - (void)testArrayQueryParams
825 | {
826 | [[JLRoutes globalRoutes] addRoute:@"/test/foo" handler:[[self class] defaultRouteHandler]];
827 |
828 | [self route:@"tests://test/foo?key=1&key=2&key=3&text=hi&text=there"];
829 | JLValidateAnyRouteMatched();
830 | JLValidateParameter((@{@"key": @[@"1", @"2", @"3"]}));
831 | JLValidateParameter((@{@"text": @[@"hi", @"there"]}));
832 | }
833 |
834 | - (void)testAddingCustomRouteDefinition
835 | {
836 | id defaultHandler = [[self class] defaultRouteHandler];
837 |
838 | JLRMockRouteDefinition *customRoute = [[JLRMockRouteDefinition alloc] initWithPattern:@"/test" priority:0 handlerBlock:defaultHandler];
839 | [[JLRoutes globalRoutes] addRoute:customRoute];
840 |
841 | [self route:@"tests://test"];
842 |
843 | JLValidateAnyRouteMatched();
844 | JLValidateScheme(JLRoutesGlobalRoutesScheme);
845 |
846 | [[JLRoutes globalRoutes] removeRoute:customRoute];
847 |
848 | [self route:@"tests://test"];
849 |
850 | JLValidateNoLastMatch();
851 | }
852 |
853 | - (void)testChangeDefaultRouteDefinitionClass
854 | {
855 | [JLRoutes setDefaultRouteDefinitionClass:[JLRMockRouteDefinition class]];
856 |
857 | id defaultHandler = [[self class] defaultRouteHandler];
858 | [[JLRoutes globalRoutes] addRoute:@"/foo" handler:defaultHandler];
859 |
860 | JLRRouteDefinition *route = [JLRoutes globalRoutes].routes.firstObject;
861 | XCTAssert([route isKindOfClass:[JLRMockRouteDefinition class]]);
862 | }
863 |
864 | - (void)testTreatsHostAsPathComponent
865 | {
866 | id defaultHandler = [[self class] defaultRouteHandler];
867 |
868 | [[JLRoutes globalRoutes] addRoute:@"/sign_in" handler:defaultHandler];
869 | [[JLRoutes globalRoutes] addRoute:@"/path/:pathid" handler:defaultHandler];
870 |
871 | [JLRoutes setAlwaysTreatsHostAsPathComponent:NO];
872 |
873 | [self route:@"https://www.mydomain.com/sign_in"];
874 | JLValidateAnyRouteMatched();
875 | JLValidatePattern(@"/sign_in");
876 |
877 | [self route:@"https://www.mydomain.com/path/3"];
878 | JLValidateAnyRouteMatched();
879 | JLValidatePattern(@"/path/:pathid");
880 | JLValidateParameter((@{@"pathid": @"3"}));
881 |
882 | [JLRoutes setAlwaysTreatsHostAsPathComponent:YES];
883 |
884 | [self route:@"https://www.mydomain2.com/sign_in"];
885 | JLValidateNoLastMatch();
886 |
887 | [self route:@"https://www.mydomain2.com/path/3"];
888 | JLValidateNoLastMatch();
889 |
890 | [[JLRoutes globalRoutes] addRoute:@"/www.mydomain2.com/sign_in" handler:defaultHandler];
891 | [[JLRoutes globalRoutes] addRoute:@"/www.mydomain2.com/path/:pathid" handler:defaultHandler];
892 |
893 | [self route:@"https://www.mydomain2.com/sign_in"];
894 | JLValidateAnyRouteMatched();
895 | JLValidatePattern(@"/www.mydomain2.com/sign_in");
896 |
897 | [self route:@"https://www.mydomain2.com/path/3"];
898 | JLValidateAnyRouteMatched();
899 | JLValidatePattern(@"/www.mydomain2.com/path/:pathid");
900 | JLValidateParameter((@{@"pathid": @"3"}));
901 | }
902 |
903 | - (void)testRouteDefinitionEquality
904 | {
905 | id defaultHandler = [[self class] defaultRouteHandler];
906 |
907 | JLRRouteDefinition *routeA = [[JLRRouteDefinition alloc] initWithPattern:@"/foo" priority:0 handlerBlock:defaultHandler];
908 | JLRRouteDefinition *routeB = [routeA copy];
909 |
910 | XCTAssertTrue([routeA isEqual:routeB]);
911 |
912 | [routeB didBecomeRegisteredForScheme:@"scheme"];
913 |
914 | XCTAssertFalse([routeA isEqual:routeB]);
915 |
916 | JLRRouteDefinition *routeC = [[JLRRouteDefinition alloc] initWithPattern:@"/foo/bar" priority:0 handlerBlock:defaultHandler];
917 |
918 | XCTAssertFalse([routeA isEqual:routeC]);
919 | }
920 |
921 | - (void)testHandlerBlockForWeakTarget
922 | {
923 | JLRMockTargetObject *object = [[JLRMockTargetObject alloc] init];
924 | id handlerBlock = [JLRRouteHandler handlerBlockForWeakTarget:object];
925 |
926 | [[JLRoutes globalRoutes] addRoute:@"/test" handler:handlerBlock];
927 |
928 | [self route:@"/test"];
929 | JLValidateAnyRouteMatched();
930 | XCTAssertEqualObjects(testsInstance.lastMatch, object.routeParams);
931 |
932 | object = nil; // ensure that the object is not retained by handler block
933 |
934 | [self route:@"/test"];
935 | JLValidateNoLastMatch();
936 | }
937 |
938 | - (void)testHandlerBlockForTargetClass
939 | {
940 | __block JLRMockTargetObject *createdObject = nil;
941 | id handlerBlock = [JLRRouteHandler handlerBlockForTargetClass:[JLRMockTargetObject class] completion:^BOOL (id object) {
942 | createdObject = (JLRMockTargetObject *)object;
943 | return YES;
944 | }];
945 |
946 | [[JLRoutes globalRoutes] addRoute:@"/test" handler:handlerBlock];
947 |
948 | [self route:@"/test"];
949 | JLValidateAnyRouteMatched();
950 | XCTAssertEqualObjects(testsInstance.lastMatch, createdObject.routeParams);
951 |
952 | XCTAssertNotNil(createdObject);
953 | }
954 |
955 | #pragma mark - Convenience Methods
956 |
957 | + (BOOL (^)(NSDictionary *))defaultRouteHandler
958 | {
959 | return ^BOOL (NSDictionary *params) {
960 | testsInstance.lastMatch = params;
961 | return YES;
962 | };
963 | }
964 |
965 | - (void)route:(NSString *)URLString
966 | {
967 | [self route:URLString withParameters:nil];
968 | }
969 |
970 | - (void)route:(NSString *)URLString withParameters:(NSDictionary *)parameters
971 | {
972 | [self routeURL:[NSURL URLWithString:URLString] withParameters:parameters];
973 | }
974 |
975 | - (void)routeURL:(NSURL *)URL withParameters:(NSDictionary *)parameters
976 | {
977 | NSLog(@"*** Routing %@", URL);
978 | self.lastMatch = nil;
979 | self.didRoute = [JLRoutes routeURL:URL withParameters:parameters];
980 | }
981 |
982 | @end
983 |
984 |
985 | @implementation JLRMockTargetObject
986 |
987 | - (instancetype)initWithRouteParameters:(NSDictionary *)parameters
988 | {
989 | self = [super init];
990 | self.routeParams = parameters;
991 | testsInstance.lastMatch = parameters;
992 | return self;
993 | }
994 |
995 | - (BOOL)handleRouteWithParameters:(NSDictionary *)parameters
996 | {
997 | self.routeParams = parameters;
998 | testsInstance.lastMatch = parameters;
999 | return YES;
1000 | }
1001 |
1002 | @end
1003 |
1004 |
1005 | @implementation JLRMockRouteDefinition
1006 |
1007 | @end
1008 |
1009 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017, Joel Levin. All rights reserved.
2 |
3 | - Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 | - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
5 | - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
6 |
7 | Neither the name of JLRoutes nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.4
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "JLRoutes",
7 | platforms: [
8 | .macOS(.v10_10),
9 | .iOS(.v9),
10 | .tvOS(.v9),
11 | ],
12 | products: [
13 | .library(
14 | name: "JLRoutes",
15 | targets: ["JLRoutes"]
16 | ),
17 | ],
18 | targets: [
19 | .target(
20 | name: "JLRoutes",
21 | path: "JLRoutes",
22 | publicHeadersPath: "."
23 | ),
24 | ],
25 | swiftLanguageVersions: [.v5]
26 | )
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | JLRoutes
2 | ========
3 |
4 | [](http://cocoapods.org/pods/JLRoutes)
5 | [](http://cocoapods.org/pods/JLRoutes)
6 | [](https://github.com/Carthage/Carthage)
7 | [](https://travis-ci.org/joeldev/JLRoutes)
8 | [](https://cocoapods.org/pods/JLRoutes)
9 |
10 | ### What is it? ###
11 | JLRoutes is a URL routing library with a simple block-based API. It is designed to make it very easy to handle complex URL schemes in your application with minimal code.
12 |
13 | ### Installation ###
14 | JLRoutes is available for installation using [CocoaPods](https://cocoapods.org/pods/JLRoutes) or Carthage (add `github "joeldev/JLRoutes"` to your `Cartfile`).
15 |
16 | ### Requirements ###
17 | JLRoutes 2.x require iOS 8.0+ or macOS 10.10+. If you need to support iOS 7 or macOS 10.9, please use version 1.6.4 (which is the last 1.x release).
18 |
19 | ### Documentation ###
20 | Documentation is available [here](http://cocoadocs.org/docsets/JLRoutes/).
21 |
22 | ### Getting Started ###
23 |
24 | [Configure your URL schemes in Info.plist.](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Inter-AppCommunication/Inter-AppCommunication.html#//apple_ref/doc/uid/TP40007072-CH6-SW2)
25 |
26 | ```objc
27 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
28 | {
29 | JLRoutes *routes = [JLRoutes globalRoutes];
30 |
31 | [routes addRoute:@"/user/view/:userID" handler:^BOOL(NSDictionary *parameters) {
32 | NSString *userID = parameters[@"userID"]; // defined in the route by specifying ":userID"
33 |
34 | // present UI for viewing user with ID 'userID'
35 |
36 | return YES; // return YES to say we have handled the route
37 | }];
38 |
39 | return YES;
40 | }
41 |
42 | - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options
43 | {
44 | return [JLRoutes routeURL:url];
45 | }
46 | ```
47 |
48 | Routes can also be registered with subscripting syntax:
49 | ```objc
50 | JLRoutes.globalRoutes[@"/user/view/:userID"] = ^BOOL(NSDictionary *parameters) {
51 | // ...
52 | };
53 | ```
54 |
55 | After adding a route for `/user/view/:userID`, the following call will cause the handler block to be called with a dictionary containing `@"userID": @"joeldev"`:
56 | ```objc
57 | NSURL *viewUserURL = [NSURL URLWithString:@"myapp://user/view/joeldev"];
58 | [JLRoutes routeURL:viewUserURL];
59 | ```
60 |
61 | ### The Parameters Dictionary ###
62 |
63 | The parameters dictionary always contains at least the following three keys:
64 | ```json
65 | {
66 | "JLRouteURL": "(the NSURL that caused this block to be fired)",
67 | "JLRoutePattern": "(the actual route pattern string)",
68 | "JLRouteScheme": "(the route scheme, defaults to JLRoutesGlobalRoutesScheme)"
69 | }
70 | ```
71 |
72 | The JLRouteScheme key refers to the scheme that the matched route lives in. [Read more about schemes.](https://github.com/joeldev/JLRoutes#scheme-namespaces)
73 |
74 | See JLRoutes.h for the list of constants.
75 |
76 | ### Handler Block Chaining ###
77 |
78 | The handler block is expected to return a boolean for if it has handled the route or not. If the block returns `NO`, JLRoutes will behave as if that route is not a match and it will continue looking for a match. A route is considered to be a match if the pattern string matches **and** the block returns `YES`.
79 |
80 | It is also important to note that if you pass nil for the handler block, an internal handler block will be created that simply returns `YES`.
81 |
82 | ### Global Configuration ###
83 |
84 | There are multiple global configuration options available to help customize JLRoutes behavior for a particular use-case. All options only take affect for the next operation.
85 |
86 | ```objc
87 | /// Configures verbose logging. Defaults to NO.
88 | + (void)setVerboseLoggingEnabled:(BOOL)loggingEnabled;
89 |
90 | /// Configures if '+' should be replaced with spaces in parsed values. Defaults to YES.
91 | + (void)setShouldDecodePlusSymbols:(BOOL)shouldDecode;
92 |
93 | /// Configures if URL host is always considered to be a path component. Defaults to NO.
94 | + (void)setAlwaysTreatsHostAsPathComponent:(BOOL)treatsHostAsPathComponent;
95 |
96 | /// Configures the default class to use when creating route definitions. Defaults to JLRRouteDefinition.
97 | + (void)setDefaultRouteDefinitionClass:(Class)routeDefinitionClass;
98 | ```
99 |
100 | These are all configured at the `JLRoutes` class level:
101 | ```objc
102 | [JLRoutes setAlwaysTreatsHostAsPathComponent:YES];
103 | ```
104 |
105 | ### More Complex Example ###
106 |
107 | ```objc
108 | [[JLRoutes globalRoutes] addRoute:@"/:object/:action/:primaryKey" handler:^BOOL(NSDictionary *parameters) {
109 | NSString *object = parameters[@"object"];
110 | NSString *action = parameters[@"action"];
111 | NSString *primaryKey = parameters[@"primaryKey"];
112 | // stuff
113 | return YES;
114 | }];
115 | ```
116 |
117 | This route would match things like `/user/view/joeldev` or `/post/edit/123`. Let's say you called `/post/edit/123` with some URL params as well:
118 |
119 | ```objc
120 | NSURL *editPost = [NSURL URLWithString:@"myapp://post/edit/123?debug=true&foo=bar"];
121 | [JLRoutes routeURL:editPost];
122 | ```
123 |
124 | The parameters dictionary that the handler block receives would contain the following key/value pairs:
125 | ```json
126 | {
127 | "object": "post",
128 | "action": "edit",
129 | "primaryKey": "123",
130 | "debug": "true",
131 | "foo": "bar",
132 | "JLRouteURL": "myapp://post/edit/123?debug=true&foo=bar",
133 | "JLRoutePattern": "/:object/:action/:primaryKey",
134 | "JLRouteScheme": "JLRoutesGlobalRoutesScheme"
135 | }
136 | ```
137 |
138 | ### Schemes ###
139 |
140 | JLRoutes supports setting up routes within a specific URL scheme. Routes that are set up within a scheme can only be matched by URLs that use a matching URL scheme. By default, all routes go into the global scheme.
141 |
142 | ```objc
143 | [[JLRoutes globalRoutes] addRoute:@"/foo" handler:^BOOL(NSDictionary *parameters) {
144 | // This block is called if the scheme is not 'thing' or 'stuff' (see below)
145 | return YES;
146 | }];
147 |
148 | [[JLRoutes routesForScheme:@"thing"] addRoute:@"/foo" handler:^BOOL(NSDictionary *parameters) {
149 | // This block is called for thing://foo
150 | return YES;
151 | }];
152 |
153 | [[JLRoutes routesForScheme:@"stuff"] addRoute:@"/foo" handler:^BOOL(NSDictionary *parameters) {
154 | // This block is called for stuff://foo
155 | return YES;
156 | }];
157 | ```
158 |
159 | This example shows that you can declare the same routes in different schemes and handle them with different callbacks on a per-scheme basis.
160 |
161 | Continuing with this example, if you were to add the following route:
162 |
163 | ```objc
164 | [[JLRoutes globalRoutes] addRoute:@"/global" handler:^BOOL(NSDictionary *parameters) {
165 | return YES;
166 | }];
167 | ```
168 |
169 | and then try to route the URL `thing://global`, it would not match because that route has not been declared within the `thing` scheme but has instead been declared within the global scheme (which we'll assume is how the developer wants it). However, you can easily change this behavior by setting the following property to `YES`:
170 |
171 | ```objc
172 | [JLRoutes routesForScheme:@"thing"].shouldFallbackToGlobalRoutes = YES;
173 | ```
174 |
175 | This tells JLRoutes that if a URL cannot be routed within the `thing` scheme (aka, it starts with `thing:` but no appropriate route can be found), try to recover by looking for a matching route in the global routes scheme as well. After setting that property to `YES`, the URL `thing://global` would be routed to the `/global` handler block.
176 |
177 |
178 | ### Wildcards ###
179 |
180 | JLRoutes supports setting up routes that will match an arbitrary number of path components at the end of the routed URL. An array containing the additional path components will be added to the parameters dictionary with the key `JLRouteWildcardComponentsKey`.
181 |
182 | For example, the following route would be triggered for any URL that started with `/wildcard/`, but would be rejected by the handler if the next component wasn't `joker`.
183 |
184 | ```objc
185 | [[JLRoutes globalRoutes] addRoute:@"/wildcard/*" handler:^BOOL(NSDictionary *parameters) {
186 | NSArray *pathComponents = parameters[JLRouteWildcardComponentsKey];
187 | if (pathComponents.count > 0 && [pathComponents[0] isEqualToString:@"joker"]) {
188 | // the route matched; do stuff
189 | return YES;
190 | }
191 |
192 | // not interested unless 'joker' is in it
193 | return NO;
194 | }];
195 | ```
196 |
197 | ### Optional Routes ###
198 |
199 | JLRoutes supports setting up routes with optional parameters. At the route registration moment, JLRoute will register multiple routes with all combinations of the route with the optional parameters and without the optional parameters. For example, for the route `/the(/foo/:a)(/bar/:b)`, it will register the following routes:
200 |
201 | - `/the/foo/:a/bar/:b`
202 | - `/the/foo/:a`
203 | - `/the/bar/:b`
204 | - `/the`
205 |
206 | ### Querying Routes ###
207 |
208 | There are multiple ways to query routes for programmatic uses (such as powering a debug UI). There's a method to get the full set of routes across all schemes and another to get just the specific list of routes for a given scheme. One note, you'll have to import `JLRRouteDefinition.h` as it is forward-declared.
209 |
210 | ```objc
211 | /// All registered routes, keyed by scheme
212 | + (NSDictionary *> *)allRoutes;
213 |
214 | /// Return all registered routes in the receiving scheme namespace.
215 | - (NSArray *)routes;
216 | ```
217 |
218 | ### Handler Block Helper ###
219 |
220 | `JLRRouteHandler` is a helper class for creating handler blocks intended to be passed to an addRoute: call.
221 |
222 | This is specifically useful for cases in which you want a separate object or class to be the handler for a deeplink route. An example might be a view controller that you want to instantiate and present in response to a deeplink being opened.
223 |
224 | In order to take advantage of this helper, your target class must conform to the `JLRRouteHandlerTarget` protocol. For example:
225 |
226 | ```objc
227 | @interface MyTargetViewController : UIViewController
228 |
229 | @property (nonatomic, copy) NSDictionary *parameters;
230 |
231 | @end
232 |
233 |
234 | @implementation MyTargetViewController
235 |
236 | - (instancetype)initWithRouteParameters:(NSDictionary *)parameters
237 | {
238 | self = [super init];
239 |
240 | _parameters = [parameters copy]; // hold on to do something with later on
241 |
242 | return self;
243 | }
244 |
245 | - (void)viewDidLoad
246 | {
247 | [super viewDidLoad];
248 | // do something interesting with self.parameters, initialize views, etc...
249 | }
250 |
251 | @end
252 | ```
253 |
254 | To hook this up via `JLRRouteHandler`, you could do something like this:
255 |
256 | ```objc
257 | id handlerBlock = [JLRRouteHandler handlerBlockForTargetClass:[MyTargetViewController class] completion:^BOOL (MyTargetViewController *viewController) {
258 | // Push the created view controller onto the nav controller
259 | [self.navigationController pushViewController:viewController animated:YES];
260 | return YES;
261 | }];
262 |
263 | [[JLRoutes globalRoutes] addRoute:@"/some/route" handler:handlerBlock];
264 | ```
265 |
266 | There's also a `JLRRouteHandler` convenience method for easily routing to an existing instance of an object vs creating a new instance. For example:
267 |
268 | ```objc
269 | MyTargetViewController *rootController = ...; // some object that exists and conforms to JLRRouteHandlerTarget.
270 | id handlerBlock = [JLRRouteHandler handlerBlockForWeakTarget:rootController];
271 |
272 | [[JLRoutes globalRoutes] addRoute:@"/some/route" handler:handlerBlock];
273 | ```
274 |
275 | When the route is matched, it will call a method on the target object:
276 |
277 | ```objc
278 | - (BOOL)handleRouteWithParameters:(NSDictionary *)parameters;
279 | ```
280 |
281 | These two mechanisms (weak target and class target) provide a few other ways to organize deep link handlers without writing boilerplate code for each handler or otherwise having to solve that for each app that integrates JLRoutes.
282 |
283 | ### Custom Route Parsing ###
284 |
285 | It is possible to control how routes are parsed by subclassing `JLRRouteDefinition` and using the `addRoute:` method to add instances of your custom subclass.
286 |
287 | ```objc
288 | // Custom route defintion that always matches
289 | @interface AlwaysMatchRouteDefinition : JLRRouteDefinition
290 | @end
291 |
292 |
293 | @implementation AlwaysMatchRouteDefinition
294 |
295 | - (JLRRouteResponse *)routeResponseForRequest:(JLRRouteRequest *)request
296 | {
297 | // This method is called when JLRoutes is trying to determine if we are a match for the given request object.
298 |
299 | // Create the parameters dictionary
300 | NSDictionary *variables = [self routeVariablesForRequest:request];
301 | NSDictionary *matchParams = [self matchParametersForRequest:request routeVariables:variables];
302 |
303 | // Return a valid match!
304 | return [JLRRouteResponse validMatchResponseWithParameters:matchParams];
305 | }
306 |
307 | @end
308 | ```
309 |
310 | This route can now be created an added:
311 | ```objc
312 | id handlerBlock = ... // assume exists
313 | AlwaysMatchRouteDefinition *alwaysMatch = [[AlwaysMatchRouteDefinition alloc] initWithPattern:@"/foo" priority:0 handlerBlock:handlerBlock];
314 | [[JLRoutes globalRoutes] addRoute:alwaysMatch];
315 | ```
316 |
317 | Alternatively, if you've written a custom route definition and want JLRoutes to always use it when adding a route (using one of the `addRoute:` methods that takes in raw parameters), use `+setDefaultRouteDefinitionClass:` to configure it as the routing definition class:
318 | ```objc
319 | [JLRoutes setDefaultRouteDefinitionClass:[MyCustomRouteDefinition class]];
320 | ```
321 |
322 | ### License ###
323 | BSD 3-clause. See the [LICENSE](LICENSE) file for details.
324 |
325 |
--------------------------------------------------------------------------------