Skip to content

Commit 193c5e6

Browse files
committed
fix: address senior-dev audit findings (full @PARAM descs, auto-gen Roxy.php docs, drift checks, transport-error wrapping)
1 parent d2fe3e3 commit 193c5e6

18 files changed

Lines changed: 1575 additions & 508 deletions

.gitattributes

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Mirror composer.json `archive.exclude` so `git archive` (and any tarball
2+
# generated outside Packagist's dist API) produces the same lean tree.
3+
4+
/.github export-ignore
5+
/.gitignore export-ignore
6+
/.gitattributes export-ignore
7+
/assets export-ignore
8+
/composer.lock export-ignore
9+
/examples export-ignore
10+
/lefthook.yml export-ignore
11+
/package.json export-ignore
12+
/phpstan.neon export-ignore
13+
/phpunit.xml export-ignore
14+
/pint.json export-ignore
15+
/scripts export-ignore
16+
/specs export-ignore
17+
/tests export-ignore

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
push:
66
branches: [main]
77

8+
permissions:
9+
contents: read
10+
811
jobs:
912
test:
1013
runs-on: ubuntu-latest
@@ -29,7 +32,7 @@ jobs:
2932
if: matrix.php == '8.2'
3033
run: |
3134
node scripts/generate.mjs
32-
git diff --exit-code -- specs/openapi.json src/Generated tests/Generated src/Version.php \
35+
git diff --exit-code -- specs/openapi.json src/Generated src/Roxy.php src/Version.php tests/Generated \
3336
|| { echo 'codegen drift: commit the regenerated files'; exit 1; }
3437
3538
- name: Lint

lefthook.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pre-push:
2222
run: vendor/bin/phpstan analyse --no-progress --memory-limit=1G
2323
priority: 2
2424
codegen-drift:
25-
run: node scripts/generate.mjs && git diff --exit-code -- specs/openapi.json src/Generated src/Version.php || { echo 'codegen drift: commit the regenerated files'; exit 1; }
25+
run: node scripts/generate.mjs && git diff --exit-code -- specs/openapi.json src/Generated src/Roxy.php src/Version.php tests/Generated || { echo 'codegen drift: commit the regenerated files'; exit 1; }
2626
priority: 3
2727
docs-drift:
2828
run: node scripts/sync-docs.mjs && git diff --exit-code -- README.md AGENTS.md || { echo 'docs drift: commit the regenerated README.md/AGENTS.md'; exit 1; }

scripts/generate.mjs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ const SPEC_FILE = path.join(ROOT, 'specs', 'openapi.json');
2626
const OUT_DIR = path.join(ROOT, 'src', 'Generated');
2727
const RESOURCES_DIR = path.join(OUT_DIR, 'Resources');
2828
const REQUESTS_DIR = path.join(OUT_DIR, 'Requests');
29-
const DTO_DIR = path.join(OUT_DIR, 'Dto');
3029
const VERSION_FILE = path.join(ROOT, 'src', 'Version.php');
3130
const ROXY_FILE = path.join(ROOT, 'src', 'Roxy.php');
3231
const TESTS_GENERATED_DIR = path.join(ROOT, 'tests', 'Generated');
@@ -496,8 +495,14 @@ function emitResource(tagName, ops) {
496495
docLines.push(` * ${op.method} ${op.path}`);
497496
docLines.push(' *');
498497
for (const p of params) {
499-
const desc = p.description ? ' ' + p.description.replace(/\s+/g, ' ').slice(0, 90) : '';
500-
docLines.push(` * @param ${p.required ? p.type : p.type + '|null'} \$${p.name}${desc}`);
498+
const flat = p.description ? p.description.replace(/\s+/g, ' ').trim() : '';
499+
if (flat) {
500+
const wrapped = wrapDoc(flat, ' * ');
501+
docLines.push(` * @param ${p.required ? p.type : p.type + '|null'} \$${p.name}`);
502+
docLines.push(...wrapped);
503+
} else {
504+
docLines.push(` * @param ${p.required ? p.type : p.type + '|null'} \$${p.name}`);
505+
}
501506
}
502507
docLines.push(' *');
503508
docLines.push(' * @return array<string, mixed>');
@@ -539,13 +544,15 @@ const BASE_RESOURCE = `${HEADER}
539544
namespace RoxyAPI\\Sdk\\Generated\\Resources;
540545
541546
use RoxyAPI\\Sdk\\RoxyApiException;
547+
use Saloon\\Exceptions\\Request\\FatalRequestException;
542548
use Saloon\\Http\\BaseResource as SaloonBaseResource;
543549
use Saloon\\Http\\Request;
544550
545551
/**
546552
* Shared base for every generated Resource class. Sends a Saloon Request,
547-
* decodes the JSON body, and throws RoxyApiException on 4xx/5xx so callers
548-
* never have to inspect HTTP status codes.
553+
* decodes the JSON body, and throws RoxyApiException for everything callers
554+
* should care about (4xx, 5xx, and transport failures) so consumers never
555+
* have to catch more than one exception type.
549556
*/
550557
abstract class BaseResource extends SaloonBaseResource
551558
{
@@ -554,15 +561,23 @@ abstract class BaseResource extends SaloonBaseResource
554561
*/
555562
protected function callRequest(Request $request): array
556563
{
557-
$response = $this->connector->send($request);
564+
try {
565+
$response = $this->connector->send($request);
566+
} catch (FatalRequestException $e) {
567+
throw RoxyApiException::fromFatal($e);
568+
}
558569
559570
if ($response->failed()) {
560571
throw RoxyApiException::fromResponse($response);
561572
}
562573
563-
$decoded = $response->json();
574+
try {
575+
$decoded = $response->json();
576+
} catch (\\Throwable) {
577+
$decoded = null;
578+
}
564579
565-
return is_array($decoded) ? $decoded : ['data' => $decoded];
580+
return is_array($decoded) ? $decoded : [];
566581
}
567582
}
568583
`;
@@ -632,11 +647,10 @@ it('${namespace} resource sends ${sample.operationId} and parses JSON', function
632647
// Clean generated dirs first so deletions in the spec actually remove files.
633648
await fs.rm(RESOURCES_DIR, { recursive: true, force: true });
634649
await fs.rm(REQUESTS_DIR, { recursive: true, force: true });
635-
await fs.rm(DTO_DIR, { recursive: true, force: true });
650+
await fs.rm(path.join(OUT_DIR, 'Dto'), { recursive: true, force: true });
636651
await fs.rm(TESTS_GENERATED_DIR, { recursive: true, force: true });
637652
await fs.mkdir(RESOURCES_DIR, { recursive: true });
638653
await fs.mkdir(REQUESTS_DIR, { recursive: true });
639-
await fs.mkdir(DTO_DIR, { recursive: true });
640654
await fs.mkdir(TESTS_GENERATED_DIR, { recursive: true });
641655

642656
// BaseResource (committed once, never changes per spec — but emit every time
@@ -658,13 +672,6 @@ for (const op of operations) {
658672
await fs.writeFile(file, emitRequest(op), 'utf8');
659673
}
660674

661-
// Placeholder so the empty Dto dir survives codegen and the package ships consistently.
662-
await fs.writeFile(
663-
path.join(DTO_DIR, '.gitkeep'),
664-
'# Reserved for future typed response DTOs. Methods currently return array<string, mixed>.\n',
665-
'utf8',
666-
);
667-
668675
// Per-tag smoke tests
669676
for (const tag of Object.keys(opsByTag).sort()) {
670677
const file = path.join(TESTS_GENERATED_DIR, tagToClassName(tag) + 'Test.php');

src/Generated/Dto/.gitkeep

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/Generated/Resources/AngelNumbersResource.php

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ class AngelNumbersResource extends BaseResource
2929
*
3030
* GET /angel-numbers/lookup
3131
*
32-
* @param string $number Number sequence to analyze (1-8 digits). Can be any number the user has encountered: clock
33-
* @param string|null $lang Response language (ISO 639-1). Supported: en, tr, de, es, hi, pt, fr, ru. Defaults to en.
32+
* @param string $number
33+
* Number sequence to analyze (1-8 digits). Can be any number the user has encountered: clock
34+
* times (1111), addresses (717), receipts (888), license plates (4444), or any repeating
35+
* pattern.
36+
* @param string|null $lang
37+
* Response language (ISO 639-1). Supported: en, tr, de, es, hi, pt, fr, ru. Defaults to en.
38+
* Languages without translations yet return English.
3439
*
3540
* @return array<string, mixed>
3641
*/
@@ -55,8 +60,12 @@ public function analyzeNumberSequence(
5560
*
5661
* GET /angel-numbers/numbers/{number}
5762
*
58-
* @param string $number Angel number sequence to look up (e.g., "111", "444", "1212", "1234"). Must match an entry
59-
* @param string|null $lang Response language (ISO 639-1). Supported: en, tr, de, es, hi, pt, fr, ru. Defaults to en.
63+
* @param string $number
64+
* Angel number sequence to look up (e.g., "111", "444", "1212", "1234"). Must match an entry
65+
* in the database.
66+
* @param string|null $lang
67+
* Response language (ISO 639-1). Supported: en, tr, de, es, hi, pt, fr, ru. Defaults to en.
68+
* Languages without translations yet return English.
6069
*
6170
* @return array<string, mixed>
6271
*/
@@ -81,9 +90,16 @@ public function getAngelNumber(
8190
*
8291
* POST /angel-numbers/daily
8392
*
84-
* @param string|null $date Date for the reading in YYYY-MM-DD format. Defaults to today (UTC). Useful for viewing pas
85-
* @param string|null $seed Optional seed for reproducible readings. Same seed + same date = same angel number every t
86-
* @param string|null $lang Response language (ISO 639-1). Supported: en, tr, de, es, hi, pt, fr, ru. Defaults to en.
93+
* @param string|null $date
94+
* Date for the reading in YYYY-MM-DD format. Defaults to today (UTC). Useful for viewing past
95+
* daily readings or pre-generating future ones.
96+
* @param string|null $seed
97+
* Optional seed for reproducible readings. Same seed + same date = same angel number every
98+
* time. Pass any unique identifier (userId, email hash, session token). Omit for anonymous
99+
* daily readings.
100+
* @param string|null $lang
101+
* Response language (ISO 639-1). Supported: en, tr, de, es, hi, pt, fr, ru. Defaults to en.
102+
* Languages without translations yet return English.
87103
*
88104
* @return array<string, mixed>
89105
*/
@@ -109,10 +125,17 @@ public function getDailyAngelNumber(
109125
*
110126
* GET /angel-numbers/numbers
111127
*
112-
* @param string|null $lang Response language (ISO 639-1). Supported: en, tr, de, es, hi, pt, fr, ru. Defaults to en.
113-
* @param int|null $limit Maximum items to return per page. Range: 1-50, default 20.
114-
* @param int|null $offset Number of items to skip for pagination. Default 0.
115-
* @param string|null $type Filter results by angel number pattern type. "repeating" returns numbers like 111, 444, 77
128+
* @param string|null $lang
129+
* Response language (ISO 639-1). Supported: en, tr, de, es, hi, pt, fr, ru. Defaults to en.
130+
* Languages without translations yet return English.
131+
* @param int|null $limit
132+
* Maximum items to return per page. Range: 1-50, default 20.
133+
* @param int|null $offset
134+
* Number of items to skip for pagination. Default 0.
135+
* @param string|null $type
136+
* Filter results by angel number pattern type. "repeating" returns numbers like 111, 444,
137+
* 7777. "sequential" returns patterns like 1234. "mirror" returns palindrome patterns like
138+
* 1212. "master" returns 11, 22, 33. "root" returns single digits 0-9.
116139
*
117140
* @return array<string, mixed>
118141
*/

0 commit comments

Comments
 (0)