Skip to content

Commit 4fcdc49

Browse files
Merge pull request #227 from nextcloud/fix/merge-specs/objects
fix(merge-specs): Use objects instead of associative arrays to fix empty JSON objects
2 parents a046a00 + b71a6e5 commit 4fcdc49

File tree

2 files changed

+75
-52
lines changed

2 files changed

+75
-52
lines changed

.github/workflows/test-repositories.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,25 @@ jobs:
7676
if: matrix.repositories == 'nextcloud/server'
7777
working-directory: temp-repository/
7878
run: |
79+
specs=()
7980
for path in core apps/*; do
8081
if [ ! -f "$path/.noopenapi" ]; then
8182
../bin/generate-spec "$path" "$path/openapi.json" || exit 1
83+
if [[ "$(basename "$path")" != "core" ]]; then
84+
if [ -f "$path/openapi-full.json" ]; then
85+
specs+=("$path/openapi-full.json")
86+
else
87+
specs+=("$path/openapi.json")
88+
fi
89+
fi
8290
fi
8391
done
8492
93+
../bin/merge-specs \
94+
--core core/openapi-full.json \
95+
--merged openapi.json \
96+
"${specs[@]}"
97+
8598
- name: Show changes of the API for assistance
8699
working-directory: temp-repository/
87100
run: |

merge-specs.php

Lines changed: 62 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -84,100 +84,110 @@
8484
$data
8585
['paths']
8686
['/ocs/v2.php/cloud/capabilities']
87-
['get']
88-
['responses']
89-
['200']
90-
['content']
91-
['application/json']
92-
['schema']
93-
['properties']
94-
['ocs']
95-
['properties']
96-
['data']
97-
['properties']
98-
['capabilities']
99-
['anyOf']
100-
= array_map(fn (string $capability): array => ['$ref' => '#/components/schemas/' . $capability], $capabilities);
101-
102-
function loadSpec(string $path): array {
103-
return rewriteRefs(json_decode(file_get_contents($path), true));
87+
->get
88+
->responses
89+
->{'200'}
90+
->content
91+
->{'application/json'}
92+
->schema
93+
->properties
94+
->ocs
95+
->properties
96+
->data
97+
->properties
98+
->capabilities
99+
->anyOf
100+
= array_map(static fn (string $capability): array => ['$ref' => '#/components/schemas/' . $capability], $capabilities);
101+
102+
function loadSpec(string $path): object {
103+
return rewriteRefs(json_decode(file_get_contents($path), false, 512, JSON_THROW_ON_ERROR));
104104
}
105105

106-
function getAppID(array $spec): string {
107-
return explode('-', $spec['info']['title'])[0];
106+
function getAppID(object $spec): string {
107+
return explode('-', $spec->info->title)[0];
108108
}
109109

110-
function rewriteRefs(array $spec): array {
110+
function rewriteRefs(object $spec): object {
111111
$readableAppID = Helpers::generateReadableAppID(getAppID($spec));
112-
array_walk_recursive($spec, function (mixed &$item, string $key) use ($readableAppID): void {
112+
object_walk_recursive($spec, static function (mixed &$item, string $key) use ($readableAppID): void {
113113
if ($key === '$ref' && $item !== '#/components/schemas/OCSMeta') {
114114
$item = str_replace('#/components/schemas/', '#/components/schemas/' . $readableAppID, $item);
115115
}
116116
});
117117
return $spec;
118118
}
119119

120-
function collectCapabilities(array $spec): array {
120+
function object_walk_recursive(object $object, \Closure $callback): void {
121+
foreach (array_keys(get_object_vars($object)) as $key) {
122+
$callback($object->{$key}, $key);
123+
if (is_object($object->{$key})) {
124+
object_walk_recursive($object->{$key}, $callback);
125+
}
126+
if (is_array($object->{$key})) {
127+
foreach ($object->{$key} as $item) {
128+
if (is_object($item)) {
129+
object_walk_recursive($item, $callback);
130+
}
131+
}
132+
}
133+
}
134+
}
135+
136+
function collectCapabilities(object $spec): array {
121137
$capabilities = [];
122138
$readableAppID = Helpers::generateReadableAppID(getAppID($spec));
123-
foreach (array_keys($spec['components']['schemas']) as $name) {
124-
if ($name == 'Capabilities' || $name == 'PublicCapabilities') {
139+
foreach (array_keys(get_object_vars($spec->components->schemas)) as $name) {
140+
if ($name === 'Capabilities' || $name === 'PublicCapabilities') {
125141
$capabilities[] = $readableAppID . $name;
126142
}
127143
}
128144

129145
return $capabilities;
130146
}
131147

132-
function rewriteSchemaNames(array $spec): array {
133-
$schemas = $spec['components']['schemas'];
148+
function rewriteSchemaNames(object $spec): array {
149+
$schemas = get_object_vars($spec->components->schemas);
134150
$readableAppID = Helpers::generateReadableAppID(getAppID($spec));
135151
return array_combine(
136-
array_map(fn (string $key): string => $key === 'OCSMeta' ? $key : $readableAppID . $key, array_keys($schemas)),
152+
array_map(static fn (string $key): string => $key === 'OCSMeta' ? $key : $readableAppID . $key, array_keys($schemas)),
137153
array_values($schemas),
138154
);
139155
}
140156

141-
function rewriteTags(array $spec): array {
142-
return array_map(function (array $tag) use ($spec) {
143-
$tag['name'] = getAppID($spec) . '/' . $tag['name'];
157+
function rewriteTags(object $spec): array {
158+
return array_map(static function (object $tag) use ($spec) {
159+
$tag->name = getAppID($spec) . '/' . $tag->name;
144160
return $tag;
145-
}, $spec['tags']);
161+
}, $spec->tags);
146162
}
147163

148-
function rewriteOperations(array $spec): array {
164+
function rewriteOperations(object $spec): array {
149165
global $firstStatusCode;
150166

151-
foreach (array_keys($spec['paths']) as $path) {
152-
foreach (array_keys($spec['paths'][$path]) as $method) {
167+
foreach (array_keys(get_object_vars($spec->paths)) as $path) {
168+
foreach (array_keys(get_object_vars($spec->paths->{$path})) as $method) {
153169
if (!in_array($method, ['delete', 'get', 'post', 'put', 'patch', 'options'])) {
154170
continue;
155171
}
156-
$operation = &$spec['paths'][$path][$method];
157-
if (array_key_exists('operationId', $operation)) {
158-
$operation['operationId'] = getAppID($spec) . '-' . $operation['operationId'];
172+
$operation = &$spec->paths->{$path}->{$method};
173+
if (property_exists($operation, 'operationId')) {
174+
$operation->operationId = getAppID($spec) . '-' . $operation->operationId;
159175
}
160-
if (array_key_exists('tags', $operation)) {
161-
$operation['tags'] = array_map(fn (string $tag): string => getAppID($spec) . '/' . $tag, $operation['tags']);
176+
if (property_exists($operation, 'tags')) {
177+
$operation->tags = array_map(static fn (string $tag): string => getAppID($spec) . '/' . $tag, $operation->tags);
162178
} else {
163-
$operation['tags'] = [getAppID($spec)];
179+
$operation->tags = [getAppID($spec)];
164180
}
165-
if ($firstStatusCode && array_key_exists('responses', $operation)) {
181+
if ($firstStatusCode && property_exists($operation, 'responses')) {
166182
/** @var string $value */
167-
$value = array_key_first($operation['responses']);
168-
$operation['responses'] = [$value => $operation['responses'][$value]];
169-
}
170-
if (array_key_exists('security', $operation)) {
171-
$counter = count($operation['security']);
172-
for ($i = 0; $i < $counter; $i++) {
173-
if (count($operation['security'][$i]) == 0) {
174-
$operation['security'][$i] = new stdClass(); // When reading {} will be converted to [], so we have to fix it
175-
}
176-
}
183+
$value = array_key_first(get_object_vars($operation->responses));
184+
$response = $operation->responses->{$value};
185+
$operation->responses = new stdClass();
186+
$operation->responses->{$value} = $response;
177187
}
178188
}
179189
}
180-
return $spec['paths'];
190+
return get_object_vars($spec->paths);
181191
}
182192

183193
file_put_contents($mergedSpecPath, json_encode($data, Helpers::jsonFlags()) . "\n");

0 commit comments

Comments
 (0)