@use-the-fork/spindle 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/Assets/Blueprints/Catalog.js +19 -9
- package/build/Assets/Blueprints/agents/sdd-implementer.agent.md +6 -6
- package/build/Assets/Blueprints/agents/sdd-orchestrator.agent.md +4 -7
- package/build/Assets/Blueprints/skills/author-agent/SKILL.md +9 -8
- package/build/Assets/Blueprints/skills/author-instruction/SKILL.md +9 -8
- package/build/Assets/Blueprints/skills/author-skill/SKILL.md +9 -8
- package/build/Assets/Blueprints/skills/grill/SKILL.md +3 -3
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/SKILL.md +16 -12
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/assets/implementer-prompt.md +2 -2
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/corrective-rounds.md +1 -1
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/example-workflow.md +14 -12
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/full-spec.md +43 -23
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/review-inputs.md +7 -7
- package/build/Assets/Blueprints/skills/int-testing-agent-guidance/SKILL.md +2 -2
- package/build/Assets/Blueprints/skills/int-writing-skills/SKILL.md +2 -2
- package/build/Assets/Blueprints/skills/int-writing-skills/references/testing-skills.md +2 -2
- package/build/Assets/Blueprints/skills/{asd-ste100 → ref-asd-ste100}/SKILL.md +1 -1
- package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/SKILL.md +38 -0
- package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/concurrency-and-pools.md +73 -0
- package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/connectors.md +99 -0
- package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/data-transfer-objects.md +104 -0
- package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/handling-failures.md +95 -0
- package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/requests.md +111 -0
- package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/responses.md +56 -0
- package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/sending-requests.md +42 -0
- package/build/Assets/Blueprints/skills/ref-php-laravel-saloon-api-integrations/references/testing.md +119 -0
- package/build/Assets/Blueprints/skills/{author-design → sdd-author-design}/SKILL.md +12 -4
- package/build/Assets/Blueprints/skills/{grill-design → sdd-grill-design}/SKILL.md +1 -1
- package/build/Assets/Blueprints/skills/{implement-spec → sdd-implement-spec}/SKILL.md +6 -8
- package/build/Assets/Blueprints/skills/{to-spec → sdd-to-spec}/SKILL.md +3 -3
- package/build/Assets/Blueprints/skills/{to-tickets → sdd-to-tickets}/SKILL.md +3 -3
- package/build/Assets/Blueprints/skills/{setup-project → spindle-project-setup}/SKILL.md +4 -4
- package/build/Assets/Blueprints/skills/{update-tooling → spindle-tooling-update}/SKILL.md +5 -5
- package/build/Assets/Templates/project/copilot-instructions.md +3 -9
- package/build/Assets/Templates/project/ticket-tracker.md +14 -6
- package/build/BlueprintManagement/Commands/Prompts.js +1 -1
- package/build/SubagentDrivenDevelopment/Commands/ChecksRecordCommand.js +27 -5
- package/build/SubagentDrivenDevelopment/Commands/ChecksRequireCommand.js +31 -7
- package/build/SubagentDrivenDevelopment/Commands/CorrectCommand.js +0 -1
- package/build/SubagentDrivenDevelopment/Commands/FinishCommand.js +2 -1
- package/build/SubagentDrivenDevelopment/Commands/ImplementCommand.js +2 -0
- package/build/SubagentDrivenDevelopment/Commands/{ClaimCommand.js → IntegrateCommand.js} +8 -7
- package/build/SubagentDrivenDevelopment/Commands/ReviewCommand.js +9 -9
- package/build/SubagentDrivenDevelopment/Commands/StartCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/StatusCommand.js +5 -0
- package/build/SubagentDrivenDevelopment/Commands/groups.js +3 -2
- package/build/SubagentDrivenDevelopment/Evidence/GitEvidence.js +188 -37
- package/build/SubagentDrivenDevelopment/Models/Workflow.js +0 -2
- package/build/SubagentDrivenDevelopment/Persistence/AssignmentSchema.js +4 -0
- package/build/SubagentDrivenDevelopment/Persistence/CheckSchema.js +4 -13
- package/build/SubagentDrivenDevelopment/Persistence/LedgerRepository.js +66 -5
- package/build/SubagentDrivenDevelopment/Persistence/ProgressSchema.js +54 -4
- package/build/SubagentDrivenDevelopment/Persistence/ReviewSchema.js +4 -0
- package/build/SubagentDrivenDevelopment/Policies/AssignmentPolicy.js +0 -3
- package/build/SubagentDrivenDevelopment/Policies/CheckPolicy.js +6 -3
- package/build/SubagentDrivenDevelopment/Policies/ReadinessPolicy.js +16 -6
- package/build/SubagentDrivenDevelopment/Policies/ReviewPolicy.js +16 -2
- package/build/SubagentDrivenDevelopment/Policies/WorkflowPolicy.js +8 -8
- package/build/SubagentDrivenDevelopment/Services/AssignmentService.js +73 -51
- package/build/SubagentDrivenDevelopment/Services/ChecksService.js +376 -21
- package/build/SubagentDrivenDevelopment/Services/CorrectionService.js +83 -14
- package/build/SubagentDrivenDevelopment/Services/DeliveryCorrectionService.js +3 -7
- package/build/SubagentDrivenDevelopment/Services/DeliveryService.js +43 -38
- package/build/SubagentDrivenDevelopment/Services/FeatureCreationService.js +1 -1
- package/build/SubagentDrivenDevelopment/Services/FeatureFinalizationService.js +1202 -0
- package/build/SubagentDrivenDevelopment/Services/FeatureInspectionService.js +41 -49
- package/build/SubagentDrivenDevelopment/Services/FeatureWorktreeService.js +544 -0
- package/build/SubagentDrivenDevelopment/Services/ReadinessService.js +2 -2
- package/build/SubagentDrivenDevelopment/Services/ReviewService.js +39 -3
- package/build/SubagentDrivenDevelopment/Services/SubmissionService.js +4 -1
- package/build/SubagentDrivenDevelopment/Services/TicketIntegrationService.js +621 -0
- package/build/SubagentDrivenDevelopment/Services/TicketService.js +3 -0
- package/build/SubagentDrivenDevelopment/Services/TicketWorktreeService.js +317 -0
- package/build/SubagentDrivenDevelopment/Services/WorkflowRecordsCommitter.js +336 -0
- package/build/SubagentDrivenDevelopment/SubagentDrivenDevelopmentServiceProvider.js +18 -8
- package/build/TemplateRendering/Computed/ComputedVariableProviders.js +2 -0
- package/build/TemplateRendering/Computed/Providers/AuthoringFlowchartProvider.js +26 -0
- package/package.json +2 -2
- package/build/SubagentDrivenDevelopment/Services/IntegrationService.js +0 -258
- /package/build/Assets/Blueprints/skills/{asd-ste100 → ref-asd-ste100}/CREDITS.MD +0 -0
- /package/build/Assets/Blueprints/skills/{asd-ste100 → ref-asd-ste100}/examples/before-after.md +0 -0
- /package/build/Assets/Blueprints/skills/{asd-ste100 → ref-asd-ste100}/references/writing-rules.md +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: asd-ste100
|
|
2
|
+
name: ref-asd-ste100
|
|
3
3
|
description: "Rewrites ambiguous English into ASD-STE100 style — one meaning per word, active voice, simple tense, short sentences. Use when agent output is hard to parse; triggers: simplify this, STE100 rewrite."
|
|
4
4
|
user-invocable: false
|
|
5
5
|
metadata:
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ref-php-laravel-saloon-api-integrations
|
|
3
|
+
description: "Use when building or testing Laravel API integrations with Saloon: connectors, requests, responses, failures, mocks, pools, or DTOs."
|
|
4
|
+
metadata:
|
|
5
|
+
spindle:
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Working with Saloon
|
|
10
|
+
|
|
11
|
+
This skill covers the latest Saloon release and its Laravel plugin.
|
|
12
|
+
|
|
13
|
+
## Integration boundaries
|
|
14
|
+
|
|
15
|
+
A connector owns the base URL, shared authentication, default headers, and client configuration.
|
|
16
|
+
A request owns its method, endpoint, and request-specific query, headers, and body.
|
|
17
|
+
The connector sends the request.
|
|
18
|
+
|
|
19
|
+
## Topic lookup
|
|
20
|
+
|
|
21
|
+
Before writing code, read each reference whose condition matches the task.
|
|
22
|
+
For combined tasks, read all matching references.
|
|
23
|
+
|
|
24
|
+
| When working on | Read |
|
|
25
|
+
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
26
|
+
| Base URL, shared authentication, default headers, timeouts, or client configuration | [Connectors](references/connectors.md) |
|
|
27
|
+
| HTTP method, endpoint, inputs, query parameters, headers, or JSON body | [Requests](references/requests.md) |
|
|
28
|
+
| `send()` or `sendAsync()`, including promise handling | [Sending requests](references/sending-requests.md) |
|
|
29
|
+
| Status, headers, body parsing, response helpers, or custom response classes | [Responses](references/responses.md) |
|
|
30
|
+
| HTTP or connection failures, throwing, or custom failure detection and exceptions | [Handling failures](references/handling-failures.md) |
|
|
31
|
+
| Tests, request assertions, mocks, or recorded fixtures | [Testing](references/testing.md) |
|
|
32
|
+
| Concurrent batches, rate limits, keyed results, or pool handlers | [Concurrency and pools](references/concurrency-and-pools.md) |
|
|
33
|
+
| Typed response mapping or DTO-driven request bodies | [Data transfer objects](references/data-transfer-objects.md) |
|
|
34
|
+
|
|
35
|
+
## Sources
|
|
36
|
+
|
|
37
|
+
Use the [official documentation](https://docs.saloon.dev/) for API details beyond these references.
|
|
38
|
+
Use the [latest release](https://github.com/saloonphp/saloon/releases/latest) for current changes.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Concurrency & Pools
|
|
2
|
+
|
|
3
|
+
A pool sends independent requests concurrently through one connector.
|
|
4
|
+
Use pools for batches such as fetching records by ID.
|
|
5
|
+
Concurrency requires `GuzzleSender` (the default) or `HttpSender`.
|
|
6
|
+
|
|
7
|
+
## Basic pool
|
|
8
|
+
|
|
9
|
+
```php
|
|
10
|
+
use Saloon\Exceptions\Request\FatalRequestException;
|
|
11
|
+
use Saloon\Exceptions\Request\RequestException;
|
|
12
|
+
use Saloon\Http\Response;
|
|
13
|
+
|
|
14
|
+
$products = [];
|
|
15
|
+
$failures = [];
|
|
16
|
+
|
|
17
|
+
$pool = $connector->pool(
|
|
18
|
+
requests: function () use ($ids): Generator {
|
|
19
|
+
foreach ($ids as $id) {
|
|
20
|
+
yield $id => new GetProductRequest($id);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
concurrency: 10,
|
|
24
|
+
responseHandler: function (Response $response, int|string $key) use (&$products) {
|
|
25
|
+
$products[$key] = $response->dtoOrFail();
|
|
26
|
+
},
|
|
27
|
+
exceptionHandler: function (FatalRequestException|RequestException $exception, int|string $key) use (&$failures) {
|
|
28
|
+
$failures[$key] = $exception instanceof RequestException
|
|
29
|
+
? $exception->getResponse()->status()
|
|
30
|
+
: $exception->getMessage();
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
$pool->send()->wait();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`send()` returns a promise; call `wait()` before reading the results.
|
|
38
|
+
|
|
39
|
+
## Requests
|
|
40
|
+
|
|
41
|
+
Provide any of the following as the first argument or with `setRequests()`:
|
|
42
|
+
|
|
43
|
+
- An array of `Request` or `PendingRequest` objects.
|
|
44
|
+
- A generator, to keep memory low for large batches.
|
|
45
|
+
- A closure or invokable class returning an array or generator, to run setup just before sending.
|
|
46
|
+
|
|
47
|
+
Keys from arrays or generators reach both handlers, so key by ID to match results. Responses complete in any order; never rely on input order.
|
|
48
|
+
|
|
49
|
+
## Concurrency
|
|
50
|
+
|
|
51
|
+
The default is 5 requests in flight.
|
|
52
|
+
Use the `concurrency` argument or `$pool->setConcurrency(10)` to change it. Both also accept a callable.
|
|
53
|
+
Choose a value that respects the API's rate limits. A pool does not throttle requests over time.
|
|
54
|
+
|
|
55
|
+
## Handlers
|
|
56
|
+
|
|
57
|
+
- `responseHandler` / `withResponseHandler()`: receives each **successful** `Response`.
|
|
58
|
+
- `exceptionHandler` / `withExceptionHandler()`: receives `RequestException` for 4xx/5xx responses and `FatalRequestException` for connection failures. HTTP failures always arrive here, even without `throw()` or `AlwaysThrowOnErrors`.
|
|
59
|
+
|
|
60
|
+
Each pool holds one handler of each kind.
|
|
61
|
+
For failure classification and custom exceptions, read [Handling failures](handling-failures.md).
|
|
62
|
+
|
|
63
|
+
## Testing pools
|
|
64
|
+
|
|
65
|
+
Use `MockClient` with pools. Mocked HTTP failures reach the exception handler.
|
|
66
|
+
For offline setup and request assertions, read [Testing](testing.md).
|
|
67
|
+
|
|
68
|
+
```php
|
|
69
|
+
$connector->withMockClient(new MockClient([
|
|
70
|
+
MockResponse::make(['id' => 1, 'name' => 'Widget', 'price_cents' => 999], 200),
|
|
71
|
+
MockResponse::make(['message' => 'Not found'], 404),
|
|
72
|
+
]));
|
|
73
|
+
```
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Connectors
|
|
2
|
+
|
|
3
|
+
A connector represents one external API. Create one connector per integration under `app/Http/Integrations/<Api>/`.
|
|
4
|
+
|
|
5
|
+
## Minimal connector
|
|
6
|
+
|
|
7
|
+
Extend `Saloon\Http\Connector` and implement `resolveBaseUrl()`.
|
|
8
|
+
|
|
9
|
+
```php
|
|
10
|
+
use Saloon\Http\Connector;
|
|
11
|
+
|
|
12
|
+
class InventoryConnector extends Connector
|
|
13
|
+
{
|
|
14
|
+
public function resolveBaseUrl(): string
|
|
15
|
+
{
|
|
16
|
+
return 'https://inventory.example.com/api/v1';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
With the Laravel plugin installed, `php artisan saloon:connector` scaffolds one.
|
|
22
|
+
|
|
23
|
+
## Constructor arguments and authentication
|
|
24
|
+
|
|
25
|
+
Pass instance-specific values through the constructor. Put shared bearer authentication in `defaultAuth()`.
|
|
26
|
+
|
|
27
|
+
```php
|
|
28
|
+
use Saloon\Contracts\Authenticator;
|
|
29
|
+
use Saloon\Http\Auth\TokenAuthenticator;
|
|
30
|
+
use Saloon\Http\Connector;
|
|
31
|
+
|
|
32
|
+
class InventoryConnector extends Connector
|
|
33
|
+
{
|
|
34
|
+
public function __construct(
|
|
35
|
+
protected readonly string $baseUrl,
|
|
36
|
+
protected readonly string $token,
|
|
37
|
+
) {}
|
|
38
|
+
|
|
39
|
+
public function resolveBaseUrl(): string
|
|
40
|
+
{
|
|
41
|
+
return $this->baseUrl;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected function defaultAuth(): ?Authenticator
|
|
45
|
+
{
|
|
46
|
+
return new TokenAuthenticator($this->token);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
$connector = new InventoryConnector(config('services.inventory.url'), config('services.inventory.token'));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`TokenAuthenticator` sends `Authorization: Bearer <token>`.
|
|
54
|
+
Load credentials from configuration or secrets. Never hard-code production credentials.
|
|
55
|
+
|
|
56
|
+
## Default headers
|
|
57
|
+
|
|
58
|
+
Override `defaultHeaders()` for headers every request needs. Request headers are merged on top.
|
|
59
|
+
|
|
60
|
+
```php
|
|
61
|
+
protected function defaultHeaders(): array
|
|
62
|
+
{
|
|
63
|
+
return [
|
|
64
|
+
'Accept' => 'application/json',
|
|
65
|
+
'Content-Type' => 'application/json',
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Change headers on an instance with `$connector->headers()->add('X-Tenant', 'acme');`.
|
|
71
|
+
|
|
72
|
+
## Timeouts
|
|
73
|
+
|
|
74
|
+
Defaults are a 10-second connection timeout and a 30-second request timeout.
|
|
75
|
+
Use `HasTimeout` on a connector for shared timeouts, or on a request for endpoint-specific timeouts.
|
|
76
|
+
|
|
77
|
+
```php
|
|
78
|
+
use Saloon\Traits\Plugins\HasTimeout;
|
|
79
|
+
|
|
80
|
+
class InventoryConnector extends Connector
|
|
81
|
+
{
|
|
82
|
+
use HasTimeout;
|
|
83
|
+
|
|
84
|
+
protected int $connectTimeout = 60;
|
|
85
|
+
|
|
86
|
+
protected int $requestTimeout = 120;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## HTTP client config
|
|
91
|
+
|
|
92
|
+
The default sender is Guzzle. Pass Guzzle request options through `defaultConfig()` or `$connector->config()->add(...)`.
|
|
93
|
+
|
|
94
|
+
```php
|
|
95
|
+
public function defaultConfig(): array
|
|
96
|
+
{
|
|
97
|
+
return ['verify' => true];
|
|
98
|
+
}
|
|
99
|
+
```
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Data Transfer Objects
|
|
2
|
+
|
|
3
|
+
A data transfer object (DTO) gives typed fields to API data.
|
|
4
|
+
Define response mapping on the request, or on the connector when all requests share that mapping.
|
|
5
|
+
Retrieve the DTO from the response.
|
|
6
|
+
|
|
7
|
+
## Define the DTO and mapping
|
|
8
|
+
|
|
9
|
+
```php
|
|
10
|
+
final class Product
|
|
11
|
+
{
|
|
12
|
+
public function __construct(
|
|
13
|
+
public readonly int $id,
|
|
14
|
+
public readonly string $name,
|
|
15
|
+
public readonly int $priceCents,
|
|
16
|
+
) {}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```php
|
|
21
|
+
use Saloon\Http\Request;
|
|
22
|
+
use Saloon\Http\Response;
|
|
23
|
+
|
|
24
|
+
class GetProductRequest extends Request
|
|
25
|
+
{
|
|
26
|
+
// method, constructor, and endpoint ...
|
|
27
|
+
|
|
28
|
+
public function createDtoFromResponse(Response $response): Product
|
|
29
|
+
{
|
|
30
|
+
$data = $response->json();
|
|
31
|
+
|
|
32
|
+
return new Product(
|
|
33
|
+
id: $data['id'],
|
|
34
|
+
name: $data['name'],
|
|
35
|
+
priceCents: $data['price_cents'],
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Retrieve the DTO
|
|
42
|
+
|
|
43
|
+
```php
|
|
44
|
+
$response = $connector->send(new GetProductRequest(42));
|
|
45
|
+
|
|
46
|
+
$product = $response->dtoOrFail(); // throws LogicException if the response failed
|
|
47
|
+
$product = $response->dto(); // always maps, even for failed responses
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use `dtoOrFail()` for success-only DTOs. It prevents mapping an error body into `Product`.
|
|
51
|
+
Use `dto()` only when the mapping handles error payloads.
|
|
52
|
+
"Failed" follows `hasRequestFailed()`; see [Handling failures](handling-failures.md).
|
|
53
|
+
|
|
54
|
+
`dto()` returns `mixed`. For static analysis, annotate it with `/** @var Product $product */`, or call the typed method directly: `$request->createDtoFromResponse($response)`.
|
|
55
|
+
|
|
56
|
+
## DTOs in requests
|
|
57
|
+
|
|
58
|
+
Accept a DTO in a request constructor to build the request body.
|
|
59
|
+
|
|
60
|
+
```php
|
|
61
|
+
use Saloon\Contracts\Body\HasBody;
|
|
62
|
+
use Saloon\Enums\Method;
|
|
63
|
+
use Saloon\Traits\Body\HasJsonBody;
|
|
64
|
+
|
|
65
|
+
class UpdateProductRequest extends Request implements HasBody
|
|
66
|
+
{
|
|
67
|
+
use HasJsonBody;
|
|
68
|
+
|
|
69
|
+
protected Method $method = Method::PUT;
|
|
70
|
+
|
|
71
|
+
public function __construct(protected readonly Product $product) {}
|
|
72
|
+
|
|
73
|
+
public function resolveEndpoint(): string
|
|
74
|
+
{
|
|
75
|
+
return '/products/' . $this->product->id;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
protected function defaultBody(): array
|
|
79
|
+
{
|
|
80
|
+
return [
|
|
81
|
+
'name' => $this->product->name,
|
|
82
|
+
'price_cents' => $this->product->priceCents,
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Keep the source response
|
|
89
|
+
|
|
90
|
+
Implement `WithResponse` and use `HasResponse` to attach the original response to the DTO.
|
|
91
|
+
|
|
92
|
+
```php
|
|
93
|
+
use Saloon\Contracts\DataObjects\WithResponse;
|
|
94
|
+
use Saloon\Traits\Responses\HasResponse;
|
|
95
|
+
|
|
96
|
+
final class Product implements WithResponse
|
|
97
|
+
{
|
|
98
|
+
use HasResponse;
|
|
99
|
+
|
|
100
|
+
// constructor ...
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
$product->getResponse()->status();
|
|
104
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Handling Failures
|
|
2
|
+
|
|
3
|
+
Saloon separates two kinds of failure:
|
|
4
|
+
|
|
5
|
+
- **HTTP failure**: the API returned 4xx/5xx. Synchronous `send()` returns a `Response` with `failed()` set to `true`, unless throwing is enabled.
|
|
6
|
+
- **Connection failure**: the API returned no response, such as a DNS error or connection timeout. Saloon throws `FatalRequestException`.
|
|
7
|
+
|
|
8
|
+
```php
|
|
9
|
+
$response = $connector->send(new CreateProductRequest('Widget', 999));
|
|
10
|
+
|
|
11
|
+
if ($response->failed()) {
|
|
12
|
+
$errors = $response->json('errors'); // e.g. a 422 validation payload
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Throwing on HTTP failures
|
|
17
|
+
|
|
18
|
+
Call `throw()` to throw on a failed response. It returns the response on success.
|
|
19
|
+
|
|
20
|
+
```php
|
|
21
|
+
$product = $connector->send(new GetProductRequest(42))->throw()->json();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For every request on a connector, add `AlwaysThrowOnErrors`.
|
|
25
|
+
|
|
26
|
+
```php
|
|
27
|
+
use Saloon\Traits\Plugins\AlwaysThrowOnErrors;
|
|
28
|
+
|
|
29
|
+
class InventoryConnector extends Connector
|
|
30
|
+
{
|
|
31
|
+
use AlwaysThrowOnErrors;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Exception hierarchy
|
|
36
|
+
|
|
37
|
+
Catch the most specific type the application needs. `RequestException` exposes the response through `getResponse()`.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
SaloonException
|
|
41
|
+
├── FatalRequestException connection errors, no response
|
|
42
|
+
└── RequestException HTTP 4xx/5xx
|
|
43
|
+
├── ClientException 4xx
|
|
44
|
+
│ ├── UnauthorizedException (401), PaymentRequiredException (402)
|
|
45
|
+
│ ├── ForbiddenException (403), NotFoundException (404)
|
|
46
|
+
│ ├── MethodNotAllowedException (405), RequestTimeOutException (408)
|
|
47
|
+
│ └── UnprocessableEntityException (422), TooManyRequestsException (429)
|
|
48
|
+
└── ServerException 5xx
|
|
49
|
+
└── InternalServerErrorException (500), ServiceUnavailableException (503), GatewayTimeoutException (504)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```php
|
|
53
|
+
use Saloon\Exceptions\Request\Statuses\UnprocessableEntityException;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
$connector->send(new CreateProductRequest('', 0))->throw();
|
|
57
|
+
} catch (UnprocessableEntityException $e) {
|
|
58
|
+
$errors = $e->getResponse()->json('errors');
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Async and pools
|
|
63
|
+
|
|
64
|
+
Promises from `sendAsync()` and pools reject on HTTP and connection failures.
|
|
65
|
+
Handle both in `otherwise` or the pool's exception handler. Calling `throw()` is unnecessary.
|
|
66
|
+
For callback examples, read [Sending requests](sending-requests.md) or [Concurrency and pools](concurrency-and-pools.md).
|
|
67
|
+
|
|
68
|
+
## Custom failure detection
|
|
69
|
+
|
|
70
|
+
Some APIs return 200 with an error in the body. Override `hasRequestFailed()` on the connector or request; returning `null` falls back to the status-code check.
|
|
71
|
+
|
|
72
|
+
```php
|
|
73
|
+
use Saloon\Http\Response;
|
|
74
|
+
|
|
75
|
+
public function hasRequestFailed(Response $response): ?bool
|
|
76
|
+
{
|
|
77
|
+
return $response->json('status') === 'error';
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This changes `failed()`, `throw()`, `dtoOrFail()`, and `AlwaysThrowOnErrors`.
|
|
82
|
+
|
|
83
|
+
## Custom exception
|
|
84
|
+
|
|
85
|
+
Override `getRequestException()` on the connector or request to throw an application exception. A request's override wins over the connector's.
|
|
86
|
+
|
|
87
|
+
```php
|
|
88
|
+
use Saloon\Http\Response;
|
|
89
|
+
use Throwable;
|
|
90
|
+
|
|
91
|
+
public function getRequestException(Response $response, ?Throwable $senderException): ?Throwable
|
|
92
|
+
{
|
|
93
|
+
return new InventoryApiException($response->json('message', 'Inventory API error'), $response->status(), $senderException);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Requests
|
|
2
|
+
|
|
3
|
+
Create a request class for each endpoint operation.
|
|
4
|
+
Saloon appends the endpoint to the connector's base URL.
|
|
5
|
+
Keep request classes beside their connector.
|
|
6
|
+
|
|
7
|
+
## Minimal request
|
|
8
|
+
|
|
9
|
+
Extend `Saloon\Http\Request`, set `$method`, and implement `resolveEndpoint()`.
|
|
10
|
+
|
|
11
|
+
```php
|
|
12
|
+
use Saloon\Enums\Method;
|
|
13
|
+
use Saloon\Http\Request;
|
|
14
|
+
|
|
15
|
+
class ListProductsRequest extends Request
|
|
16
|
+
{
|
|
17
|
+
protected Method $method = Method::GET;
|
|
18
|
+
|
|
19
|
+
public function resolveEndpoint(): string
|
|
20
|
+
{
|
|
21
|
+
return '/products';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
With the Laravel plugin installed, `php artisan saloon:request` scaffolds one.
|
|
27
|
+
|
|
28
|
+
## Constructor arguments
|
|
29
|
+
|
|
30
|
+
Pass identifiers and inputs through the constructor.
|
|
31
|
+
|
|
32
|
+
```php
|
|
33
|
+
class GetProductRequest extends Request
|
|
34
|
+
{
|
|
35
|
+
protected Method $method = Method::GET;
|
|
36
|
+
|
|
37
|
+
public function __construct(protected readonly int $id) {}
|
|
38
|
+
|
|
39
|
+
public function resolveEndpoint(): string
|
|
40
|
+
{
|
|
41
|
+
return '/products/' . $this->id;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Query parameters
|
|
47
|
+
|
|
48
|
+
Override `defaultQuery()`, or add to an instance with `$request->query()->add('page', 2)`.
|
|
49
|
+
|
|
50
|
+
```php
|
|
51
|
+
protected function defaultQuery(): array
|
|
52
|
+
{
|
|
53
|
+
return [
|
|
54
|
+
'sort' => 'name',
|
|
55
|
+
'filter[active]' => 'true',
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Saloon builds query strings with `http_build_query`.
|
|
61
|
+
|
|
62
|
+
## Headers
|
|
63
|
+
|
|
64
|
+
Override `defaultHeaders()`; these merge with the connector's headers. Instance changes use `$request->headers()->add(...)`.
|
|
65
|
+
|
|
66
|
+
```php
|
|
67
|
+
protected function defaultHeaders(): array
|
|
68
|
+
{
|
|
69
|
+
return ['Accept' => 'application/json'];
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## JSON body
|
|
74
|
+
|
|
75
|
+
Implement `HasBody`, use `HasJsonBody`, and override `defaultBody()`.
|
|
76
|
+
|
|
77
|
+
```php
|
|
78
|
+
use Saloon\Contracts\Body\HasBody;
|
|
79
|
+
use Saloon\Enums\Method;
|
|
80
|
+
use Saloon\Http\Request;
|
|
81
|
+
use Saloon\Traits\Body\HasJsonBody;
|
|
82
|
+
|
|
83
|
+
class CreateProductRequest extends Request implements HasBody
|
|
84
|
+
{
|
|
85
|
+
use HasJsonBody;
|
|
86
|
+
|
|
87
|
+
protected Method $method = Method::POST;
|
|
88
|
+
|
|
89
|
+
public function __construct(
|
|
90
|
+
protected readonly string $name,
|
|
91
|
+
protected readonly int $priceCents,
|
|
92
|
+
) {}
|
|
93
|
+
|
|
94
|
+
public function resolveEndpoint(): string
|
|
95
|
+
{
|
|
96
|
+
return '/products';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected function defaultBody(): array
|
|
100
|
+
{
|
|
101
|
+
return [
|
|
102
|
+
'name' => $this->name,
|
|
103
|
+
'price_cents' => $this->priceCents,
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Timeouts
|
|
110
|
+
|
|
111
|
+
For endpoint-specific timeouts, use the trait and properties shown in [Timeouts](connectors.md#timeouts) on the request.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Responses
|
|
2
|
+
|
|
3
|
+
Synchronous `send()` returns a `Saloon\Http\Response`.
|
|
4
|
+
For HTTP and connection failure behavior, read [Handling failures](handling-failures.md).
|
|
5
|
+
|
|
6
|
+
## Reading a response
|
|
7
|
+
|
|
8
|
+
```php
|
|
9
|
+
$response = $connector->send(new GetProductRequest(42));
|
|
10
|
+
|
|
11
|
+
$response->status(); // 200
|
|
12
|
+
$response->header('Content-Type');
|
|
13
|
+
$response->body(); // raw string
|
|
14
|
+
$response->json(); // decoded array
|
|
15
|
+
$response->json('data.name'); // nested key (dot notation)
|
|
16
|
+
$response->json('missing', 'x'); // default when absent
|
|
17
|
+
$response->object(); // stdClass
|
|
18
|
+
$response->collect('data'); // Laravel Collection (requires illuminate/collections)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Useful methods
|
|
22
|
+
|
|
23
|
+
| Method | Purpose |
|
|
24
|
+
| ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
25
|
+
| `status()`, `headers()`, `header($name)` | Status code and headers |
|
|
26
|
+
| `body()`, `json()` / `array()`, `object()`, `collect()` | Body as string, array, object, or Collection |
|
|
27
|
+
| `stream()`, `saveBodyToFile($path)` | Large or binary bodies |
|
|
28
|
+
| `xmlReader()`, `dom()` | XML (needs `saloonphp/xml-wrangler`) or HTML (needs `symfony/dom-crawler`) |
|
|
29
|
+
| `ok()`, `successful()`, `redirect()`, `failed()`, `clientError()`, `serverError()` | Status checks; `failed()` is customizable |
|
|
30
|
+
| `throw()` | Throw if the response is considered failed |
|
|
31
|
+
| `dto()`, `dtoOrFail()` | Convert to a DTO; see [Data Transfer Objects](data-transfer-objects.md) |
|
|
32
|
+
| `getPendingRequest()`, `getPsrRequest()`, `getPsrResponse()` | The built request and the raw PSR-7 messages |
|
|
33
|
+
|
|
34
|
+
## Custom response class
|
|
35
|
+
|
|
36
|
+
Extend `Response` to add API-specific helpers.
|
|
37
|
+
Register the class on the connector for all requests, or on a request for that request only.
|
|
38
|
+
|
|
39
|
+
```php
|
|
40
|
+
use Saloon\Http\Response;
|
|
41
|
+
|
|
42
|
+
class InventoryResponse extends Response
|
|
43
|
+
{
|
|
44
|
+
public function nextPage(): ?int
|
|
45
|
+
{
|
|
46
|
+
return $this->json('meta.next_page');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class InventoryConnector extends Connector
|
|
51
|
+
{
|
|
52
|
+
protected ?string $response = InventoryResponse::class;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Override `resolveResponseClass(): string` instead of the property when the class must be chosen dynamically.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Sending Requests
|
|
2
|
+
|
|
3
|
+
Pass a request to its connector to send it.
|
|
4
|
+
|
|
5
|
+
## Synchronous
|
|
6
|
+
|
|
7
|
+
```php
|
|
8
|
+
$connector = new InventoryConnector($baseUrl, $token);
|
|
9
|
+
|
|
10
|
+
$response = $connector->send(new ListProductsRequest);
|
|
11
|
+
|
|
12
|
+
$products = $response->json('data');
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`send()` returns a `Saloon\Http\Response`. A 4xx/5xx status does **not** throw by default; see [Handling Failures](handling-failures.md).
|
|
16
|
+
|
|
17
|
+
## Asynchronous
|
|
18
|
+
|
|
19
|
+
`sendAsync()` returns a Guzzle `PromiseInterface`.
|
|
20
|
+
Successful responses reach `then`. HTTP and connection failures reject the promise and reach `otherwise`.
|
|
21
|
+
|
|
22
|
+
```php
|
|
23
|
+
use Saloon\Exceptions\Request\FatalRequestException;
|
|
24
|
+
use Saloon\Exceptions\Request\RequestException;
|
|
25
|
+
use Saloon\Http\Response;
|
|
26
|
+
|
|
27
|
+
$promise = $connector->sendAsync(new ListProductsRequest);
|
|
28
|
+
|
|
29
|
+
$promise
|
|
30
|
+
->then(function (Response $response) {
|
|
31
|
+
// handle success
|
|
32
|
+
})
|
|
33
|
+
->otherwise(function (FatalRequestException|RequestException $exception) {
|
|
34
|
+
// RequestException: the API returned 4xx/5xx; use $exception->getResponse()
|
|
35
|
+
// FatalRequestException: no response, e.g. connection failure
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
$promise->wait(); // resolve the promise
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For concurrent batches, read [Concurrency and pools](concurrency-and-pools.md).
|
|
42
|
+
For failure classification and custom exceptions, read [Handling failures](handling-failures.md).
|