Completed
Push — master ( a787fa...dc9af5 )
by Vincent
05:35
created

AssertStructure::assertHasValidTopLevelMembers()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 34
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 24
c 0
b 0
f 0
dl 0
loc 34
rs 9.536
cc 3
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace VGirol\JsonApiAssert\Asserts\Structure;
6
7
use PHPUnit\Framework\Assert as PHPUnit;
8
use VGirol\JsonApiAssert\Members;
9
use VGirol\JsonApiAssert\Messages;
10
11
/**
12
 * Assertions relating to the overall structure of the document
13
 */
14
trait AssertStructure
15
{
16
    /**
17
     * Asserts that a json document has valid structure.
18
     *
19
     * @param array     $json
20
     * @param boolean   $strict     If true, unsafe characters are not allowed when checking members name.
21
     * @return void
22
     * @throws \PHPUnit\Framework\ExpectationFailedException
23
     */
24
    public static function assertHasValidStructure($json, bool $strict): void
25
    {
26
        static::assertHasValidTopLevelMembers($json);
27
28
        if (isset($json[Members::DATA])) {
29
            static::assertIsValidPrimaryData($json[Members::DATA], $strict);
30
31
            if (isset($json[Members::INCLUDED])) {
32
                static::assertIsValidIncludedCollection($json[Members::INCLUDED], $json[Members::DATA], $strict);
33
            }
34
        }
35
36
        if (isset($json[Members::ERRORS])) {
37
            static::assertIsValidErrorsObject($json[Members::ERRORS], $strict);
38
        }
39
40
        if (isset($json[Members::META])) {
41
            static::assertIsValidMetaObject($json[Members::META], $strict);
42
        }
43
44
        if (isset($json[Members::JSONAPI])) {
45
            static::assertIsValidJsonapiObject($json[Members::JSONAPI], $strict);
46
        }
47
48
        if (isset($json[Members::LINKS])) {
49
            static::assertIsValidTopLevelLinksMember($json[Members::LINKS], $strict);
50
        }
51
    }
52
53
    /**
54
     * Asserts that a json document has valid top-level structure.
55
     *
56
     * @param array $json
57
     * @return void
58
     * @throws \PHPUnit\Framework\ExpectationFailedException
59
     */
60
    public static function assertHasValidTopLevelMembers($json): void
61
    {
62
        $expected = [
63
            Members::DATA,
64
            Members::ERRORS,
65
            Members::META
66
        ];
67
        static::assertContainsAtLeastOneMember(
68
            $expected,
69
            $json,
70
            \sprintf(Messages::TOP_LEVEL_MEMBERS, implode('", "', $expected))
71
        );
72
73
        PHPUnit::assertFalse(
74
            isset($json[Members::DATA]) && isset($json[Members::ERRORS]),
75
            Messages::TOP_LEVEL_DATA_AND_ERROR
76
        );
77
78
        $allowed = [
79
            Members::DATA,
80
            Members::ERRORS,
81
            Members::META,
82
            Members::JSONAPI,
83
            Members::LINKS,
84
            Members::INCLUDED
85
        ];
86
        static::assertContainsOnlyAllowedMembers(
87
            $allowed,
88
            $json
89
        );
90
91
        PHPUnit::assertFALSE(
92
            !isset($json[Members::DATA]) && isset($json[Members::INCLUDED]),
93
            Messages::TOP_LEVEL_DATA_AND_INCLUDED
94
        );
95
    }
96
97
    /**
98
     * Asserts that a json fragment is a valid top-level links member.
99
     *
100
     * @param array     $json
101
     * @param boolean   $strict     If true, unsafe characters are not allowed when checking members name.
102
     * @return void
103
     * @throws \PHPUnit\Framework\ExpectationFailedException
104
     */
105
    public static function assertIsValidTopLevelLinksMember($json, bool $strict): void
106
    {
107
        $allowed = [
108
            Members::SELF,
109
            Members::RELATED,
110
            Members::FIRST,
111
            Members::LAST,
112
            Members::NEXT,
113
            Members::PREV
114
        ];
115
        static::assertIsValidLinksObject($json, $allowed, $strict);
116
    }
117
118
    /**
119
     * Asserts a json fragment is a valid primary data object.
120
     *
121
     * @param array|null    $json
122
     * @param boolean       $strict     If true, unsafe characters are not allowed when checking members name.
123
     * @return void
124
     * @throws \PHPUnit\Framework\ExpectationFailedException
125
     */
126
    public static function assertIsValidPrimaryData($json, bool $strict): void
127
    {
128
        if (\is_null($json)) {
129
            $json = [];
130
        }
131
132
        PHPUnit::assertIsArray(
133
            $json,
134
            Messages::PRIMARY_DATA_NOT_ARRAY
135
        );
136
137
        if (empty($json)) {
138
            return;
139
        }
140
141
        if (static::isArrayOfObjects($json)) {
142
            // Resource collection (Resource Objects or Resource Identifier Objects)
143
            static::assertIsValidPrimaryCollection($json, true, $strict);
144
145
            return;
146
        }
147
148
        // Single Resource (Resource Object or Resource Identifier Object)
149
        static::assertIsValidPrimarySingle($json, $strict);
150
    }
151
152
    /**
153
     * Asserts that a collection of resource object is valid.
154
     *
155
     * @param array     $list
156
     * @param boolean   $checkType      If true, asserts that all resources of the collection are of same type.
157
     * @param boolean   $strict         If true, excludes not safe characters when checking members name
158
     * @return void
159
     * @throws \PHPUnit\Framework\ExpectationFailedException
160
     */
161
    private static function assertIsValidPrimaryCollection($list, bool $checkType, bool $strict): void
162
    {
163
        $isResourceObject = null;
164
        foreach ($list as $index => $resource) {
165
            if ($checkType) {
166
                // Assert that all resources of the collection are of same type.
167
                if ($index == 0) {
168
                    $isResourceObject = static::dataIsResourceObject($resource);
169
                    continue;
170
                }
171
172
                PHPUnit::assertEquals(
173
                    $isResourceObject,
174
                    static::dataIsResourceObject($resource),
175
                    Messages::PRIMARY_DATA_SAME_TYPE
176
                );
177
            }
178
179
            // Check the resource
180
            static::assertIsValidPrimarySingle($resource, $strict);
181
        }
182
    }
183
184
    /**
185
     * Assert that a single resource object is valid.
186
     *
187
     * @param array     $resource
188
     * @param boolean   $strict     If true, excludes not safe characters when checking members name
189
     * @return void
190
     * @throws \PHPUnit\Framework\ExpectationFailedException
191
     */
192
    private static function assertIsValidPrimarySingle($resource, bool $strict): void
193
    {
194
        if (static::dataIsResourceObject($resource)) {
195
            static::assertIsValidResourceObject($resource, $strict);
196
197
            return;
198
        }
199
200
        static::assertIsValidResourceIdentifierObject($resource, $strict);
201
    }
202
203
    /**
204
     * Asserts that a collection of included resources is valid.
205
     *
206
     * @param array     $included   The included top-level member of the json document.
207
     * @param array     $data       The primary data of the json document.
208
     * @param boolean   $strict     If true, unsafe characters are not allowed when checking members name.
209
     * @return void
210
     * @throws \PHPUnit\Framework\ExpectationFailedException
211
     */
212
    public static function assertIsValidIncludedCollection($included, $data, bool $strict): void
213
    {
214
        static::assertIsValidResourceObjectCollection($included, $strict);
215
216
        $resIdentifiers = array_merge(
217
            static::getAllResourceIdentifierObjects($data),
218
            static::getAllResourceIdentifierObjects($included)
219
        );
220
221
        $present = [];
222
        foreach ($included as $inc) {
223
            PHPUnit::assertTrue(
224
                self::existsInArray($inc, $resIdentifiers),
225
                Messages::INCLUDED_RESOURCE_NOT_LINKED
226
            );
227
228
            if (!isset($present[$inc[Members::TYPE]])) {
229
                $present[$inc[Members::TYPE]] = [];
230
            }
231
            PHPUnit::assertNotContains(
232
                $inc[Members::ID],
233
                $present[$inc[Members::TYPE]],
234
                Messages::COMPOUND_DOCUMENT_ONLY_ONE_RESOURCE
235
            );
236
            array_push($present[$inc[Members::TYPE]], $inc[Members::ID]);
237
        }
238
    }
239
240
    /**
241
     * Checks if a given json fragment is a resource object.
242
     *
243
     * @param array $resource
244
     * @return bool
245
     */
246
    private static function dataIsResourceObject($resource): bool
247
    {
248
        $expected = [
249
            Members::ATTRIBUTES,
250
            Members::RELATIONSHIPS,
251
            Members::LINKS
252
        ];
253
254
        return static::containsAtLeastOneMember($expected, $resource);
255
    }
256
257
    /**
258
     * Get all the resource identifier objects (resource linkage) presents in a collection of resource.
259
     *
260
     * @param array $data
261
     * @return array
262
     */
263
    private static function getAllResourceIdentifierObjects($data): array
264
    {
265
        $arr = [];
266
        if (empty($data)) {
267
            return $arr;
268
        }
269
        if (!static::isArrayOfObjects($data)) {
270
            $data = [$data];
271
        }
272
        foreach ($data as $obj) {
273
            if (!isset($obj[Members::RELATIONSHIPS])) {
274
                continue;
275
            }
276
            foreach ($obj[Members::RELATIONSHIPS] as $relationship) {
277
                if (!isset($relationship[Members::DATA])) {
278
                    continue;
279
                }
280
                $arr = array_merge(
281
                    $arr,
282
                    static::isArrayOfObjects($relationship[Members::DATA]) ?
283
                        $relationship[Members::DATA] : [$relationship[Members::DATA]]
284
                );
285
            }
286
        }
287
288
        return $arr;
289
    }
290
291
    /**
292
     * Checks if a resource is present in a given array.
293
     *
294
     * @param array $needle
295
     * @param array $arr
296
     * @return bool
297
     */
298
    private static function existsInArray($needle, $arr): bool
299
    {
300
        foreach ($arr as $resIdentifier) {
301
            $test = $resIdentifier[Members::TYPE] === $needle[Members::TYPE]
302
                && $resIdentifier[Members::ID] === $needle[Members::ID];
303
            if ($test) {
304
                return true;
305
            }
306
        }
307
308
        return false;
309
    }
310
}
311