From 36cbb4ebbefec83449360405b2f294149e2dc2ac Mon Sep 17 00:00:00 2001 From: Gabriel Pan Gantes Date: Wed, 27 May 2026 11:31:31 +0200 Subject: [PATCH] chore(claude): add slash commands for OAS sync and SDK additions Ports the pattern already established in pluggy-node (.claude/commands/) adapted to this SDK's Java/Maven/Retrofit/Lombok stack: - sync-api: fetch api.pluggy.ai/oas3.json, diff against PluggyApiService + DTOs, produce gap report. Run periodically to catch missing fields and casing mismatches before users do. - add-model-fields: Java/Lombok templates for adding fields, with the Gson IDENTITY casing gotcha called out as the highest-impact gap. - add-endpoint: Retrofit endpoint templates with DTO naming conventions. Also adds .claude/settings.local.json to .gitignore (local-only file, mirrors pluggy-node's convention) and points to the new commands from CLAUDE.md. --- .claude/commands/add-endpoint.md | 102 ++++++++++++++++++++++ .claude/commands/add-model-fields.md | 124 +++++++++++++++++++++++++++ .claude/commands/sync-api.md | 72 ++++++++++++++++ .gitignore | 3 + CLAUDE.md | 8 ++ 5 files changed, 309 insertions(+) create mode 100644 .claude/commands/add-endpoint.md create mode 100644 .claude/commands/add-model-fields.md create mode 100644 .claude/commands/sync-api.md diff --git a/.claude/commands/add-endpoint.md b/.claude/commands/add-endpoint.md new file mode 100644 index 0000000..a79fb06 --- /dev/null +++ b/.claude/commands/add-endpoint.md @@ -0,0 +1,102 @@ +# Add New Endpoint + +Add a new API endpoint to the Pluggy Java SDK. + +## Required Information + +- **Endpoint path**: e.g. `/models`, `/models/{id}` +- **HTTP method**: `GET`, `POST`, `PATCH`, `DELETE` +- **Query / path / body parameters**: from the OAS +- **Response schema**: from the OAS +- **Request schema** (if any): from the OAS + +## Steps + +### 1. Define request and response DTOs + +Create DTOs under: +- `src/main/java/ai/pluggy/client/response/` — response payloads (`@Data @Builder`) +- `src/main/java/ai/pluggy/client/request/` — request bodies (`@Value @AllArgsConstructor @Builder`) or query maps + +See `add-model-fields` for field patterns. **Field names must match the OAS byte-for-byte** (Gson `IDENTITY`). + +Naming conventions: +- Single item response: bare entity name — `Account`, `Bill`, `Transaction` +- Collection / paginated response: `sResponse` — `AccountsResponse`, `BillsResponse` +- Request body: `Request` — `CreateItemRequest`, `UpdateItemRequest` +- Query map: `SearchRequest` or `Request` — `TransactionsSearchRequest`, `AccountsRequest` + +### 2. Add the method to `PluggyApiService` + +`src/main/java/ai/pluggy/client/PluggyApiService.java` is the single Retrofit interface for the entire SDK. Add the new method there with the appropriate annotations. + +**Retrofit patterns:** + +```java +// GET with no params +@GET("/categories") +Call getCategories(); + +// GET with path param +@GET("/categories/{id}") +Call getCategory(@Path("id") String categoryId); + +// GET with single query param +@GET("/accounts") +Call getAccounts(@Query("itemId") String itemId); + +// GET with query map (filters / pagination) +@GET("/accounts") +Call getAccounts(@QueryMap AccountsRequest accountsRequest); + +// POST with body +@POST("/items") +Call createItem(@Body CreateItemRequest createItemRequest); + +// PATCH with path param + body +@PATCH("/items/{id}") +Call updateItem(@Path("id") String itemId, + @Body UpdateItemRequest updateItemRequest); + +// DELETE with path param +@DELETE("/items/{id}") +Call deleteItem(@Path("id") String existingItemId); +``` + +Provide overloads (e.g. a no-body `updateItem` and a body-accepting one) only if the API genuinely supports both shapes — don't add convenience overloads speculatively. + +### 3. Build and verify compilation + +```bash +mvn -B package +``` + +### 4. Add an integration test + +Put the test under `src/test/java/ai/pluggy/client/integration/` extending `BaseApiIntegrationTest`. The base class provides an authenticated `client` and cleans up items registered in `itemsIdCreated` after each test. + +```java +class GetModelTest extends BaseApiIntegrationTest { + + @Test + void getModel_validId_responseOk() throws IOException { + Response response = client.service().getModel("some-id").execute(); + assertSuccessful(response); // from integration/util/AssertionsUtils + Model model = response.body(); + assertNotNull(model); + assertEquals("some-id", model.getId()); + } +} +``` + +If the test creates items, **register them** in `itemsIdCreated` immediately after creation so the `@AfterEach` cleanup deletes them — otherwise they leak into production. + +### 5. Run the integration test + +```bash +doppler run -- mvn -B verify -Dit.test=GetModelTest -DfailIfNoTests=false +``` + +### 6. Update CLAUDE.md (optional) + +If the new endpoint is a notable addition (e.g. a whole new resource family), mention it under "Architecture". diff --git a/.claude/commands/add-model-fields.md b/.claude/commands/add-model-fields.md new file mode 100644 index 0000000..7e6e851 --- /dev/null +++ b/.claude/commands/add-model-fields.md @@ -0,0 +1,124 @@ +# Add Model Fields + +Add missing fields to existing DTOs in the Pluggy Java SDK. + +## Required Information + +- **DTO name**: e.g. `Transaction`, `Investment`, `Account` +- **Field name (OAS)**: exact JSON property name from the OpenAPI spec — **case-sensitive** +- **Field type**: Java type (`String`, `Integer`, `Double`, `Boolean`, `Date`, custom DTO, array/list) +- **Nullability**: nullable by default in this SDK. Use boxed types (`Integer`, `Double`) for nullable numerics; primitives only when guaranteed non-null. +- **Description**: brief comment if the field is non-obvious (avoid otherwise) + +## Critical: Gson IDENTITY casing + +The Gson instance is configured with `FieldNamingPolicy.IDENTITY`. Java field names must match the OAS JSON property name **byte-for-byte**. `payeeMCC` ≠ `payeeMcc` — a casing mismatch silently produces `null` with no warning. **Always copy the OAS field name exactly, including unusual casing.** + +## Steps + +### 1. Locate the DTO + +DTOs live in `src/main/java/ai/pluggy/client/response/` (response payloads) and `src/main/java/ai/pluggy/client/request/` (request bodies and query maps). + +### 2. Verify the OAS field name + +Fetch the OAS (see `sync-api`) and inspect the schema: + +```bash +jq '.components.schemas..properties' /tmp/pluggy-oas.json +``` + +Copy the property key **exactly** as it appears. + +### 3. Add the field + +Response DTO pattern (Lombok `@Data @Builder`, package-private fields): + +```java +@Data +@Builder +public class Transaction { + String id; + String accountId; + Double amount; + // ... existing fields ... + + // New field — name must match OAS exactly + String operationType; +} +``` + +Request DTO pattern (Lombok `@Value @AllArgsConstructor @Builder` with convenience constructors): + +```java +@Value +@AllArgsConstructor +@Builder +public class CreateItemRequest { + Integer connectorId; + ParametersMap parameters; + // ... existing fields ... + + // New optional field + String newField; + + // If you add a field, update each convenience constructor to set it to null + public CreateItemRequest(Integer connectorId, ParametersMap parameters) { + this.connectorId = connectorId; + this.parameters = parameters; + // ... null-out the rest, including newField ... + this.newField = null; + } +} +``` + +### 4. Type patterns + +```java +// Nullable string +String name; + +// Non-null primitive (only if API guarantees it) +int count; + +// Nullable number (default — boxed) +Integer count; +Double balance; + +// Boolean +Boolean isActive; + +// Date — usually serialized as ISO 8601 string in the API; prefer String +// unless an existing pattern in a sibling DTO uses java.util.Date +String createdAt; + +// Nested DTO +CreditData creditData; + +// Array +String[] tags; +SubModel[] items; + +// Enum — declared as a separate file under response/ or request/ +ProductType[] products; +``` + +### 5. Add nested DTOs if needed + +If the field is a complex type new to the SDK, create a sibling file in the same package and follow the `@Data @Builder` pattern (for response) or `@Value @Builder` (for request). + +### 6. Build and test + +```bash +mvn -B package # compile + unit tests +doppler run -- mvn -B verify # integration tests +``` + +For deserialization changes, integration tests are the real signal — they exercise real API responses. + +### 7. Verification checklist + +- [ ] Field name matches OAS byte-for-byte (no case smoothing) +- [ ] Nullable fields use boxed types +- [ ] Existing convenience constructors updated (for request DTOs) +- [ ] `mvn -B verify` passes diff --git a/.claude/commands/sync-api.md b/.claude/commands/sync-api.md new file mode 100644 index 0000000..10af0e1 --- /dev/null +++ b/.claude/commands/sync-api.md @@ -0,0 +1,72 @@ +# Sync SDK with API + +Synchronize the Pluggy Java SDK with the current production API. + +## Steps + +### 1. Fetch Current API Specification + +Fetch the OpenAPI 3.1 spec. The endpoint requires authentication via `x-api-key`: + +```bash +doppler run -- bash -c ' + apikey=$(curl -s -X POST https://api.pluggy.ai/auth \ + -H "Content-Type: application/json" \ + -d "{\"clientId\":\"$PLUGGY_CLIENT_ID\",\"clientSecret\":\"$PLUGGY_CLIENT_SECRET\"}" \ + | jq -r .apiKey) + curl -s https://api.pluggy.ai/oas3.json -H "x-api-key: $apikey" -o /tmp/pluggy-oas.json +' +``` + +If Doppler is not configured, set `PLUGGY_CLIENT_ID` and `PLUGGY_CLIENT_SECRET` manually. + +### 2. Compare Endpoints + +Review the Retrofit interface `src/main/java/ai/pluggy/client/PluggyApiService.java` against the OpenAPI `paths`. + +**Check for:** +- Missing endpoints (paths in OAS but not in `PluggyApiService`) +- Missing HTTP methods on existing paths +- Path parameters declared with `@Path` matching OAS `{param}` placeholders +- Query parameters: single `@Query("name")` vs. `@QueryMap SearchRequest` patterns +- Request body shape (`@Body` DTO) matches the OAS request schema +- Response type (`Call`) matches the OAS response schema + +### 3. Compare Types + +Review types in `src/main/java/ai/pluggy/client/response/` and `src/main/java/ai/pluggy/client/request/` against the OAS `components.schemas`. + +**Check for:** +- Missing fields in existing DTOs +- New schemas not yet defined as DTOs +- **Field name casing**: the Gson instance uses `FieldNamingPolicy.IDENTITY`, so Java field names must match OAS property names **byte-for-byte** (e.g. OAS `payeeMCC` → Java `payeeMCC`, not `payeeMcc`). A single-character mismatch silently deserializes to `null`. This is the single highest-impact gap class — audit casing carefully. See commit `d1befb0` for prior incident. +- Type mismatches (e.g. OAS `integer` vs Java `Integer`, OAS `array` vs `List` / `T[]`) +- Nullability: Java has no `?` syntax; use boxed types (`Integer`, `Double`, `Boolean`) for nullable fields. Primitives (`int`, `double`, `boolean`) cannot represent null. + +### 4. Document Findings + +Produce a gap analysis grouped by: +- **Missing endpoints** — with priority and HTTP method +- **Missing fields** — by DTO, with API name, Java name, type, nullability +- **Casing mismatches** — highest priority because they fail silently +- **Type mismatches** — flag and propose fix +- **Breaking changes** (if the OAS removed a field the SDK still exposes) + +### 5. Implementation Order + +1. **Phase 1 — Casing fixes**: rename Java fields to match OAS exactly. Highest impact because failures are silent. +2. **Phase 2 — Missing fields**: add new fields to existing DTOs (see `add-model-fields`). +3. **Phase 3 — Missing endpoints**: implement new endpoints (see `add-endpoint`). +4. **Phase 4 — Type corrections**: fix any wrong types. + +### 6. Validation + +After implementation: +```bash +mvn -B package # compile + unit tests +doppler run -- mvn -B verify # integration tests against live API +``` + +### 7. Update CLAUDE.md + +If significant gaps were closed, add a line under "Architecture" noting the sync date and what was added. diff --git a/.gitignore b/.gitignore index 8fd04c2..a524e9b 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,6 @@ buildNumber.properties !.vscode/launch.json !.vscode/extensions.json .history + +### Claude Code ### +.claude/settings.local.json diff --git a/CLAUDE.md b/CLAUDE.md index 34f8de1..8775ce0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,6 +29,14 @@ mvn -B verify -Dit.test=GetConnectorsTest -DfailIfNoTests=false No lint/format plugin is configured (no checkstyle, spotless, PMD, errorprone) — formatting is enforced only by `.editorconfig`. Don't go looking for one. +## Claude commands + +Three slash commands under `.claude/commands/` codify the recurring SDK ↔ API workflows: + +- `sync-api` — fetch the live OpenAPI spec from `api.pluggy.ai/oas3.json`, diff it against `PluggyApiService` and the DTOs under `client/request/` + `client/response/`, and produce a gap report. Use periodically to catch missing fields and casing mismatches before users do. +- `add-model-fields` — Java/Lombok templates for adding fields to existing DTOs, with the Gson `IDENTITY` casing gotcha called out. +- `add-endpoint` — templates for adding a new endpoint method to `PluggyApiService` with the right Retrofit annotations and DTO naming conventions. + Integration tests require the following env vars (loaded from Doppler in CI, see `.github/workflows/maven.yml`): - `PLUGGY_CLIENT_ID`