-
Notifications
You must be signed in to change notification settings - Fork 0
/
endpoints.html
404 lines (374 loc) · 25.3 KB
/
endpoints.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
<!DOCTYPE html>
<html lang="en">
<head>
<title>Endpoints Reference</title>
<link rel="stylesheet" type="text/css" href="css/jazzy.css" />
<link rel="stylesheet" type="text/css" href="css/highlight.css" />
<meta charset='utf-8'>
<script src="js/jquery.min.js" defer></script>
<script src="js/jazzy.js" defer></script>
</head>
<body>
<a title="Endpoints Reference"></a>
<header>
<div class="content-wrapper">
<p><a href="index.html">Moya Docs</a> (82% documented)</p>
</div>
</header>
<div class="content-wrapper">
<p id="breadcrumbs">
<a href="index.html">Moya Reference</a>
<img id="carat" src="img/carat.png" />
Endpoints Reference
</p>
</div>
<div class="content-wrapper">
<nav class="sidebar">
<ul class="nav-groups">
<li class="nav-group-name">
<a href="Guides.html">Guides</a>
<ul class="nav-group-tasks">
<li class="nav-group-task">
<a href="authentication.html">Authentication</a>
</li>
<li class="nav-group-task">
<a href="communityprojects.html">CommunityProjects</a>
</li>
<li class="nav-group-task">
<a href="endpoints.html">Endpoints</a>
</li>
<li class="nav-group-task">
<a href="plugins.html">Plugins</a>
</li>
<li class="nav-group-task">
<a href="providers.html">Providers</a>
</li>
<li class="nav-group-task">
<a href="readme.html">README</a>
</li>
<li class="nav-group-task">
<a href="reactiveswift.html">ReactiveSwift</a>
</li>
<li class="nav-group-task">
<a href="releasing.html">Releasing</a>
</li>
<li class="nav-group-task">
<a href="rxswift.html">RxSwift</a>
</li>
<li class="nav-group-task">
<a href="targets.html">Targets</a>
</li>
<li class="nav-group-task">
<a href="testing.html">Testing</a>
</li>
<li class="nav-group-task">
<a href="threading.html">Threading</a>
</li>
</ul>
</li>
<li class="nav-group-name">
<a href="Classes.html">Classes</a>
<ul class="nav-group-tasks">
<li class="nav-group-task">
<a href="Classes/CancellableToken.html">CancellableToken</a>
</li>
<li class="nav-group-task">
<a href="Classes/CredentialsPlugin.html">CredentialsPlugin</a>
</li>
<li class="nav-group-task">
<a href="Classes/Endpoint.html">Endpoint</a>
</li>
<li class="nav-group-task">
<a href="Classes/MoyaProvider.html">MoyaProvider</a>
</li>
<li class="nav-group-task">
<a href="Classes/NetworkActivityPlugin.html">NetworkActivityPlugin</a>
</li>
<li class="nav-group-task">
<a href="Classes/NetworkLoggerPlugin.html">NetworkLoggerPlugin</a>
</li>
<li class="nav-group-task">
<a href="Classes/NetworkLoggerPlugin/Configuration.html">– Configuration</a>
</li>
<li class="nav-group-task">
<a href="Classes/Response.html">Response</a>
</li>
</ul>
</li>
<li class="nav-group-name">
<a href="Enums.html">Enumerations</a>
<ul class="nav-group-tasks">
<li class="nav-group-task">
<a href="Enums/AuthorizationType.html">AuthorizationType</a>
</li>
<li class="nav-group-task">
<a href="Enums/EndpointSampleResponse.html">EndpointSampleResponse</a>
</li>
<li class="nav-group-task">
<a href="Enums/MoyaError.html">MoyaError</a>
</li>
<li class="nav-group-task">
<a href="Enums/MultiTarget.html">MultiTarget</a>
</li>
<li class="nav-group-task">
<a href="Enums/NetworkActivityChangeType.html">NetworkActivityChangeType</a>
</li>
<li class="nav-group-task">
<a href="Enums/StubBehavior.html">StubBehavior</a>
</li>
<li class="nav-group-task">
<a href="Enums/Task.html">Task</a>
</li>
<li class="nav-group-task">
<a href="Enums/ValidationType.html">ValidationType</a>
</li>
</ul>
</li>
<li class="nav-group-name">
<a href="Extensions.html">Extensions</a>
<ul class="nav-group-tasks">
<li class="nav-group-task">
<a href="Extensions/Method.html">Method</a>
</li>
<li class="nav-group-task">
<a href="Extensions/Request.html">Request</a>
</li>
<li class="nav-group-task">
<a href="Extensions/URL.html">URL</a>
</li>
</ul>
</li>
<li class="nav-group-name">
<a href="Functions.html">Functions</a>
<ul class="nav-group-tasks">
<li class="nav-group-task">
<a href="Functions.html#/s:4Moya23convertResponseToResult_7request4data5errors0E0OyAA0C0CAA0A5ErrorOGSo17NSHTTPURLResponseCSg_10Foundation10URLRequestVSgAP4DataVSgs0I0_pSgtF">convertResponseToResult(_:request:data:error:)</a>
</li>
</ul>
</li>
<li class="nav-group-name">
<a href="Protocols.html">Protocols</a>
<ul class="nav-group-tasks">
<li class="nav-group-task">
<a href="Protocols/AccessTokenAuthorizable.html">AccessTokenAuthorizable</a>
</li>
<li class="nav-group-task">
<a href="Protocols/Cancellable.html">Cancellable</a>
</li>
<li class="nav-group-task">
<a href="Protocols/MoyaProviderType.html">MoyaProviderType</a>
</li>
<li class="nav-group-task">
<a href="Protocols/PluginType.html">PluginType</a>
</li>
<li class="nav-group-task">
<a href="Protocols/RequestType.html">RequestType</a>
</li>
<li class="nav-group-task">
<a href="Protocols/TargetType.html">TargetType</a>
</li>
</ul>
</li>
<li class="nav-group-name">
<a href="Structs.html">Structures</a>
<ul class="nav-group-tasks">
<li class="nav-group-task">
<a href="Structs/AccessTokenPlugin.html">AccessTokenPlugin</a>
</li>
<li class="nav-group-task">
<a href="Structs/MultipartFormData.html">MultipartFormData</a>
</li>
<li class="nav-group-task">
<a href="Structs/MultipartFormData/FormDataProvider.html">– FormDataProvider</a>
</li>
<li class="nav-group-task">
<a href="Structs/ProgressResponse.html">ProgressResponse</a>
</li>
</ul>
</li>
<li class="nav-group-name">
<a href="Typealiases.html">Type Aliases</a>
<ul class="nav-group-tasks">
<li class="nav-group-task">
<a href="Typealiases.html#/s:4Moya10Completiona">Completion</a>
</li>
<li class="nav-group-task">
<a href="Typealiases.html#/s:4Moya19DownloadDestinationa">DownloadDestination</a>
</li>
<li class="nav-group-task">
<a href="Typealiases.html#/s:4Moya5Imagea">Image</a>
</li>
<li class="nav-group-task">
<a href="Typealiases.html#/ImageType">ImageType</a>
</li>
<li class="nav-group-task">
<a href="Typealiases.html#/s:4Moya9ImageTypea">ImageType</a>
</li>
<li class="nav-group-task">
<a href="Typealiases.html#/s:4Moya12JSONEncodinga">JSONEncoding</a>
</li>
<li class="nav-group-task">
<a href="Typealiases.html#/s:4Moya6Methoda">Method</a>
</li>
<li class="nav-group-task">
<a href="Typealiases.html#/s:4Moya17ParameterEncodinga">ParameterEncoding</a>
</li>
<li class="nav-group-task">
<a href="Typealiases.html#/s:4Moya13ProgressBlocka">ProgressBlock</a>
</li>
<li class="nav-group-task">
<a href="Typealiases.html#/s:4Moya18RequestInterceptora">RequestInterceptor</a>
</li>
<li class="nav-group-task">
<a href="Typealiases.html#/s:4Moya24RequestMultipartFormDataa">RequestMultipartFormData</a>
</li>
<li class="nav-group-task">
<a href="Typealiases.html#/s:4Moya7Sessiona">Session</a>
</li>
<li class="nav-group-task">
<a href="Typealiases.html#/s:4Moya11URLEncodinga">URLEncoding</a>
</li>
</ul>
</li>
</ul>
</nav>
<article class="main-content">
<section>
<section class="section">
<h1 id='endpoints' class='heading'>Endpoints</h1>
<p>An endpoint is a semi-internal data structure that Moya uses to reason about
the network request that will ultimately be made. An endpoint stores the
following data:</p>
<ul>
<li>The url.</li>
<li>The HTTP method (<code>GET</code>, <code>POST</code>, etc).</li>
<li>The HTTP request header fields.</li>
<li><code><a href="Enums/Task.html">Task</a></code> to differentiate <code>upload</code>, <code>download</code> or <code>request</code>.</li>
<li>The sample response (for unit testing).</li>
</ul>
<p><a href="Providers.md">Providers</a> map <a href="Targets.md">Targets</a> to Endpoints, then map
Endpoints to actual network requests.</p>
<p>There are two ways that you interact with Endpoints.</p>
<ol>
<li>When creating a provider, you may specify a mapping from <code>Target</code> to <code><a href="Classes/Endpoint.html">Endpoint</a></code>.</li>
<li>When creating a provider, you may specify a mapping from <code><a href="Classes/Endpoint.html">Endpoint</a></code> to <code>URLRequest</code>.</li>
</ol>
<p>The first might resemble the following:</p>
<pre class="highlight swift"><code><span class="k">let</span> <span class="nv">endpointClosure</span> <span class="o">=</span> <span class="p">{</span> <span class="p">(</span><span class="nv">target</span><span class="p">:</span> <span class="kt">MyTarget</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Endpoint</span> <span class="k">in</span>
<span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">target</span><span class="p">:</span> <span class="n">target</span><span class="p">)</span><span class="o">.</span><span class="n">absoluteString</span>
<span class="k">return</span> <span class="kt">Endpoint</span><span class="p">(</span><span class="nv">url</span><span class="p">:</span> <span class="n">url</span><span class="p">,</span> <span class="nv">sampleResponseClosure</span><span class="p">:</span> <span class="p">{</span><span class="o">.</span><span class="nf">networkResponse</span><span class="p">(</span><span class="mi">200</span><span class="p">,</span> <span class="n">target</span><span class="o">.</span><span class="n">sampleData</span><span class="p">)},</span> <span class="nv">method</span><span class="p">:</span> <span class="n">target</span><span class="o">.</span><span class="n">method</span><span class="p">,</span> <span class="nv">task</span><span class="p">:</span> <span class="n">target</span><span class="o">.</span><span class="n">task</span><span class="p">,</span> <span class="nv">httpHeaderFields</span><span class="p">:</span> <span class="n">target</span><span class="o">.</span><span class="n">headers</span><span class="p">)</span>
<span class="p">}</span>
</code></pre>
<p>This is actually the default implementation Moya provides. If you need something
custom, or if you’re creating a test provider that returns non-200 HTTP statuses in unit tests,
this is where you would do it.</p>
<p>Notice the <code>URL(target:)</code> initializer, Moya provides a convenient extension to create a <code>URL</code> from any <code><a href="Protocols/TargetType.html">TargetType</a></code>.</p>
<p>The second use is very uncommon. Moya tries to prevent you from having to worry
about low-level details. But it’s there if you need it. Its use is covered
further below.</p>
<p>Let’s take a look at an example of the flexibility mapping from a Target to
an Endpoint can provide.</p>
<h2 id='from-target-to-endpoint' class='heading'>From Target to Endpoint</h2>
<p>In this closure you have absolute power over converting from <code>Target</code> to <code><a href="Classes/Endpoint.html">Endpoint</a></code>.
You can change the <code>task</code>, <code>method</code>, <code>url</code>, <code>headers</code> or <code>sampleResponse</code>.
For example, we may wish to set our application name in the HTTP header fields for server-side
analytics.</p>
<pre class="highlight swift"><code><span class="k">let</span> <span class="nv">endpointClosure</span> <span class="o">=</span> <span class="p">{</span> <span class="p">(</span><span class="nv">target</span><span class="p">:</span> <span class="kt">MyTarget</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Endpoint</span> <span class="k">in</span>
<span class="k">let</span> <span class="nv">defaultEndpoint</span> <span class="o">=</span> <span class="kt">MoyaProvider</span><span class="o">.</span><span class="nf">defaultEndpointMapping</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="n">target</span><span class="p">)</span>
<span class="k">return</span> <span class="n">defaultEndpoint</span><span class="o">.</span><span class="nf">adding</span><span class="p">(</span><span class="nv">newHTTPHeaderFields</span><span class="p">:</span> <span class="p">[</span><span class="s">"APP_NAME"</span><span class="p">:</span> <span class="s">"MY_AWESOME_APP"</span><span class="p">])</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">provider</span> <span class="o">=</span> <span class="kt">MoyaProvider</span><span class="o"><</span><span class="kt">GitHub</span><span class="o">></span><span class="p">(</span><span class="nv">endpointClosure</span><span class="p">:</span> <span class="n">endpointClosure</span><span class="p">)</span>
</code></pre>
<p><em>Note that header fields can also be added as part of the <a href="Targets.md">Target</a> definition.</em></p>
<p>This also means that you can provide additional parameters to some or all of
your endpoints. For example, say that there is an authentication token we need
for all values of the hypothetical <code>MyTarget</code> target, with the exception of the
target that actually does the authentication. We could construct an
<code>endpointClosure</code> resembling the following.</p>
<pre class="highlight swift"><code><span class="k">let</span> <span class="nv">endpointClosure</span> <span class="o">=</span> <span class="p">{</span> <span class="p">(</span><span class="nv">target</span><span class="p">:</span> <span class="kt">MyTarget</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Endpoint</span> <span class="k">in</span>
<span class="k">let</span> <span class="nv">defaultEndpoint</span> <span class="o">=</span> <span class="kt">MoyaProvider</span><span class="o">.</span><span class="nf">defaultEndpointMapping</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="n">target</span><span class="p">)</span>
<span class="c1">// Sign all non-authenticating requests</span>
<span class="k">switch</span> <span class="n">target</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">authenticate</span><span class="p">:</span>
<span class="k">return</span> <span class="n">defaultEndpoint</span>
<span class="k">default</span><span class="p">:</span>
<span class="k">return</span> <span class="n">defaultEndpoint</span><span class="o">.</span><span class="nf">adding</span><span class="p">(</span><span class="nv">newHTTPHeaderFields</span><span class="p">:</span> <span class="p">[</span><span class="s">"AUTHENTICATION_TOKEN"</span><span class="p">:</span> <span class="kt">GlobalAppStorage</span><span class="o">.</span><span class="n">authToken</span><span class="p">])</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">provider</span> <span class="o">=</span> <span class="kt">MoyaProvider</span><span class="o"><</span><span class="kt">GitHub</span><span class="o">></span><span class="p">(</span><span class="nv">endpointClosure</span><span class="p">:</span> <span class="n">endpointClosure</span><span class="p">)</span>
</code></pre>
<p>Awesome.</p>
<p>Note that we can rely on the existing behavior of Moya and extend – instead
of replace – it. The <code>adding(newHttpHeaderFields:)</code> function allows you to
rely on the existing Moya code and add your own custom values.</p>
<p>Sample responses are a requirement of the <code><a href="Protocols/TargetType.html">TargetType</a></code> protocol. However, they
only specify the data returned. The Target-to-Endpoint mapping closure is where
you can specify more details, which is useful for unit testing.</p>
<p>Sample responses have one of these values:</p>
<ul>
<li><code>.networkError(NSError)</code> when network failed to send the request, or failed to retrieve a response (eg a timeout).</li>
<li><code>.networkResponse(Int, Data)</code> where <code>Int</code> is a status code and <code>Data</code> is the returned data.</li>
<li><code>.response(HTTPURLResponse, Data)</code> where <code>HTTPURLResponse</code> is the response and <code>Data</code> is the returned data. This one can be used to fully stub a response.</li>
</ul>
<h2 id='request-mapping' class='heading'>Request Mapping</h2>
<p>As we mentioned earlier, the purpose of this library is not really to provide a
coding framework with which to access the network – that’s Alamofire’s job.
Instead, Moya is about a way to frame your thoughts about network access and
provide compile-time checking of well-defined network targets. You’ve already
seen how to map targets into endpoints using the <code>endpointClosure</code> parameter
of the <code><a href="Classes/MoyaProvider.html">MoyaProvider</a></code> initializer. That lets you create an <code><a href="Classes/Endpoint.html">Endpoint</a></code> instance
that Moya will use to reason about the network API call. At some point, that
<code><a href="Classes/Endpoint.html">Endpoint</a></code> must be resolved into an actual <code>URLRequest</code> to give to Alamofire.
That’s what the <code>requestClosure</code> parameter is for.</p>
<p>The <code>requestClosure</code> is an optional, last-minute way to modify the request
that hits the network. It has a default value of <code>MoyaProvider.defaultRequestMapping</code>,
which uses the <code>urlRequest()</code> method of the <code><a href="Classes/Endpoint.html">Endpoint</a></code> instance. This <code>urlRequest()</code>
method throws three possible errors: </p>
<ul>
<li><code>MoyaError.requestMapping(String)</code> when <code>URLRequest</code> could not be created for given path</li>
<li><code>MoyaError.parameterEncoding(Swift.Error)</code> when parameters couldn’t be encoded</li>
<li><code>MoyaError.encodableMapping(Swift.Error)</code> when <code>Encodable</code> object couldn’t be encoded into <code>Data</code></li>
</ul>
<p>This closure receives an <code><a href="Classes/Endpoint.html">Endpoint</a></code> instance and is responsible for invoking a
its argument of <code>RequestResultClosure</code> (shorthand for <code>Result<URLRequest, MoyaError> -> Void</code>) with a request that represents the Endpoint.
It’s here that you’d do your OAuth signing or whatever. Since you may invoke the
closure asynchronously, you can use whatever authentication library you like (<a href="https://github.com/rheinfabrik/Heimdallr.swift">example</a>).
Instead of modifying the request, you could simply log it, instead.</p>
<pre class="highlight swift"><code><span class="k">let</span> <span class="nv">requestClosure</span> <span class="o">=</span> <span class="p">{</span> <span class="p">(</span><span class="nv">endpoint</span><span class="p">:</span> <span class="kt">Endpoint</span><span class="p">,</span> <span class="nv">done</span><span class="p">:</span> <span class="kt">MoyaProvider</span><span class="o">.</span><span class="kt">RequestResultClosure</span><span class="p">)</span> <span class="k">in</span>
<span class="k">do</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">request</span> <span class="o">=</span> <span class="k">try</span> <span class="n">endpoint</span><span class="o">.</span><span class="nf">urlRequest</span><span class="p">()</span>
<span class="c1">// Modify the request however you like.</span>
<span class="nf">done</span><span class="p">(</span><span class="o">.</span><span class="nf">success</span><span class="p">(</span><span class="n">request</span><span class="p">))</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
<span class="nf">done</span><span class="p">(</span><span class="o">.</span><span class="nf">failure</span><span class="p">(</span><span class="kt">MoyaError</span><span class="o">.</span><span class="nf">underlying</span><span class="p">(</span><span class="n">error</span><span class="p">)))</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">provider</span> <span class="o">=</span> <span class="kt">MoyaProvider</span><span class="o"><</span><span class="kt">GitHub</span><span class="o">></span><span class="p">(</span><span class="nv">requestClosure</span><span class="p">:</span> <span class="n">requestClosure</span><span class="p">)</span>
</code></pre>
<p>This <code>requestClosure</code> is useful for modifying properties specific to the <code>URLRequest</code> or providing information to the request that cannot be known until that request is created, like cookies settings. Note that the <code>endpointClosure</code> mentioned above is not intended for this purpose or any request-specific application-level mapping.</p>
<p>This parameter is actually very useful for modifying the request object.
<code>URLRequest</code> has many properties you can customize. Say you want to disable
all cookies on requests:</p>
<pre class="highlight swift"><code><span class="p">{</span> <span class="p">(</span><span class="nv">endpoint</span><span class="p">:</span> <span class="kt">Endpoint</span><span class="p">,</span> <span class="nv">done</span><span class="p">:</span> <span class="kt">MoyaProvider</span><span class="o">.</span><span class="kt">RequestResultClosure</span><span class="p">)</span> <span class="k">in</span>
<span class="k">do</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">request</span><span class="p">:</span> <span class="kt">URLRequest</span> <span class="o">=</span> <span class="k">try</span> <span class="n">endpoint</span><span class="o">.</span><span class="nf">urlRequest</span><span class="p">()</span>
<span class="n">request</span><span class="o">.</span><span class="n">httpShouldHandleCookies</span> <span class="o">=</span> <span class="kc">false</span>
<span class="nf">done</span><span class="p">(</span><span class="o">.</span><span class="nf">success</span><span class="p">(</span><span class="n">request</span><span class="p">))</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
<span class="nf">done</span><span class="p">(</span><span class="o">.</span><span class="nf">failure</span><span class="p">(</span><span class="kt">MoyaError</span><span class="o">.</span><span class="nf">underlying</span><span class="p">(</span><span class="n">error</span><span class="p">)))</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
<p>You could also perform logging of network requests, since this closure is
invoked just before the request is sent to the network.</p>
</section>
</section>
<section id="footer">
<p>© 2020 <a class="link" href="https://github.com/Moya/Moya" target="_blank" rel="external">Ash Furrow</a>. All rights reserved. (Last updated: 2020-02-24)</p>
<p>Generated by <a class="link" href="https://github.com/realm/jazzy" target="_blank" rel="external">jazzy ♪♫ v0.9.6</a>, a <a class="link" href="https://realm.io" target="_blank" rel="external">Realm</a> project.</p>
</section>
</article>
</div>
</body>
</div>
</html>