Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 43 additions & 11 deletions src/SDK/Language/Deno.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,16 +175,48 @@ public function getParamExample(array $param): string
};
}

return match ($type) {
self::TYPE_ARRAY, self::TYPE_INTEGER, self::TYPE_NUMBER => $example,
self::TYPE_FILE => 'InputFile.fromPath(\'/path/to/file.png\', \'file.png\')',
self::TYPE_BOOLEAN => ($example) ? 'true' : 'false',
self::TYPE_OBJECT => ($example === '{}')
? '{}'
: (($formatted = json_encode(json_decode($example, true), JSON_PRETTY_PRINT))
? preg_replace('/\n/', "\n ", $formatted)
: $example),
self::TYPE_STRING => "'{$example}'",
};
switch ($type) {
case self::TYPE_ARRAY:
if (is_array($example)) {
return json_encode($example, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}

$decoded = json_decode($example, true);
if (null !== $decoded && is_array($decoded)) {
return json_encode($decoded, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}

// Fallback: normalize permission-like tokens inside array elements
$fixed = preg_replace_callback('/([a-zA-Z_][a-zA-Z0-9_]*)\(\s*"([^"\)]*)"\s*\)/', function ($m) {
return $m[1] . "('" . $m[2] . "')";
}, $example);

return $fixed ?? $example;

Comment on lines +189 to +195
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Escape single quotes in permission-token fallback.

Ensure read("O'Connor") becomes read('O'Connor'), not invalid JS.

Apply:

-                $fixed = preg_replace_callback('/([a-zA-Z_][a-zA-Z0-9_]*)\(\s*"([^"\)]*)"\s*\)/', function ($m) {
-                    return $m[1] . "('" . $m[2] . "')";
-                }, $example);
+                $fixed = preg_replace_callback('/([a-zA-Z_][a-zA-Z0-9_]*)\(\s*"([^"\)]*)"\s*\)/', function ($m) {
+                    $inner = str_replace("'", "\\'", $m[2]);
+                    return $m[1] . "('{$inner}')";
+                }, (string) $example);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/SDK/Language/Deno.php around lines 189 to 195, the preg_replace_callback
builds a single-quoted token string but doesn’t escape inner single quotes, so
input like read("O'Connor") becomes invalid JS; update the callback to escape
single quotes in $m[2] before embedding (e.g. replace ' with \' via str_replace
or similar) so the returned string is $m[1] . "('" . <escaped $m[2]> . "')";
keep the rest of the logic unchanged.

case self::TYPE_INTEGER:
case self::TYPE_NUMBER:
return $example;

case self::TYPE_FILE:
return 'InputFile.fromPath(\'/path/to/file.png\', \'file.png\')';

case self::TYPE_BOOLEAN:
return ($example) ? 'true' : 'false';

case self::TYPE_OBJECT:
if ($example === '{}') {
return '{}';
}
$decodedObj = json_decode($example, true);
if (null !== $decodedObj && is_array($decodedObj)) {
$formatted = json_encode($decodedObj, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return preg_replace('/\n/', "\n ", $formatted);
}
return $example;

case self::TYPE_STRING:
default:
return json_encode((string) $example, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
}
}
55 changes: 44 additions & 11 deletions src/SDK/Language/ReactNative.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,16 +203,49 @@ public function getParamExample(array $param): string
};
}

return match ($type) {
self::TYPE_ARRAY, self::TYPE_FILE, self::TYPE_INTEGER, self::TYPE_NUMBER => $example,
self::TYPE_BOOLEAN => ($example) ? 'true' : 'false',
self::TYPE_OBJECT => ($example === '{}')
? '{}'
: (($formatted = json_encode(json_decode($example, true), JSON_PRETTY_PRINT))
? preg_replace('/\n/', "\n ", $formatted)
: $example),
self::TYPE_STRING => "'{$example}'",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we only needed change in type string, any reasons for changing all the others?

};
switch ($type) {
case self::TYPE_ARRAY:
if (is_array($example)) {
return json_encode($example, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}

$decoded = json_decode($example, true);
if (null !== $decoded && is_array($decoded)) {
return json_encode($decoded, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}

// Fallback: normalize permission-like tokens inside array elements
$fixed = preg_replace_callback('/([a-zA-Z_][a-zA-Z0-9_]*)\(\s*"([^"\)]*)"\s*\)/', function ($m) {
return $m[1] . "('" . $m[2] . "')";
}, $example);

return $fixed ?? $example;

Comment on lines +217 to +223
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Escape single quotes in permission-token fallback.

Values like read("O'Connor") will render invalid JS: read('O'Connor'). Escape inner single quotes and cast $example to string.

Apply:

-                $fixed = preg_replace_callback('/([a-zA-Z_][a-zA-Z0-9_]*)\(\s*"([^"\)]*)"\s*\)/', function ($m) {
-                    return $m[1] . "('" . $m[2] . "')";
-                }, $example);
+                $fixed = preg_replace_callback('/([a-zA-Z_][a-zA-Z0-9_]*)\(\s*"([^"\)]*)"\s*\)/', function ($m) {
+                    $inner = str_replace("'", "\\'", $m[2]);
+                    return $m[1] . "('{$inner}')";
+                }, (string) $example);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Fallback: normalize permission-like tokens inside array elements
$fixed = preg_replace_callback('/([a-zA-Z_][a-zA-Z0-9_]*)\(\s*"([^"\)]*)"\s*\)/', function ($m) {
return $m[1] . "('" . $m[2] . "')";
}, $example);
return $fixed ?? $example;
// Fallback: normalize permission-like tokens inside array elements
$fixed = preg_replace_callback('/([a-zA-Z_][a-zA-Z0-9_]*)\(\s*"([^"\)]*)"\s*\)/', function ($m) {
$inner = str_replace("'", "\\'", $m[2]);
return $m[1] . "('{$inner}')";
}, (string) $example);
return $fixed ?? $example;
🤖 Prompt for AI Agents
In src/SDK/Language/ReactNative.php around lines 217-223, the
preg_replace_callback fallback doesn't escape single quotes and doesn't ensure
$example is a string; update the callback to escape inner single quotes in the
permission token (e.g. replace ' with \') when building the replacement, cast
$example to string when passing it to preg_replace_callback (or at least in the
return), and return the escaped result or (string)$example if no replacement
occurred.

case self::TYPE_INTEGER:
case self::TYPE_NUMBER:
return $example;

case self::TYPE_FILE:
return 'InputFile.fromPath(\'/path/to/file\', \'filename\')';

case self::TYPE_BOOLEAN:
return ($example) ? 'true' : 'false';

case self::TYPE_OBJECT:
if ($example === '{}') {
return '{}';
}
$decodedObj = json_decode($example, true);
if (null !== $decodedObj && is_array($decodedObj)) {
$formatted = json_encode($decodedObj, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return preg_replace('/\n/', "\n ", $formatted);
}
return $example;

case self::TYPE_STRING:
default:
return json_encode((string) $example, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
}

public function getReturn(array $method, array $spec): string
Expand Down Expand Up @@ -241,7 +274,7 @@ public function getReturn(array $method, array $spec): string
$this->populateGenerics($method['responseModel'], $spec, $models);

$models = array_unique($models);
$models = array_filter($models, fn ($model) => $model != $this->toPascalCase($method['responseModel']));
$models = array_filter($models, fn($model) => $model != $this->toPascalCase($method['responseModel']));

if (!empty($models)) {
$ret .= '<' . implode(', ', $models) . '>';
Expand Down
63 changes: 49 additions & 14 deletions src/SDK/Language/Web.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,17 +144,52 @@ public function getParamExample(array $param): string
};
}

return match ($type) {
self::TYPE_ARRAY, self::TYPE_INTEGER, self::TYPE_NUMBER => $example,
self::TYPE_FILE => 'document.getElementById(\'uploader\').files[0]',
self::TYPE_BOOLEAN => ($example) ? 'true' : 'false',
self::TYPE_OBJECT => ($example === '{}')
? '{}'
: (($formatted = json_encode(json_decode($example, true), JSON_PRETTY_PRINT))
? preg_replace('/\n/', "\n ", $formatted)
: $example),
self::TYPE_STRING => "'{$example}'",
};
switch ($type) {
case self::TYPE_ARRAY:
// Try to decode JSON array and re-encode to ensure valid JS array literal
if (is_array($example)) {
return json_encode($example, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}

$decoded = json_decode($example, true);
if (null !== $decoded && is_array($decoded)) {
return json_encode($decoded, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}

// Fallback: handle permission-like strings that contain unescaped inner quotes,
// e.g. ["read("any")"] -> ["read('any')"] by converting inner double-quotes to single-quotes
$fixed = preg_replace_callback('/([a-zA-Z_]+)\(\"([^\"]+)\"\)/', function ($m) {
return $m[1] . "('" . $m[2] . "')";
}, $example);

return $fixed ?? $example;

case self::TYPE_INTEGER:
case self::TYPE_NUMBER:
return $example;

case self::TYPE_FILE:
return 'document.getElementById(\'uploader\').files[0]';

case self::TYPE_BOOLEAN:
return ($example) ? 'true' : 'false';

case self::TYPE_OBJECT:
if ($example === '{}') {
return '{}';
}
$decodedObj = json_decode($example, true);
if (null !== $decodedObj && is_array($decodedObj)) {
$formatted = json_encode($decodedObj, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return preg_replace('/\n/', "\n ", $formatted);
}
return $example;

case self::TYPE_STRING:
default:
// Use json_encode to produce properly quoted and escaped JS string literal
return json_encode((string) $example, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
Comment on lines +147 to +192
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

❓ Verification inconclusive

Robustness gap: TYPE_OBJECT may throw TypeError when example is array/object.

json_decode expects string; passing array/object can error in PHP 8+. Handle non-string examples directly and tighten decoding.

Proposed patch:

         switch ($type) {
             case self::TYPE_ARRAY:
                 // Try to decode JSON array and re-encode to ensure valid JS array literal
                 if (is_array($example)) {
                     return json_encode($example, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
                 }
@@
                 return ($example) ? 'true' : 'false';
 
             case self::TYPE_OBJECT:
-                if ($example === '{}') {
-                    return '{}';
-                }
-                $decodedObj = json_decode($example, true);
-                if (null !== $decodedObj && is_array($decodedObj)) {
-                    $formatted = json_encode($decodedObj, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
-                    return preg_replace('/\n/', "\n    ", $formatted);
-                }
-                return $example;
+                // Accept pre-parsed examples
+                if (is_array($example) || is_object($example)) {
+                    $formatted = json_encode($example, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+                    return preg_replace('/\n/', "\n    ", $formatted);
+                }
+                if ($example === '{}' || \trim((string) $example) === '{}') {
+                    return '{}';
+                }
+                $decodedObj = json_decode((string) $example, true);
+                if (json_last_error() === JSON_ERROR_NONE && is_array($decodedObj)) {
+                    $formatted = json_encode($decodedObj, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+                    return preg_replace('/\n/', "\n    ", $formatted);
+                }
+                return (string) $example;
 
             case self::TYPE_STRING:
             default:
                 // Use json_encode to produce properly quoted and escaped JS string literal
                 return json_encode((string) $example, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
         }

Also, optional: after fixing inner quotes in TYPE_ARRAY, consider trimming and returning the normalized string (current approach is fine for JS literals).


Handle non-string $example in TYPE_OBJECT to prevent TypeError: In the TYPE_OBJECT case of src/SDK/Language/Web.php, first check is_array($example) || is_object($example) and format with json_encode, then cast $example to string for json_decode and verify json_last_error().

}

public function getReadOnlyProperties(array $parameter, string $responseModel, array $spec = []): array
Expand Down Expand Up @@ -270,7 +305,7 @@ public function getGenerics(string $model, array $spec, bool $skipFirst = false)
}

$generics = array_unique($generics);
$generics = array_map(fn ($type) => "{$type} extends Models.{$type} = Models.Default{$type}", $generics);
$generics = array_map(fn($type) => "{$type} extends Models.{$type} = Models.Default{$type}", $generics);

return '<' . implode(', ', $generics) . '>';
}
Expand Down Expand Up @@ -303,7 +338,7 @@ public function getReturn(array $method, array $spec): string
$this->populateGenerics($method['responseModel'], $spec, $models);

$models = array_unique($models);
$models = array_filter($models, fn ($model) => $model != $this->toPascalCase($method['responseModel']));
$models = array_filter($models, fn($model) => $model != $this->toPascalCase($method['responseModel']));

if (!empty($models)) {
$ret .= '<' . implode(', ', $models) . '>';
Expand All @@ -323,7 +358,7 @@ public function getSubSchema(array $property, array $spec, string $methodName =
$generics = [];
$this->populateGenerics($property['sub_schema'], $spec, $generics);

$generics = array_filter($generics, fn ($model) => $model != $this->toPascalCase($property['sub_schema']));
$generics = array_filter($generics, fn($model) => $model != $this->toPascalCase($property['sub_schema']));

$ret .= $this->toPascalCase($property['sub_schema']);
if (!empty($generics)) {
Expand Down
10 changes: 5 additions & 5 deletions templates/deno/src/permission.ts.twig
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
export class Permission {

static read = (role: string): string => {
return `read("${role}")`
return `read('${role}')`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, not related

}

static write = (role: string): string => {
return `write("${role}")`
return `write('${role}')`
}

static create = (role: string): string => {
return `create("${role}")`
return `create('${role}')`
}

static update = (role: string): string => {
return `update("${role}")`
return `update('${role}')`
}

static delete = (role: string): string => {
return `delete("${role}")`
return `delete('${role}')`
}
}
10 changes: 5 additions & 5 deletions templates/react-native/src/permission.ts.twig
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
export class Permission {

static read = (role: string): string => {
return `read("${role}")`
return `read('${role}')`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, not related

}

static write = (role: string): string => {
return `write("${role}")`
return `write('${role}')`
}

static create = (role: string): string => {
return `create("${role}")`
return `create('${role}')`
}

static update = (role: string): string => {
return `update("${role}")`
return `update('${role}')`
}

static delete = (role: string): string => {
return `delete("${role}")`
return `delete('${role}')`
}
}
10 changes: 5 additions & 5 deletions templates/web/src/permission.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class Permission {
* @returns {string}
*/
static read = (role: string): string => {
return `read("${role}")`;
return `read('${role}')`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these shoudnt matter no? not related to docs at all

}

/**
Expand All @@ -22,7 +22,7 @@ export class Permission {
* @returns {string}
*/
static write = (role: string): string => {
return `write("${role}")`;
return `write('${role}')`;
}

/**
Expand All @@ -32,7 +32,7 @@ export class Permission {
* @returns {string}
*/
static create = (role: string): string => {
return `create("${role}")`;
return `create('${role}')`;
}

/**
Expand All @@ -42,7 +42,7 @@ export class Permission {
* @returns {string}
*/
static update = (role: string): string => {
return `update("${role}")`;
return `update('${role}')`;
}

/**
Expand All @@ -52,6 +52,6 @@ export class Permission {
* @returns {string}
*/
static delete = (role: string): string => {
return `delete("${role}")`;
return `delete('${role}')`;
}
}
Loading