diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9594c35a..96f0b3b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,18 +1,51 @@ name: CI on: push: - branches-ignore: - - 'generated' - - 'codegen/**' - - 'integrated/**' - - 'stl-preview-head/**' - - 'stl-preview-base/**' + branches: + - '**' + - '!integrated/**' + - '!stl-preview-head/**' + - '!stl-preview-base/**' + - '!generated' + - '!codegen/**' + - 'codegen/stl/**' pull_request: branches-ignore: - 'stl-preview-head/**' - 'stl-preview-base/**' jobs: + build: + timeout-minutes: 10 + name: build + permissions: + contents: read + id-token: write + runs-on: ${{ github.repository == 'stainless-sdks/finch-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: |- + github.repository == 'stainless-sdks/finch-go' && + (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Get GitHub OIDC Token + if: |- + github.repository == 'stainless-sdks/finch-go' && + !startsWith(github.ref, 'refs/heads/stl/') + id: github-oidc + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + if: |- + github.repository == 'stainless-sdks/finch-go' && + !startsWith(github.ref, 'refs/heads/stl/') + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh lint: timeout-minutes: 10 name: lint @@ -20,10 +53,10 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version-file: ./go.mod @@ -35,10 +68,10 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/finch-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version-file: ./go.mod diff --git a/.gitignore b/.gitignore index c6d05015..8554affe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .prism.log +.stdy.log codegen.log Brewfile.lock.json .idea/ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 9446d50e..44959ac4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.34.1" + ".": "1.35.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 18fe76f0..b9153e49 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 45 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-9c32d7e477bd1c441abd65db0dfe6220948aa00face05fc8b57395e368ee2099.yml -openapi_spec_hash: 3da940ffc5da8000a4f359c958ed341f -config_hash: 6d3585c0032e08d723d077d660fc8448 +configured_endpoints: 47 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch/finch-5092370ef89959c46138a85f9d6d3c919682a5492a0f9f85ac4421de702f35a8.yml +openapi_spec_hash: a4ca94b3405fc83934c949068943e16c +config_hash: a1c4b7d897cbf8ed42c5f474b3161d79 diff --git a/CHANGELOG.md b/CHANGELOG.md index b4815cd5..cd6d398b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,94 @@ # Changelog +## 1.35.0 (2026-05-13) + +Full Changelog: [v1.34.1...v1.35.0](https://github.com/Finch-API/finch-api-go/compare/v1.34.1...v1.35.0) + +### Features + +* **api:** add per endpoint security ([4f6a61c](https://github.com/Finch-API/finch-api-go/commit/4f6a61cb9e3290136a7c3d8564ebb4ce0b493c55)) +* **api:** add register benefits method ([be108cc](https://github.com/Finch-API/finch-api-go/commit/be108cc33180f7e0b54b104de0cdf2e59a9dcb31)) +* **api:** api update ([9e53333](https://github.com/Finch-API/finch-api-go/commit/9e53333d990dda00cfb819b3ac069a2254f68ea5)) +* **api:** api update ([4263048](https://github.com/Finch-API/finch-api-go/commit/4263048f6d00c65b4b52b942fe3dd8b80a46f371)) +* **api:** api update ([b0aa765](https://github.com/Finch-API/finch-api-go/commit/b0aa76565c99ff56b69a5acd326f96782f25602b)) +* **api:** api update ([bf87e6f](https://github.com/Finch-API/finch-api-go/commit/bf87e6fb3eecb38486fcc1f43f192437d8302ef2)) +* **api:** api update ([498a799](https://github.com/Finch-API/finch-api-go/commit/498a799c62561b2b03e7140a6b5545e1d8744232)) +* **api:** api update ([9dc8e01](https://github.com/Finch-API/finch-api-go/commit/9dc8e015f0f6f97ba58817fba474bbaf27f3581e)) +* **api:** api update ([d869d30](https://github.com/Finch-API/finch-api-go/commit/d869d30065f591dea7781b6dc3124306e26e88fe)) +* **api:** api update ([0e69b17](https://github.com/Finch-API/finch-api-go/commit/0e69b1715d0da0aaba88240f51dc8950bb893015)) +* **api:** api update ([6708aac](https://github.com/Finch-API/finch-api-go/commit/6708aacc01982e5141ac1ca52412987994f3aecd)) +* **api:** api update ([e4f5130](https://github.com/Finch-API/finch-api-go/commit/e4f5130e25eb506c09204f69d6085e9b243cfd76)) +* **api:** api update ([f5c5bbc](https://github.com/Finch-API/finch-api-go/commit/f5c5bbcf3cf0d07df31e2330159de37d36e946f2)) +* **api:** api update ([6385a1f](https://github.com/Finch-API/finch-api-go/commit/6385a1ff8de901c11dd7452adc89f44542144247)) +* **api:** api update ([60a3e0c](https://github.com/Finch-API/finch-api-go/commit/60a3e0c4abdcac96bb7ca1a5ce64f99367fb5a12)) +* **api:** api update ([66108ba](https://github.com/Finch-API/finch-api-go/commit/66108ba1d8a47acf7663432e1d9b5744800575e6)) +* **api:** change auth to npm to oidc ([e5c1a0d](https://github.com/Finch-API/finch-api-go/commit/e5c1a0d20beaccb40e034af4ac54c282cc1ef103)) +* **api:** manual updates ([a7620d2](https://github.com/Finch-API/finch-api-go/commit/a7620d229ae967e7c06e853eeccf8a56881195d3)) +* **api:** manual updates ([1ed52cf](https://github.com/Finch-API/finch-api-go/commit/1ed52cffc078b436f3899b736e5deb7e8cb5f703)) +* **api:** update automated code reviewer selection ([03c7271](https://github.com/Finch-API/finch-api-go/commit/03c7271b89082892da533f8e9fb102ce18de39da)) +* **go:** add default http client with timeout ([cf3dcb3](https://github.com/Finch-API/finch-api-go/commit/cf3dcb3d1ddf6c1136e53dd57a93f7facd23a58f)) +* **internal:** support comma format in multipart form encoding ([59dc2b9](https://github.com/Finch-API/finch-api-go/commit/59dc2b9dce285eb80a691ca90e9ac97229034ff6)) +* support setting headers via env ([1e3953b](https://github.com/Finch-API/finch-api-go/commit/1e3953bccd8b1cbc8f77d404779a392aba709ed9)) + + +### Bug Fixes + +* **aliases:** resolve broken types for alias methods with pagination ([5eb0ac9](https://github.com/Finch-API/finch-api-go/commit/5eb0ac9e24a960fdd768f749babe93b523a4ca1f)) +* allow canceling a request while it is waiting to retry ([e6db22d](https://github.com/Finch-API/finch-api-go/commit/e6db22de47e89efee09be198a42e7c02200e364b)) +* **api:** remove invalid transform config ([d5b60bf](https://github.com/Finch-API/finch-api-go/commit/d5b60bfaa7cbf1aa7c55fbe24f58ebe0474754fe)) +* **client:** send correct authentication methods ([1e5ccd8](https://github.com/Finch-API/finch-api-go/commit/1e5ccd8416ee5a97e58656d59c2cf9c3980cf446)) +* **docs:** add missing pointer prefix to api.md return types ([5aba5fa](https://github.com/Finch-API/finch-api-go/commit/5aba5fa426a29a3084302bc382a7d15af2eb8bea)) +* **docs:** fix mcp installation instructions for remote servers ([ad830d2](https://github.com/Finch-API/finch-api-go/commit/ad830d20499fffdd11c6b197f921177b4abd65f3)) +* **go:** avoid panic when http.DefaultTransport is wrapped ([7fa82db](https://github.com/Finch-API/finch-api-go/commit/7fa82dbdfe66f9394927ab9b86c612df1c171e17)) +* **java:** Resolve name collisions ([0844ac5](https://github.com/Finch-API/finch-api-go/commit/0844ac58ae69b2d0c1274bac8cf7c1ee0eb8e349)) +* **mcp:** correct code tool API endpoint ([78d27b1](https://github.com/Finch-API/finch-api-go/commit/78d27b1c7b5a74781897a42d948994a923957e28)) +* prevent duplicate ? in query params ([e8c398d](https://github.com/Finch-API/finch-api-go/commit/e8c398dcf6b5418f188cf388fdbb16d39b71e97e)) +* rename param to avoid collision ([a23c9b2](https://github.com/Finch-API/finch-api-go/commit/a23c9b2b69248225624750994cc66775535fe3f9)) +* **tests:** skip broken date validation test ([2a9c325](https://github.com/Finch-API/finch-api-go/commit/2a9c325c2352c3b2cd113b412c621cf611cecd3a)) +* **types:** generate shared enum types that are not referenced by other schemas ([8f1aae5](https://github.com/Finch-API/finch-api-go/commit/8f1aae5f99d7632660d3eeb0f25d86e0416212b1)) + + +### Chores + +* avoid embedding reflect.Type for dead code elimination ([e97e7d8](https://github.com/Finch-API/finch-api-go/commit/e97e7d864ba1f7532a1a6d3cf78cf17b11c383a6)) +* **ci:** skip lint on metadata-only changes ([9da39dd](https://github.com/Finch-API/finch-api-go/commit/9da39ddbd53e1a4ab93d8dfb1977294882210ad0)) +* **ci:** skip uploading artifacts on stainless-internal branches ([4365581](https://github.com/Finch-API/finch-api-go/commit/43655817dc1d53427fe0eb41ee6bef24f6ea8340)) +* **ci:** support opting out of skipping builds on metadata-only commits ([5734b45](https://github.com/Finch-API/finch-api-go/commit/5734b45a802b756182f81689f6eac0fa19f72ea0)) +* elide duplicate aliases ([fe9cc50](https://github.com/Finch-API/finch-api-go/commit/fe9cc50323a51a6caafe44cebf2628f3dd788c4a)) +* **internal:** codegen related update ([28a26e6](https://github.com/Finch-API/finch-api-go/commit/28a26e60952b98adc9b0236e808671c562f892ac)) +* **internal:** codegen related update ([513bd70](https://github.com/Finch-API/finch-api-go/commit/513bd70b2b8fa7d55c23e6ee3d80f025a47e96e9)) +* **internal:** codegen related update ([97407f5](https://github.com/Finch-API/finch-api-go/commit/97407f552709b01565f5f627a182ac3bebbb2ed4)) +* **internal:** codegen related update ([2e87c84](https://github.com/Finch-API/finch-api-go/commit/2e87c84e8db41801091a7d05df535abe45831140)) +* **internal:** codegen related update ([da796cf](https://github.com/Finch-API/finch-api-go/commit/da796cf9a6f4f8bb56d92c6a13707a1eb3c8e762)) +* **internal:** minor cleanup ([30717c9](https://github.com/Finch-API/finch-api-go/commit/30717c9086ae53b1cfeb4212f3197f1f2a22ee25)) +* **internal:** more robust bootstrap script ([4567209](https://github.com/Finch-API/finch-api-go/commit/45672092de0a9c9277bc500e3f9a184ac3349926)) +* **internal:** move custom custom `json` tags to `api` ([1969766](https://github.com/Finch-API/finch-api-go/commit/1969766dcd2efdebf079bd7500b39805036c7bb1)) +* **internal:** tweak CI branches ([49d5d5f](https://github.com/Finch-API/finch-api-go/commit/49d5d5f7c566775bc52728b6e63f3ae5af8a5b99)) +* **internal:** update `actions/checkout` version ([842d28f](https://github.com/Finch-API/finch-api-go/commit/842d28fa959584aef8f23bb9e7984295faf61980)) +* **internal:** update gitignore ([ff360d9](https://github.com/Finch-API/finch-api-go/commit/ff360d9cc7a273133dc43807eb5eb0b7b380fc8c)) +* **internal:** use explicit returns ([50598bc](https://github.com/Finch-API/finch-api-go/commit/50598bc2920ffe7720f4610df198b161e7f3d07d)) +* **internal:** use explicit returns in more places ([d3df8e0](https://github.com/Finch-API/finch-api-go/commit/d3df8e00450d15b7ce72981a833409246f5c69fb)) +* redact api-key headers in debug logs ([b14a3e2](https://github.com/Finch-API/finch-api-go/commit/b14a3e2098028267d2d0c0c40ac42f3a9497a709)) +* remove unnecessary error check for url parsing ([1edfaa7](https://github.com/Finch-API/finch-api-go/commit/1edfaa775dfacad3af6bec607e7f8cbee34beb14)) +* **tests:** bump steady to v0.19.4 ([6612aa2](https://github.com/Finch-API/finch-api-go/commit/6612aa2d1245057a8e457df53056e084aaad3f22)) +* **tests:** bump steady to v0.19.5 ([7f24bb0](https://github.com/Finch-API/finch-api-go/commit/7f24bb0ff1a37cbedf05981b10d860f2f3034a15)) +* **tests:** bump steady to v0.19.6 ([ccb5268](https://github.com/Finch-API/finch-api-go/commit/ccb5268248f77e85c2873cb3c5df85cf91d69098)) +* **tests:** bump steady to v0.19.7 ([e14f2c7](https://github.com/Finch-API/finch-api-go/commit/e14f2c77eba0497a647d4716541e60a28707d000)) +* **tests:** bump steady to v0.20.1 ([ac6482c](https://github.com/Finch-API/finch-api-go/commit/ac6482cfa68f67125bb869a3175bbbd96f2b980a)) +* **tests:** bump steady to v0.20.2 ([b74a347](https://github.com/Finch-API/finch-api-go/commit/b74a347b3c26a85eb5e1e1735484af1f66f37ade)) +* **tests:** bump steady to v0.22.1 ([d53b177](https://github.com/Finch-API/finch-api-go/commit/d53b1771929eaba7226b50fe268445f1043b57f0)) +* update mock server docs ([55defed](https://github.com/Finch-API/finch-api-go/commit/55defede58d60e846a93fdc7623e2dea863a1e9e)) + + +### Documentation + +* prominently feature MCP server setup in root SDK readmes ([8d0c7b6](https://github.com/Finch-API/finch-api-go/commit/8d0c7b66a2c2dc08f0d93b299416c76e7fc4dd90)) + + +### Refactors + +* **tests:** switch from prism to steady ([484a07b](https://github.com/Finch-API/finch-api-go/commit/484a07bb959e9640ec3205ced8eed62f58c4a25f)) + ## 1.34.1 (2025-10-31) Full Changelog: [v1.34.0...v1.34.1](https://github.com/Finch-API/finch-api-go/compare/v1.34.0...v1.34.1) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1400be83..eb1605f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,11 +46,10 @@ $ go mod edit -replace github.com/Finch-API/finch-api-go=/path/to/finch-api-go ## Running tests -Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. +Most tests require you to [set up a mock server](https://github.com/dgellow/steady) against the OpenAPI spec to run the tests. ```sh -# you will need npm installed -$ npx prism mock path/to/your/openapi.yml +$ ./scripts/mock ``` ```sh diff --git a/LICENSE b/LICENSE index eee60015..489cf4d3 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Finch + Copyright 2026 Finch Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index bdb5a8cc..58c40559 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,15 @@ from applications written in Go. It is generated with [Stainless](https://www.stainless.com/). +## MCP Server + +Use the Finch MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. + +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40tryfinch%2Ffinch-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0cnlmaW5jaC9maW5jaC1hcGktbWNwIl0sImVudiI6eyJGSU5DSF9BQ0NFU1NfVE9LRU4iOiJNeSBBY2Nlc3MgVG9rZW4iLCJGSU5DSF9DTElFTlRfSUQiOiI0YWIxNWU1MS0xMWFkLTQ5ZjQtYWNhZS1mMzQzYjc3OTQzNzUiLCJGSU5DSF9DTElFTlRfU0VDUkVUIjoiTXkgQ2xpZW50IFNlY3JldCIsIkZJTkNIX1dFQkhPT0tfU0VDUkVUIjoiTXkgV2ViaG9vayBTZWNyZXQifX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40tryfinch%2Ffinch-api-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40tryfinch%2Ffinch-api-mcp%22%5D%2C%22env%22%3A%7B%22FINCH_ACCESS_TOKEN%22%3A%22My%20Access%20Token%22%2C%22FINCH_CLIENT_ID%22%3A%224ab15e51-11ad-49f4-acae-f343b7794375%22%2C%22FINCH_CLIENT_SECRET%22%3A%22My%20Client%20Secret%22%2C%22FINCH_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Secret%22%7D%7D) + +> Note: You may need to set environment variables in your MCP client. + ## Installation @@ -24,7 +33,7 @@ Or to pin the version: ```sh -go get -u 'github.com/Finch-API/finch-api-go@v1.34.1' +go get -u 'github.com/Finch-API/finch-api-go@v1.35.0' ``` @@ -199,7 +208,7 @@ When the API returns a non-success status code, we return an error with type To handle errors, we recommend that you use the `errors.As` pattern: ```go -_, err := client.HRIS.Company.Get(context.TODO()) +_, err := client.HRIS.Company.Get(context.TODO(), finchgo.HRISCompanyGetParams{}) if err != nil { var apierr *finchgo.Error if errors.As(err, &apierr) { @@ -267,6 +276,77 @@ client.HRIS.Directory.List( ) ``` +### Accessing raw response data (e.g. response headers) + +You can access the raw HTTP response data by using the `option.WithResponseInto()` request option. This is useful when +you need to examine response headers, status codes, or other details. + +```go +// Create a variable to store the HTTP response +var response *http.Response +page, err := client.HRIS.Directory.List( + context.TODO(), + finchgo.HRISDirectoryListParams{}, + option.WithResponseInto(&response), +) +if err != nil { + // handle error +} +fmt.Printf("%+v\n", page) + +fmt.Printf("Status Code: %d\n", response.StatusCode) +fmt.Printf("Headers: %+#v\n", response.Header) +``` + +### Making custom/undocumented requests + +This library is typed for convenient access to the documented API. If you need to access undocumented +endpoints, params, or response properties, the library can still be used. + +#### Undocumented endpoints + +To make requests to undocumented endpoints, you can use `client.Get`, `client.Post`, and other HTTP verbs. +`RequestOptions` on the client, such as retries, will be respected when making these requests. + +```go +var ( + // params can be an io.Reader, a []byte, an encoding/json serializable object, + // or a "…Params" struct defined in this library. + params map[string]interface{} + + // result can be an []byte, *http.Response, a encoding/json deserializable object, + // or a model defined in this library. + result *http.Response +) +err := client.Post(context.Background(), "/unspecified", params, &result) +if err != nil { + … +} +``` + +#### Undocumented request params + +To make requests using undocumented parameters, you may use either the `option.WithQuerySet()` +or the `option.WithJSONSet()` methods. + +```go +params := FooNewParams{ + ID: finchgo.F("id_xxxx"), + Data: finchgo.F(FooNewParamsData{ + FirstName: finchgo.F("John"), + }), +} +client.Foo.New(context.Background(), params, option.WithJSONSet("data.last_name", "Doe")) +``` + +#### Undocumented response properties + +To access undocumented response properties, you may either access the raw JSON of the response as a string +with `result.JSON.RawJSON()`, or get the raw JSON of a particular field on the result with +`result.JSON.Foo.Raw()`. + +Any fields that are not present on the response struct will be saved and can be accessed by `result.JSON.ExtraFields()` which returns the extra fields as a `map[string]Field`. + ### Middleware We provide `option.WithMiddleware` which applies the given diff --git a/accesstoken.go b/accesstoken.go index 61ebd7f2..623daa27 100644 --- a/accesstoken.go +++ b/accesstoken.go @@ -73,28 +73,30 @@ func (r *AccessTokenService) New(ctx context.Context, body AccessTokenNewParams, path := "auth/token" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } type CreateAccessTokenResponse struct { // The access token for the connection - AccessToken string `json:"access_token,required"` + AccessToken string `json:"access_token" api:"required"` // The type of application associated with a token. - ClientType CreateAccessTokenResponseClientType `json:"client_type,required"` + ClientType CreateAccessTokenResponseClientType `json:"client_type" api:"required"` // The Finch UUID of the connection associated with the `access_token` - ConnectionID string `json:"connection_id,required"` + ConnectionID string `json:"connection_id" api:"required"` // The type of the connection associated with the token. // // - `provider` - connection to an external provider // - `finch` - finch-generated data. - ConnectionType CreateAccessTokenResponseConnectionType `json:"connection_type,required"` + ConnectionType CreateAccessTokenResponseConnectionType `json:"connection_type" api:"required"` + // An array of entity IDs that can be accessed with this access token + EntityIDs []string `json:"entity_ids" api:"required" format:"uuid"` // An array of the authorized products associated with the `access_token` - Products []string `json:"products,required"` + Products []string `json:"products" api:"required"` // The ID of the provider associated with the `access_token` - ProviderID string `json:"provider_id,required"` + ProviderID string `json:"provider_id" api:"required"` // The RFC 8693 token type (Finch uses `bearer` tokens) - TokenType string `json:"token_type,required"` + TokenType string `json:"token_type" api:"required"` // [DEPRECATED] Use `connection_id` to identify the connection instead of this // account ID // @@ -107,8 +109,11 @@ type CreateAccessTokenResponse struct { CompanyID string `json:"company_id"` // The ID of your customer you provided to Finch when a connect session was created // for this connection - CustomerID string `json:"customer_id,nullable"` - JSON createAccessTokenResponseJSON `json:"-"` + CustomerID string `json:"customer_id" api:"nullable"` + // The name of your customer you provided to Finch when a connect session was + // created for this connection + CustomerName string `json:"customer_name" api:"nullable"` + JSON createAccessTokenResponseJSON `json:"-"` } // createAccessTokenResponseJSON contains the JSON metadata for the struct @@ -118,12 +123,14 @@ type createAccessTokenResponseJSON struct { ClientType apijson.Field ConnectionID apijson.Field ConnectionType apijson.Field + EntityIDs apijson.Field Products apijson.Field ProviderID apijson.Field TokenType apijson.Field AccountID apijson.Field CompanyID apijson.Field CustomerID apijson.Field + CustomerName apijson.Field raw string ExtraFields map[string]apijson.Field } @@ -174,7 +181,7 @@ func (r CreateAccessTokenResponseConnectionType) IsKnown() bool { type AccessTokenNewParams struct { // The authorization code received from the authorization server - Code param.Field[string] `json:"code,required"` + Code param.Field[string] `json:"code" api:"required"` // The client ID for your application ClientID param.Field[string] `json:"client_id" format:"uuid"` // The client secret for your application diff --git a/accesstoken_test.go b/accesstoken_test.go index 723297ec..93c2f426 100644 --- a/accesstoken_test.go +++ b/accesstoken_test.go @@ -14,6 +14,7 @@ import ( ) func TestAccessTokenNewWithOptionalParams(t *testing.T) { + t.Skip("prism doesnt like the format for the API-Version header") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -24,6 +25,8 @@ func TestAccessTokenNewWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.AccessTokens.New(context.TODO(), finchgo.AccessTokenNewParams{ Code: finchgo.F("code"), diff --git a/account.go b/account.go index 63b27fcd..ac574aef 100644 --- a/account.go +++ b/account.go @@ -9,6 +9,7 @@ import ( "slices" "github.com/Finch-API/finch-api-go/internal/apijson" + "github.com/Finch-API/finch-api-go/internal/param" "github.com/Finch-API/finch-api-go/internal/requestconfig" "github.com/Finch-API/finch-api-go/option" "github.com/Finch-API/finch-api-go/shared" @@ -36,23 +37,57 @@ func NewAccountService(opts ...option.RequestOption) (r *AccountService) { // Disconnect one or more `access_token`s from your application. func (r *AccountService) Disconnect(ctx context.Context, opts ...option.RequestOption) (res *DisconnectResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "disconnect" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...) - return + return res, err +} + +// Disconnect entity(s) from a connection without affecting other entities +// associated with the same connection. +func (r *AccountService) DisconnectEntity(ctx context.Context, body AccountDisconnectEntityParams, opts ...option.RequestOption) (res *DisconnectEntityResponse, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) + path := "disconnect-entity" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return res, err } // Read account information associated with an `access_token` func (r *AccountService) Introspect(ctx context.Context, opts ...option.RequestOption) (res *Introspection, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "introspect" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + return res, err +} + +type DisconnectEntityResponse struct { + // If the request is successful, Finch will return "success" (HTTP 200 status). + Status string `json:"status" api:"required"` + JSON disconnectEntityResponseJSON `json:"-"` +} + +// disconnectEntityResponseJSON contains the JSON metadata for the struct +// [DisconnectEntityResponse] +type disconnectEntityResponseJSON struct { + Status apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *DisconnectEntityResponse) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r disconnectEntityResponseJSON) RawJSON() string { + return r.raw } type DisconnectResponse struct { // If the request is successful, Finch will return "success" (HTTP 200 status). - Status string `json:"status,required"` + Status string `json:"status" api:"required"` JSON disconnectResponseJSON `json:"-"` } @@ -74,23 +109,23 @@ func (r disconnectResponseJSON) RawJSON() string { type Introspection struct { // The Finch UUID of the token being introspected - ID string `json:"id,required"` + ID string `json:"id" api:"required"` // The client ID of the application associated with the `access_token` - ClientID string `json:"client_id,required"` + ClientID string `json:"client_id" api:"required"` // The type of application associated with a token. - ClientType IntrospectionClientType `json:"client_type,required"` + ClientType IntrospectionClientType `json:"client_type" api:"required"` // The Finch UUID of the connection associated with the `access_token` - ConnectionID string `json:"connection_id,required"` - ConnectionStatus IntrospectionConnectionStatus `json:"connection_status,required"` + ConnectionID string `json:"connection_id" api:"required"` + ConnectionStatus IntrospectionConnectionStatus `json:"connection_status" api:"required"` // The type of the connection associated with the token. // // - `provider` - connection to an external provider // - `finch` - finch-generated data. - ConnectionType IntrospectionConnectionType `json:"connection_type,required"` + ConnectionType IntrospectionConnectionType `json:"connection_type" api:"required"` // An array of the authorized products associated with the `access_token`. - Products []string `json:"products,required"` + Products []string `json:"products" api:"required"` // The ID of the provider associated with the `access_token`. - ProviderID string `json:"provider_id,required"` + ProviderID string `json:"provider_id" api:"required"` // [DEPRECATED] Use `connection_id` to associate tokens with a Finch connection // instead of this account ID // @@ -104,13 +139,16 @@ type Introspection struct { CompanyID string `json:"company_id"` // The email of your customer you provided to Finch when a connect session was // created for this connection - CustomerEmail string `json:"customer_email,nullable"` + CustomerEmail string `json:"customer_email" api:"nullable"` // The ID of your customer you provided to Finch when a connect session was created // for this connection - CustomerID string `json:"customer_id,nullable"` + CustomerID string `json:"customer_id" api:"nullable"` // The name of your customer you provided to Finch when a connect session was // created for this connection - CustomerName string `json:"customer_name,nullable"` + CustomerName string `json:"customer_name" api:"nullable"` + // Array of detailed entity information for each connected account in multi-account + // mode + Entities []IntrospectionEntity `json:"entities"` // Whether the connection associated with the `access_token` uses the Assisted // Connect Flow. (`true` if using Assisted Connect, `false` if connection is // automated) @@ -121,7 +159,7 @@ type Introspection struct { // Deprecated: deprecated PayrollProviderID string `json:"payroll_provider_id"` // The account username used for login associated with the `access_token`. - Username string `json:"username,nullable"` + Username string `json:"username" api:"nullable"` JSON introspectionJSON `json:"-"` } @@ -141,6 +179,7 @@ type introspectionJSON struct { CustomerEmail apijson.Field CustomerID apijson.Field CustomerName apijson.Field + Entities apijson.Field Manual apijson.Field PayrollProviderID apijson.Field Username apijson.Field @@ -174,9 +213,9 @@ func (r IntrospectionClientType) IsKnown() bool { } type IntrospectionConnectionStatus struct { - Status shared.ConnectionStatusType `json:"status,required"` + Status shared.ConnectionStatusType `json:"status" api:"required"` // The datetime when the connection was last successfully synced - LastSuccessfulSync IntrospectionConnectionStatusLastSuccessfulSyncUnion `json:"last_successful_sync,nullable" format:"date-time"` + LastSuccessfulSync IntrospectionConnectionStatusLastSuccessfulSyncUnion `json:"last_successful_sync" api:"nullable" format:"date-time"` Message string `json:"message"` JSON introspectionConnectionStatusJSON `json:"-"` } @@ -242,7 +281,7 @@ func (r IntrospectionConnectionType) IsKnown() bool { type IntrospectionAuthenticationMethod struct { // The type of authentication method - Type IntrospectionAuthenticationMethodsType `json:"type,required"` + Type IntrospectionAuthenticationMethodsType `json:"type" api:"required"` ConnectionStatus IntrospectionAuthenticationMethodsConnectionStatus `json:"connection_status"` // An array of the authorized products associated with the `access_token` Products []string `json:"products"` @@ -287,9 +326,9 @@ func (r IntrospectionAuthenticationMethodsType) IsKnown() bool { } type IntrospectionAuthenticationMethodsConnectionStatus struct { - Status shared.ConnectionStatusType `json:"status,required"` + Status shared.ConnectionStatusType `json:"status" api:"required"` // The datetime when the connection was last successfully synced - LastSuccessfulSync IntrospectionAuthenticationMethodsConnectionStatusLastSuccessfulSyncUnion `json:"last_successful_sync,nullable" format:"date-time"` + LastSuccessfulSync IntrospectionAuthenticationMethodsConnectionStatusLastSuccessfulSyncUnion `json:"last_successful_sync" api:"nullable" format:"date-time"` Message string `json:"message"` JSON introspectionAuthenticationMethodsConnectionStatusJSON `json:"-"` } @@ -333,3 +372,64 @@ func init() { }, ) } + +type IntrospectionEntity struct { + // The connection account ID for this entity + ID string `json:"id" api:"required" format:"uuid"` + // The name of the entity (payroll provider company name) + Name string `json:"name" api:"required,nullable"` + // The source ID of the entity + SourceID string `json:"source_id" api:"required,nullable"` + // The status of the entity connection + Status IntrospectionEntitiesStatus `json:"status" api:"required"` + JSON introspectionEntityJSON `json:"-"` +} + +// introspectionEntityJSON contains the JSON metadata for the struct +// [IntrospectionEntity] +type introspectionEntityJSON struct { + ID apijson.Field + Name apijson.Field + SourceID apijson.Field + Status apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *IntrospectionEntity) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r introspectionEntityJSON) RawJSON() string { + return r.raw +} + +// The status of the entity connection +type IntrospectionEntitiesStatus string + +const ( + IntrospectionEntitiesStatusPending IntrospectionEntitiesStatus = "pending" + IntrospectionEntitiesStatusProcessing IntrospectionEntitiesStatus = "processing" + IntrospectionEntitiesStatusConnected IntrospectionEntitiesStatus = "connected" + IntrospectionEntitiesStatusErrorNoAccountSetup IntrospectionEntitiesStatus = "error_no_account_setup" + IntrospectionEntitiesStatusErrorPermissions IntrospectionEntitiesStatus = "error_permissions" + IntrospectionEntitiesStatusReauth IntrospectionEntitiesStatus = "reauth" + IntrospectionEntitiesStatusDisconnected IntrospectionEntitiesStatus = "disconnected" +) + +func (r IntrospectionEntitiesStatus) IsKnown() bool { + switch r { + case IntrospectionEntitiesStatusPending, IntrospectionEntitiesStatusProcessing, IntrospectionEntitiesStatusConnected, IntrospectionEntitiesStatusErrorNoAccountSetup, IntrospectionEntitiesStatusErrorPermissions, IntrospectionEntitiesStatusReauth, IntrospectionEntitiesStatusDisconnected: + return true + } + return false +} + +type AccountDisconnectEntityParams struct { + // Array of entity UUIDs to disconnect. At least one entity ID must be provided. + EntityIDs param.Field[[]string] `json:"entity_ids" api:"required" format:"uuid"` +} + +func (r AccountDisconnectEntityParams) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} diff --git a/account_test.go b/account_test.go index 47c22247..c62e5605 100644 --- a/account_test.go +++ b/account_test.go @@ -24,6 +24,8 @@ func TestAccountDisconnect(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Account.Disconnect(context.TODO()) if err != nil { @@ -35,6 +37,32 @@ func TestAccountDisconnect(t *testing.T) { } } +func TestAccountDisconnectEntity(t *testing.T) { + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := finchgo.NewClient( + option.WithBaseURL(baseURL), + option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), + ) + _, err := client.Account.DisconnectEntity(context.TODO(), finchgo.AccountDisconnectEntityParams{ + EntityIDs: finchgo.F([]string{"3c90c3cc-0d44-4b50-8888-8dd25736052a", "5e6f7a8b-9c10-4d11-a12b-c13d14e15f16"}), + }) + if err != nil { + var apierr *finchgo.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + func TestAccountIntrospect(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -46,6 +74,8 @@ func TestAccountIntrospect(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Account.Introspect(context.TODO()) if err != nil { diff --git a/api.md b/api.md index 8a5a397b..4e3b5576 100644 --- a/api.md +++ b/api.md @@ -1,6 +1,7 @@ # Shared Params Types - shared.ConnectionStatusType +- shared.OperationSupport # Shared Response Types @@ -24,7 +25,7 @@ Response Types: Methods: -- client.AccessTokens.New(ctx context.Context, body finchgo.AccessTokenNewParams) (finchgo.CreateAccessTokenResponse, error) +- client.AccessTokens.New(ctx context.Context, body finchgo.AccessTokenNewParams) (\*finchgo.CreateAccessTokenResponse, error) # HRIS @@ -47,7 +48,7 @@ Response Types: Methods: -- client.HRIS.Company.Get(ctx context.Context) (finchgo.Company, error) +- client.HRIS.Company.Get(ctx context.Context, query finchgo.HRISCompanyGetParams) (\*finchgo.Company, error) ### PayStatementItem @@ -57,7 +58,7 @@ Response Types: Methods: -- client.HRIS.Company.PayStatementItem.List(ctx context.Context, query finchgo.HRISCompanyPayStatementItemListParams) (pagination.ResponsesPage[finchgo.HRISCompanyPayStatementItemListResponse], error) +- client.HRIS.Company.PayStatementItem.List(ctx context.Context, query finchgo.HRISCompanyPayStatementItemListParams) (\*pagination.ResponsesPage[finchgo.HRISCompanyPayStatementItemListResponse], error) #### Rules @@ -70,10 +71,10 @@ Response Types: Methods: -- client.HRIS.Company.PayStatementItem.Rules.New(ctx context.Context, body finchgo.HRISCompanyPayStatementItemRuleNewParams) (finchgo.HRISCompanyPayStatementItemRuleNewResponse, error) -- client.HRIS.Company.PayStatementItem.Rules.Update(ctx context.Context, ruleID string, body finchgo.HRISCompanyPayStatementItemRuleUpdateParams) (finchgo.HRISCompanyPayStatementItemRuleUpdateResponse, error) -- client.HRIS.Company.PayStatementItem.Rules.List(ctx context.Context) (pagination.ResponsesPage[finchgo.HRISCompanyPayStatementItemRuleListResponse], error) -- client.HRIS.Company.PayStatementItem.Rules.Delete(ctx context.Context, ruleID string) (finchgo.HRISCompanyPayStatementItemRuleDeleteResponse, error) +- client.HRIS.Company.PayStatementItem.Rules.New(ctx context.Context, params finchgo.HRISCompanyPayStatementItemRuleNewParams) (\*finchgo.HRISCompanyPayStatementItemRuleNewResponse, error) +- client.HRIS.Company.PayStatementItem.Rules.Update(ctx context.Context, ruleID string, params finchgo.HRISCompanyPayStatementItemRuleUpdateParams) (\*finchgo.HRISCompanyPayStatementItemRuleUpdateResponse, error) +- client.HRIS.Company.PayStatementItem.Rules.List(ctx context.Context, query finchgo.HRISCompanyPayStatementItemRuleListParams) (\*pagination.ResponsesPage[finchgo.HRISCompanyPayStatementItemRuleListResponse], error) +- client.HRIS.Company.PayStatementItem.Rules.Delete(ctx context.Context, ruleID string, body finchgo.HRISCompanyPayStatementItemRuleDeleteParams) (\*finchgo.HRISCompanyPayStatementItemRuleDeleteResponse, error) ## Directory @@ -83,7 +84,7 @@ Response Types: Methods: -- client.HRIS.Directory.List(ctx context.Context, query finchgo.HRISDirectoryListParams) (finchgo.IndividualsPage, error) +- client.HRIS.Directory.List(ctx context.Context, query finchgo.HRISDirectoryListParams) (\*finchgo.IndividualsPage, error) ## Individuals @@ -94,7 +95,7 @@ Response Types: Methods: -- client.HRIS.Individuals.GetMany(ctx context.Context, body finchgo.HRISIndividualGetManyParams) (pagination.ResponsesPage[finchgo.IndividualResponse], error) +- client.HRIS.Individuals.GetMany(ctx context.Context, params finchgo.HRISIndividualGetManyParams) (\*pagination.ResponsesPage[finchgo.IndividualResponse], error) ## Employments @@ -105,7 +106,7 @@ Response Types: Methods: -- client.HRIS.Employments.GetMany(ctx context.Context, body finchgo.HRISEmploymentGetManyParams) (pagination.ResponsesPage[finchgo.EmploymentDataResponse], error) +- client.HRIS.Employments.GetMany(ctx context.Context, params finchgo.HRISEmploymentGetManyParams) (\*pagination.ResponsesPage[finchgo.EmploymentDataResponse], error) ## Payments @@ -115,7 +116,7 @@ Response Types: Methods: -- client.HRIS.Payments.List(ctx context.Context, query finchgo.HRISPaymentListParams) (pagination.SinglePage[finchgo.Payment], error) +- client.HRIS.Payments.List(ctx context.Context, query finchgo.HRISPaymentListParams) (\*pagination.SinglePage[finchgo.Payment], error) ## PayStatements @@ -128,7 +129,7 @@ Response Types: Methods: -- client.HRIS.PayStatements.GetMany(ctx context.Context, body finchgo.HRISPayStatementGetManyParams) (pagination.ResponsesPage[finchgo.PayStatementResponse], error) +- client.HRIS.PayStatements.GetMany(ctx context.Context, params finchgo.HRISPayStatementGetManyParams) (\*pagination.ResponsesPage[finchgo.PayStatementResponse], error) ## Documents @@ -142,8 +143,8 @@ Response Types: Methods: -- client.HRIS.Documents.List(ctx context.Context, query finchgo.HRISDocumentListParams) (finchgo.HRISDocumentListResponse, error) -- client.HRIS.Documents.Retreive(ctx context.Context, documentID string) (finchgo.HRISDocumentRetreiveResponse, error) +- client.HRIS.Documents.List(ctx context.Context, query finchgo.HRISDocumentListParams) (\*finchgo.HRISDocumentListResponse, error) +- client.HRIS.Documents.Retreive(ctx context.Context, documentID string, query finchgo.HRISDocumentRetreiveParams) (\*finchgo.HRISDocumentRetreiveResponse, error) ## Benefits @@ -160,17 +161,19 @@ Response Types: - finchgo.BenefitsSupport - finchgo.CompanyBenefit - finchgo.CreateCompanyBenefitsResponse +- finchgo.RegisterCompanyBenefitResponse - finchgo.SupportPerBenefitType - finchgo.SupportedBenefit - finchgo.UpdateCompanyBenefitResponse Methods: -- client.HRIS.Benefits.New(ctx context.Context, body finchgo.HRISBenefitNewParams) (finchgo.CreateCompanyBenefitsResponse, error) -- client.HRIS.Benefits.Get(ctx context.Context, benefitID string) (finchgo.CompanyBenefit, error) -- client.HRIS.Benefits.Update(ctx context.Context, benefitID string, body finchgo.HRISBenefitUpdateParams) (finchgo.UpdateCompanyBenefitResponse, error) -- client.HRIS.Benefits.List(ctx context.Context) (pagination.SinglePage[finchgo.CompanyBenefit], error) -- client.HRIS.Benefits.ListSupportedBenefits(ctx context.Context) (pagination.SinglePage[finchgo.SupportedBenefit], error) +- client.HRIS.Benefits.New(ctx context.Context, params finchgo.HRISBenefitNewParams) (\*finchgo.CreateCompanyBenefitsResponse, error) +- client.HRIS.Benefits.Get(ctx context.Context, benefitID string, query finchgo.HRISBenefitGetParams) (\*finchgo.CompanyBenefit, error) +- client.HRIS.Benefits.Update(ctx context.Context, benefitID string, params finchgo.HRISBenefitUpdateParams) (\*finchgo.UpdateCompanyBenefitResponse, error) +- client.HRIS.Benefits.List(ctx context.Context, query finchgo.HRISBenefitListParams) (\*pagination.SinglePage[finchgo.CompanyBenefit], error) +- client.HRIS.Benefits.ListSupportedBenefits(ctx context.Context, query finchgo.HRISBenefitListSupportedBenefitsParams) (\*pagination.SinglePage[finchgo.SupportedBenefit], error) +- client.HRIS.Benefits.Register(ctx context.Context, params finchgo.HRISBenefitRegisterParams) (\*finchgo.RegisterCompanyBenefitResponse, error) ### Individuals @@ -182,9 +185,9 @@ Response Types: Methods: -- client.HRIS.Benefits.Individuals.EnrolledIDs(ctx context.Context, benefitID string) (finchgo.HRISBenefitIndividualEnrolledIDsResponse, error) -- client.HRIS.Benefits.Individuals.GetManyBenefits(ctx context.Context, benefitID string, query finchgo.HRISBenefitIndividualGetManyBenefitsParams) (pagination.SinglePage[finchgo.IndividualBenefit], error) -- client.HRIS.Benefits.Individuals.UnenrollMany(ctx context.Context, benefitID string, body finchgo.HRISBenefitIndividualUnenrollManyParams) (finchgo.UnenrolledIndividualBenefitResponse, error) +- client.HRIS.Benefits.Individuals.EnrolledIDs(ctx context.Context, benefitID string, query finchgo.HRISBenefitIndividualEnrolledIDsParams) (\*finchgo.HRISBenefitIndividualEnrolledIDsResponse, error) +- client.HRIS.Benefits.Individuals.GetManyBenefits(ctx context.Context, benefitID string, query finchgo.HRISBenefitIndividualGetManyBenefitsParams) (\*pagination.SinglePage[finchgo.IndividualBenefit], error) +- client.HRIS.Benefits.Individuals.UnenrollMany(ctx context.Context, benefitID string, params finchgo.HRISBenefitIndividualUnenrollManyParams) (\*finchgo.UnenrolledIndividualBenefitResponse, error) # Providers @@ -194,19 +197,21 @@ Response Types: Methods: -- client.Providers.List(ctx context.Context) (pagination.SinglePage[finchgo.ProviderListResponse], error) +- client.Providers.List(ctx context.Context) (\*pagination.SinglePage[finchgo.ProviderListResponse], error) # Account Response Types: +- finchgo.DisconnectEntityResponse - finchgo.DisconnectResponse - finchgo.Introspection Methods: -- client.Account.Disconnect(ctx context.Context) (finchgo.DisconnectResponse, error) -- client.Account.Introspect(ctx context.Context) (finchgo.Introspection, error) +- client.Account.Disconnect(ctx context.Context) (\*finchgo.DisconnectResponse, error) +- client.Account.DisconnectEntity(ctx context.Context, body finchgo.AccountDisconnectEntityParams) (\*finchgo.DisconnectEntityResponse, error) +- client.Account.Introspect(ctx context.Context) (\*finchgo.Introspection, error) # Webhooks @@ -236,7 +241,7 @@ Response Types: Methods: -- client.RequestForwarding.Forward(ctx context.Context, body finchgo.RequestForwardingForwardParams) (finchgo.RequestForwardingForwardResponse, error) +- client.RequestForwarding.Forward(ctx context.Context, body finchgo.RequestForwardingForwardParams) (\*finchgo.RequestForwardingForwardResponse, error) # Jobs @@ -250,9 +255,9 @@ Response Types: Methods: -- client.Jobs.Automated.New(ctx context.Context, body finchgo.JobAutomatedNewParams) (finchgo.JobAutomatedNewResponse, error) -- client.Jobs.Automated.Get(ctx context.Context, jobID string) (finchgo.AutomatedAsyncJob, error) -- client.Jobs.Automated.List(ctx context.Context, query finchgo.JobAutomatedListParams) (finchgo.JobAutomatedListResponse, error) +- client.Jobs.Automated.New(ctx context.Context, body finchgo.JobAutomatedNewParams) (\*finchgo.JobAutomatedNewResponse, error) +- client.Jobs.Automated.Get(ctx context.Context, jobID string) (\*finchgo.AutomatedAsyncJob, error) +- client.Jobs.Automated.List(ctx context.Context, query finchgo.JobAutomatedListParams) (\*finchgo.JobAutomatedListResponse, error) ## Manual @@ -262,7 +267,7 @@ Response Types: Methods: -- client.Jobs.Manual.Get(ctx context.Context, jobID string) (finchgo.ManualAsyncJob, error) +- client.Jobs.Manual.Get(ctx context.Context, jobID string) (\*finchgo.ManualAsyncJob, error) # Sandbox @@ -274,7 +279,7 @@ Response Types: Methods: -- client.Sandbox.Connections.New(ctx context.Context, body finchgo.SandboxConnectionNewParams) (finchgo.SandboxConnectionNewResponse, error) +- client.Sandbox.Connections.New(ctx context.Context, body finchgo.SandboxConnectionNewParams) (\*finchgo.SandboxConnectionNewResponse, error) ### Accounts @@ -285,8 +290,8 @@ Response Types: Methods: -- client.Sandbox.Connections.Accounts.New(ctx context.Context, body finchgo.SandboxConnectionAccountNewParams) (finchgo.SandboxConnectionAccountNewResponse, error) -- client.Sandbox.Connections.Accounts.Update(ctx context.Context, body finchgo.SandboxConnectionAccountUpdateParams) (finchgo.SandboxConnectionAccountUpdateResponse, error) +- client.Sandbox.Connections.Accounts.New(ctx context.Context, body finchgo.SandboxConnectionAccountNewParams) (\*finchgo.SandboxConnectionAccountNewResponse, error) +- client.Sandbox.Connections.Accounts.Update(ctx context.Context, body finchgo.SandboxConnectionAccountUpdateParams) (\*finchgo.SandboxConnectionAccountUpdateResponse, error) ## Company @@ -296,7 +301,7 @@ Response Types: Methods: -- client.Sandbox.Company.Update(ctx context.Context, body finchgo.SandboxCompanyUpdateParams) (finchgo.SandboxCompanyUpdateResponse, error) +- client.Sandbox.Company.Update(ctx context.Context, body finchgo.SandboxCompanyUpdateParams) (\*finchgo.SandboxCompanyUpdateResponse, error) ## Directory @@ -306,7 +311,7 @@ Response Types: Methods: -- client.Sandbox.Directory.New(ctx context.Context, body finchgo.SandboxDirectoryNewParams) ([]finchgo.SandboxDirectoryNewResponse, error) +- client.Sandbox.Directory.New(ctx context.Context, body finchgo.SandboxDirectoryNewParams) (\*[]finchgo.SandboxDirectoryNewResponse, error) ## Individual @@ -316,7 +321,7 @@ Response Types: Methods: -- client.Sandbox.Individual.Update(ctx context.Context, individualID string, body finchgo.SandboxIndividualUpdateParams) (finchgo.SandboxIndividualUpdateResponse, error) +- client.Sandbox.Individual.Update(ctx context.Context, individualID string, body finchgo.SandboxIndividualUpdateParams) (\*finchgo.SandboxIndividualUpdateResponse, error) ## Employment @@ -326,7 +331,7 @@ Response Types: Methods: -- client.Sandbox.Employment.Update(ctx context.Context, individualID string, body finchgo.SandboxEmploymentUpdateParams) (finchgo.SandboxEmploymentUpdateResponse, error) +- client.Sandbox.Employment.Update(ctx context.Context, individualID string, body finchgo.SandboxEmploymentUpdateParams) (\*finchgo.SandboxEmploymentUpdateResponse, error) ## Payment @@ -336,7 +341,7 @@ Response Types: Methods: -- client.Sandbox.Payment.New(ctx context.Context, body finchgo.SandboxPaymentNewParams) (finchgo.SandboxPaymentNewResponse, error) +- client.Sandbox.Payment.New(ctx context.Context, body finchgo.SandboxPaymentNewParams) (\*finchgo.SandboxPaymentNewResponse, error) ## Jobs @@ -346,7 +351,7 @@ Response Types: Methods: -- client.Sandbox.Jobs.New(ctx context.Context, body finchgo.SandboxJobNewParams) (finchgo.SandboxJobNewResponse, error) +- client.Sandbox.Jobs.New(ctx context.Context, body finchgo.SandboxJobNewParams) (\*finchgo.SandboxJobNewResponse, error) ### Configuration @@ -356,8 +361,8 @@ Response Types: Methods: -- client.Sandbox.Jobs.Configuration.Get(ctx context.Context) ([]finchgo.SandboxJobConfiguration, error) -- client.Sandbox.Jobs.Configuration.Update(ctx context.Context, body finchgo.SandboxJobConfigurationUpdateParams) (finchgo.SandboxJobConfiguration, error) +- client.Sandbox.Jobs.Configuration.Get(ctx context.Context) (\*[]finchgo.SandboxJobConfiguration, error) +- client.Sandbox.Jobs.Configuration.Update(ctx context.Context, body finchgo.SandboxJobConfigurationUpdateParams) (\*finchgo.SandboxJobConfiguration, error) # Payroll @@ -370,8 +375,8 @@ Response Types: Methods: -- client.Payroll.PayGroups.Get(ctx context.Context, payGroupID string) (finchgo.PayrollPayGroupGetResponse, error) -- client.Payroll.PayGroups.List(ctx context.Context, query finchgo.PayrollPayGroupListParams) (pagination.SinglePage[finchgo.PayrollPayGroupListResponse], error) +- client.Payroll.PayGroups.Get(ctx context.Context, payGroupID string, query finchgo.PayrollPayGroupGetParams) (\*finchgo.PayrollPayGroupGetResponse, error) +- client.Payroll.PayGroups.List(ctx context.Context, query finchgo.PayrollPayGroupListParams) (\*pagination.SinglePage[finchgo.PayrollPayGroupListResponse], error) # Connect @@ -384,5 +389,5 @@ Response Types: Methods: -- client.Connect.Sessions.New(ctx context.Context, body finchgo.ConnectSessionNewParams) (finchgo.ConnectSessionNewResponse, error) -- client.Connect.Sessions.Reauthenticate(ctx context.Context, body finchgo.ConnectSessionReauthenticateParams) (finchgo.ConnectSessionReauthenticateResponse, error) +- client.Connect.Sessions.New(ctx context.Context, body finchgo.ConnectSessionNewParams) (\*finchgo.ConnectSessionNewResponse, error) +- client.Connect.Sessions.Reauthenticate(ctx context.Context, body finchgo.ConnectSessionReauthenticateParams) (\*finchgo.ConnectSessionReauthenticateResponse, error) diff --git a/client.go b/client.go index 88c5876e..ec122a86 100644 --- a/client.go +++ b/client.go @@ -12,6 +12,7 @@ import ( "os" "strconv" "slices" + "strings" "github.com/Finch-API/finch-api-go/internal/requestconfig" "github.com/Finch-API/finch-api-go/option" @@ -38,7 +39,7 @@ type Client struct { // FINCH_CLIENT_SECRET, FINCH_WEBHOOK_SECRET, FINCH_BASE_URL). This should be used // to initialize new clients. func DefaultClientOptions() []option.RequestOption { - defaults := []option.RequestOption{option.WithEnvironmentProduction()} + defaults := []option.RequestOption{option.WithHTTPClient(defaultHTTPClient()), option.WithEnvironmentProduction()} if o, ok := os.LookupEnv("FINCH_BASE_URL"); ok { defaults = append(defaults, option.WithBaseURL(o)) } @@ -51,6 +52,14 @@ func DefaultClientOptions() []option.RequestOption { if o, ok := os.LookupEnv("FINCH_WEBHOOK_SECRET"); ok { defaults = append(defaults, option.WithWebhookSecret(o)) } + if o, ok := os.LookupEnv("FINCH_CUSTOM_HEADERS"); ok { + for _, line := range strings.Split(o, "\n") { + colon := strings.Index(line, ":") + if colon >= 0 { + defaults = append(defaults, option.WithHeader(strings.TrimSpace(line[:colon]), strings.TrimSpace(line[colon+1:]))) + } + } + } return defaults } diff --git a/client_test.go b/client_test.go index 36abf9c5..dd4a8bcd 100644 --- a/client_test.go +++ b/client_test.go @@ -27,6 +27,8 @@ func TestUserAgentHeader(t *testing.T) { var userAgent string client := finchgo.NewClient( option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -38,7 +40,7 @@ func TestUserAgentHeader(t *testing.T) { }, }), ) - client.HRIS.Directory.List(context.Background(), finchgo.HRISDirectoryListParams{}) + _, _ = client.HRIS.Directory.List(context.Background(), finchgo.HRISDirectoryListParams{}) if userAgent != fmt.Sprintf("Finch/Go %s", internal.PackageVersion) { t.Errorf("Expected User-Agent to be correct, but got: %#v", userAgent) } @@ -48,6 +50,8 @@ func TestRetryAfter(t *testing.T) { retryCountHeaders := make([]string, 0) client := finchgo.NewClient( option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -82,6 +86,8 @@ func TestDeleteRetryCountHeader(t *testing.T) { retryCountHeaders := make([]string, 0) client := finchgo.NewClient( option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -112,6 +118,8 @@ func TestOverwriteRetryCountHeader(t *testing.T) { retryCountHeaders := make([]string, 0) client := finchgo.NewClient( option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -142,6 +150,8 @@ func TestRetryAfterMs(t *testing.T) { attempts := 0 client := finchgo.NewClient( option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -168,6 +178,8 @@ func TestRetryAfterMs(t *testing.T) { func TestContextCancel(t *testing.T) { client := finchgo.NewClient( option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -188,6 +200,8 @@ func TestContextCancel(t *testing.T) { func TestContextCancelDelay(t *testing.T) { client := finchgo.NewClient( option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { @@ -216,6 +230,8 @@ func TestContextDeadline(t *testing.T) { go func() { client := finchgo.NewClient( option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), option.WithHTTPClient(&http.Client{ Transport: &closureTransport{ fn: func(req *http.Request) (*http.Response, error) { diff --git a/connectsession.go b/connectsession.go index db8b4dc6..f3adf735 100644 --- a/connectsession.go +++ b/connectsession.go @@ -34,25 +34,27 @@ func NewConnectSessionService(opts ...option.RequestOption) (r *ConnectSessionSe // Create a new connect session for an employer func (r *ConnectSessionService) New(ctx context.Context, body ConnectSessionNewParams, opts ...option.RequestOption) (res *ConnectSessionNewResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBasicAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "connect/sessions" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } // Create a new Connect session for reauthenticating an existing connection func (r *ConnectSessionService) Reauthenticate(ctx context.Context, body ConnectSessionReauthenticateParams, opts ...option.RequestOption) (res *ConnectSessionReauthenticateResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBasicAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "connect/sessions/reauthenticate" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } type ConnectSessionNewResponse struct { // The Connect URL to redirect the user to for authentication - ConnectURL string `json:"connect_url,required" format:"uri"` + ConnectURL string `json:"connect_url" api:"required" format:"uri"` // The unique identifier for the created connect session - SessionID string `json:"session_id,required"` + SessionID string `json:"session_id" api:"required"` JSON connectSessionNewResponseJSON `json:"-"` } @@ -75,9 +77,9 @@ func (r connectSessionNewResponseJSON) RawJSON() string { type ConnectSessionReauthenticateResponse struct { // The Connect URL to redirect the user to for reauthentication - ConnectURL string `json:"connect_url,required" format:"uri"` + ConnectURL string `json:"connect_url" api:"required" format:"uri"` // The unique identifier for the created connect session - SessionID string `json:"session_id,required"` + SessionID string `json:"session_id" api:"required"` JSON connectSessionReauthenticateResponseJSON `json:"-"` } @@ -99,37 +101,62 @@ func (r connectSessionReauthenticateResponseJSON) RawJSON() string { } type ConnectSessionNewParams struct { - // Email address of the customer - CustomerEmail param.Field[string] `json:"customer_email,required" format:"email"` // Unique identifier for the customer - CustomerID param.Field[string] `json:"customer_id,required"` + CustomerID param.Field[string] `json:"customer_id" api:"required"` // Name of the customer - CustomerName param.Field[string] `json:"customer_name,required"` + CustomerName param.Field[string] `json:"customer_name" api:"required"` + // The Finch products to request access to. Use `benefits` to access deductions + // endpoints — `deduction` is a deprecated alias that is still accepted but should + // not be combined with `benefits`. + Products param.Field[[]ConnectSessionNewParamsProduct] `json:"products" api:"required"` + // Email address of the customer + CustomerEmail param.Field[string] `json:"customer_email" format:"email"` // Integration configuration for the connect session - Integration param.Field[ConnectSessionNewParamsIntegration] `json:"integration,required"` + Integration param.Field[ConnectSessionNewParamsIntegration] `json:"integration"` // Enable manual authentication mode - Manual param.Field[bool] `json:"manual,required"` + Manual param.Field[bool] `json:"manual"` // The number of minutes until the session expires (defaults to 129,600, which is // 90 days) - MinutesToExpire param.Field[float64] `json:"minutes_to_expire,required"` - // The Finch products to request access to - Products param.Field[[]ConnectSessionNewParamsProduct] `json:"products,required"` + MinutesToExpire param.Field[float64] `json:"minutes_to_expire"` // The URI to redirect to after the Connect flow is completed - RedirectUri param.Field[string] `json:"redirect_uri,required"` + RedirectUri param.Field[string] `json:"redirect_uri"` // Sandbox mode for testing - Sandbox param.Field[ConnectSessionNewParamsSandbox] `json:"sandbox,required"` + Sandbox param.Field[ConnectSessionNewParamsSandbox] `json:"sandbox"` } func (r ConnectSessionNewParams) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } +type ConnectSessionNewParamsProduct string + +const ( + ConnectSessionNewParamsProductBenefits ConnectSessionNewParamsProduct = "benefits" + ConnectSessionNewParamsProductCompany ConnectSessionNewParamsProduct = "company" + ConnectSessionNewParamsProductDeduction ConnectSessionNewParamsProduct = "deduction" + ConnectSessionNewParamsProductDirectory ConnectSessionNewParamsProduct = "directory" + ConnectSessionNewParamsProductDocuments ConnectSessionNewParamsProduct = "documents" + ConnectSessionNewParamsProductEmployment ConnectSessionNewParamsProduct = "employment" + ConnectSessionNewParamsProductIndividual ConnectSessionNewParamsProduct = "individual" + ConnectSessionNewParamsProductPayment ConnectSessionNewParamsProduct = "payment" + ConnectSessionNewParamsProductPayStatement ConnectSessionNewParamsProduct = "pay_statement" + ConnectSessionNewParamsProductSsn ConnectSessionNewParamsProduct = "ssn" +) + +func (r ConnectSessionNewParamsProduct) IsKnown() bool { + switch r { + case ConnectSessionNewParamsProductBenefits, ConnectSessionNewParamsProductCompany, ConnectSessionNewParamsProductDeduction, ConnectSessionNewParamsProductDirectory, ConnectSessionNewParamsProductDocuments, ConnectSessionNewParamsProductEmployment, ConnectSessionNewParamsProductIndividual, ConnectSessionNewParamsProductPayment, ConnectSessionNewParamsProductPayStatement, ConnectSessionNewParamsProductSsn: + return true + } + return false +} + // Integration configuration for the connect session type ConnectSessionNewParamsIntegration struct { - // The authentication method to use - AuthMethod param.Field[ConnectSessionNewParamsIntegrationAuthMethod] `json:"auth_method,required"` // The provider to integrate with - Provider param.Field[string] `json:"provider,required"` + Provider param.Field[string] `json:"provider" api:"required"` + // The authentication method to use + AuthMethod param.Field[ConnectSessionNewParamsIntegrationAuthMethod] `json:"auth_method"` } func (r ConnectSessionNewParamsIntegration) MarshalJSON() (data []byte, err error) { @@ -154,30 +181,6 @@ func (r ConnectSessionNewParamsIntegrationAuthMethod) IsKnown() bool { return false } -// The Finch products that can be requested during the Connect flow. -type ConnectSessionNewParamsProduct string - -const ( - ConnectSessionNewParamsProductBenefits ConnectSessionNewParamsProduct = "benefits" - ConnectSessionNewParamsProductCompany ConnectSessionNewParamsProduct = "company" - ConnectSessionNewParamsProductDeduction ConnectSessionNewParamsProduct = "deduction" - ConnectSessionNewParamsProductDirectory ConnectSessionNewParamsProduct = "directory" - ConnectSessionNewParamsProductDocuments ConnectSessionNewParamsProduct = "documents" - ConnectSessionNewParamsProductEmployment ConnectSessionNewParamsProduct = "employment" - ConnectSessionNewParamsProductIndividual ConnectSessionNewParamsProduct = "individual" - ConnectSessionNewParamsProductPayment ConnectSessionNewParamsProduct = "payment" - ConnectSessionNewParamsProductPayStatement ConnectSessionNewParamsProduct = "pay_statement" - ConnectSessionNewParamsProductSsn ConnectSessionNewParamsProduct = "ssn" -) - -func (r ConnectSessionNewParamsProduct) IsKnown() bool { - switch r { - case ConnectSessionNewParamsProductBenefits, ConnectSessionNewParamsProductCompany, ConnectSessionNewParamsProductDeduction, ConnectSessionNewParamsProductDirectory, ConnectSessionNewParamsProductDocuments, ConnectSessionNewParamsProductEmployment, ConnectSessionNewParamsProductIndividual, ConnectSessionNewParamsProductPayment, ConnectSessionNewParamsProductPayStatement, ConnectSessionNewParamsProductSsn: - return true - } - return false -} - // Sandbox mode for testing type ConnectSessionNewParamsSandbox string @@ -196,21 +199,22 @@ func (r ConnectSessionNewParamsSandbox) IsKnown() bool { type ConnectSessionReauthenticateParams struct { // The ID of the existing connection to reauthenticate - ConnectionID param.Field[string] `json:"connection_id,required"` + ConnectionID param.Field[string] `json:"connection_id" api:"required"` // The number of minutes until the session expires (defaults to 43,200, which is 30 // days) - MinutesToExpire param.Field[int64] `json:"minutes_to_expire,required"` - // The products to request access to (optional for reauthentication) - Products param.Field[[]ConnectSessionReauthenticateParamsProduct] `json:"products,required"` + MinutesToExpire param.Field[int64] `json:"minutes_to_expire"` + // The products to request access to (optional for reauthentication). Use + // `benefits` to access deductions endpoints — `deduction` is a deprecated alias + // that is still accepted but should not be combined with `benefits`. + Products param.Field[[]ConnectSessionReauthenticateParamsProduct] `json:"products"` // The URI to redirect to after the Connect flow is completed - RedirectUri param.Field[string] `json:"redirect_uri,required" format:"uri"` + RedirectUri param.Field[string] `json:"redirect_uri" format:"uri"` } func (r ConnectSessionReauthenticateParams) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } -// The Finch products that can be requested during the Connect flow. type ConnectSessionReauthenticateParamsProduct string const ( diff --git a/connectsession_test.go b/connectsession_test.go index 18c76f63..699e4298 100644 --- a/connectsession_test.go +++ b/connectsession_test.go @@ -13,7 +13,7 @@ import ( "github.com/Finch-API/finch-api-go/option" ) -func TestConnectSessionNew(t *testing.T) { +func TestConnectSessionNewWithOptionalParams(t *testing.T) { t.Skip("prism tests are broken") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -25,18 +25,20 @@ func TestConnectSessionNew(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Connect.Sessions.New(context.TODO(), finchgo.ConnectSessionNewParams{ - CustomerEmail: finchgo.F("dev@stainless.com"), CustomerID: finchgo.F("x"), CustomerName: finchgo.F("x"), + Products: finchgo.F([]finchgo.ConnectSessionNewParamsProduct{finchgo.ConnectSessionNewParamsProductBenefits}), + CustomerEmail: finchgo.F("dev@stainless.com"), Integration: finchgo.F(finchgo.ConnectSessionNewParamsIntegration{ - AuthMethod: finchgo.F(finchgo.ConnectSessionNewParamsIntegrationAuthMethodAssisted), Provider: finchgo.F("provider"), + AuthMethod: finchgo.F(finchgo.ConnectSessionNewParamsIntegrationAuthMethodAssisted), }), Manual: finchgo.F(true), MinutesToExpire: finchgo.F(1.000000), - Products: finchgo.F([]finchgo.ConnectSessionNewParamsProduct{finchgo.ConnectSessionNewParamsProductBenefits}), RedirectUri: finchgo.F("redirect_uri"), Sandbox: finchgo.F(finchgo.ConnectSessionNewParamsSandboxFinch), }) @@ -49,7 +51,7 @@ func TestConnectSessionNew(t *testing.T) { } } -func TestConnectSessionReauthenticate(t *testing.T) { +func TestConnectSessionReauthenticateWithOptionalParams(t *testing.T) { t.Skip("prism tests are broken") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -61,6 +63,8 @@ func TestConnectSessionReauthenticate(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Connect.Sessions.Reauthenticate(context.TODO(), finchgo.ConnectSessionReauthenticateParams{ ConnectionID: finchgo.F("connection_id"), diff --git a/default_http_client.go b/default_http_client.go new file mode 100644 index 00000000..829fee9e --- /dev/null +++ b/default_http_client.go @@ -0,0 +1,30 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package finchgo + +import ( + "net/http" + "time" +) + +// defaultResponseHeaderTimeout bounds the time between a fully written request +// and the server's response headers. It does not apply to the response body, +// so long-running streams are unaffected. Without this, a server that accepts +// the connection but never responds would hang the request indefinitely. +const defaultResponseHeaderTimeout = 10 * time.Minute + +// defaultHTTPClient returns an [*http.Client] used when the caller does not +// supply one via [option.WithHTTPClient]. When [http.DefaultTransport] is the +// stdlib [*http.Transport], it is cloned and a [http.Transport.ResponseHeaderTimeout] +// is set so stuck connections fail fast instead of compounding across retries. +// If [http.DefaultTransport] has been wrapped (for example by otelhttp for +// distributed tracing), the wrapping is preserved and the header timeout is +// skipped. +func defaultHTTPClient() *http.Client { + if t, ok := http.DefaultTransport.(*http.Transport); ok { + t = t.Clone() + t.ResponseHeaderTimeout = defaultResponseHeaderTimeout + return &http.Client{Transport: t} + } + return &http.Client{Transport: http.DefaultTransport} +} diff --git a/hris.go b/hris.go index 7e6b2bba..c373f835 100644 --- a/hris.go +++ b/hris.go @@ -50,14 +50,14 @@ func NewHRISService(opts ...option.RequestOption) (r *HRISService) { // depending on what information the provider returns. type Income struct { // The income amount in cents. - Amount int64 `json:"amount,required,nullable"` + Amount int64 `json:"amount" api:"required,nullable"` // The currency code. - Currency string `json:"currency,required,nullable"` + Currency string `json:"currency" api:"required,nullable"` // The date the income amount went into effect. - EffectiveDate time.Time `json:"effective_date,required,nullable" format:"date"` + EffectiveDate time.Time `json:"effective_date" api:"required,nullable" format:"date"` // The income unit of payment. Options: `yearly`, `quarterly`, `monthly`, // `semi_monthly`, `bi_weekly`, `weekly`, `daily`, `hourly`, and `fixed`. - Unit IncomeUnit `json:"unit,required,nullable"` + Unit IncomeUnit `json:"unit" api:"required,nullable"` JSON incomeJSON `json:"-"` } @@ -108,14 +108,14 @@ func (r IncomeUnit) IsKnown() bool { // depending on what information the provider returns. type IncomeParam struct { // The income amount in cents. - Amount param.Field[int64] `json:"amount,required"` + Amount param.Field[int64] `json:"amount" api:"required"` // The currency code. - Currency param.Field[string] `json:"currency,required"` + Currency param.Field[string] `json:"currency" api:"required"` // The date the income amount went into effect. - EffectiveDate param.Field[time.Time] `json:"effective_date,required" format:"date"` + EffectiveDate param.Field[time.Time] `json:"effective_date" api:"required" format:"date"` // The income unit of payment. Options: `yearly`, `quarterly`, `monthly`, // `semi_monthly`, `bi_weekly`, `weekly`, `daily`, `hourly`, and `fixed`. - Unit param.Field[IncomeUnit] `json:"unit,required"` + Unit param.Field[IncomeUnit] `json:"unit" api:"required"` } func (r IncomeParam) MarshalJSON() (data []byte, err error) { @@ -124,19 +124,19 @@ func (r IncomeParam) MarshalJSON() (data []byte, err error) { type Location struct { // City, district, suburb, town, or village. - City string `json:"city,required,nullable"` + City string `json:"city" api:"required,nullable"` // The 2-letter ISO 3166 country code. - Country string `json:"country,required,nullable"` + Country string `json:"country" api:"required,nullable"` // Street address or PO box. - Line1 string `json:"line1,required,nullable"` + Line1 string `json:"line1" api:"required,nullable"` // Apartment, suite, unit, or building. - Line2 string `json:"line2,required,nullable"` + Line2 string `json:"line2" api:"required,nullable"` // The postal code or zip code. - PostalCode string `json:"postal_code,required,nullable"` + PostalCode string `json:"postal_code" api:"required,nullable"` // The state code. - State string `json:"state,required,nullable"` - Name string `json:"name,nullable"` - SourceID string `json:"source_id,nullable"` + State string `json:"state" api:"required,nullable"` + Name string `json:"name" api:"nullable"` + SourceID string `json:"source_id" api:"nullable"` JSON locationJSON `json:"-"` } @@ -164,17 +164,17 @@ func (r locationJSON) RawJSON() string { type LocationParam struct { // City, district, suburb, town, or village. - City param.Field[string] `json:"city,required"` + City param.Field[string] `json:"city" api:"required"` // The 2-letter ISO 3166 country code. - Country param.Field[string] `json:"country,required"` + Country param.Field[string] `json:"country" api:"required"` // Street address or PO box. - Line1 param.Field[string] `json:"line1,required"` + Line1 param.Field[string] `json:"line1" api:"required"` // Apartment, suite, unit, or building. - Line2 param.Field[string] `json:"line2,required"` + Line2 param.Field[string] `json:"line2" api:"required"` // The postal code or zip code. - PostalCode param.Field[string] `json:"postal_code,required"` + PostalCode param.Field[string] `json:"postal_code" api:"required"` // The state code. - State param.Field[string] `json:"state,required"` + State param.Field[string] `json:"state" api:"required"` Name param.Field[string] `json:"name"` SourceID param.Field[string] `json:"source_id"` } @@ -185,8 +185,8 @@ func (r LocationParam) MarshalJSON() (data []byte, err error) { type Money struct { // Amount for money object (in cents) - Amount int64 `json:"amount,required,nullable"` - Currency string `json:"currency,required"` + Amount int64 `json:"amount" api:"required,nullable"` + Currency string `json:"currency" api:"required"` JSON moneyJSON `json:"-"` } diff --git a/hrisbenefit.go b/hrisbenefit.go index 6606f6e3..e42cfc20 100644 --- a/hrisbenefit.go +++ b/hrisbenefit.go @@ -7,9 +7,11 @@ import ( "errors" "fmt" "net/http" + "net/url" "slices" "github.com/Finch-API/finch-api-go/internal/apijson" + "github.com/Finch-API/finch-api-go/internal/apiquery" "github.com/Finch-API/finch-api-go/internal/param" "github.com/Finch-API/finch-api-go/internal/requestconfig" "github.com/Finch-API/finch-api-go/option" @@ -40,44 +42,48 @@ func NewHRISBenefitService(opts ...option.RequestOption) (r *HRISBenefitService) // Creates a new company-wide deduction or contribution. Please use the // `/providers` endpoint to view available types for each provider. -func (r *HRISBenefitService) New(ctx context.Context, body HRISBenefitNewParams, opts ...option.RequestOption) (res *CreateCompanyBenefitsResponse, err error) { - opts = slices.Concat(r.Options, opts) +func (r *HRISBenefitService) New(ctx context.Context, params HRISBenefitNewParams, opts ...option.RequestOption) (res *CreateCompanyBenefitsResponse, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "employer/benefits" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...) + return res, err } // Lists deductions and contributions information for a given item -func (r *HRISBenefitService) Get(ctx context.Context, benefitID string, opts ...option.RequestOption) (res *CompanyBenefit, err error) { - opts = slices.Concat(r.Options, opts) +func (r *HRISBenefitService) Get(ctx context.Context, benefitID string, query HRISBenefitGetParams, opts ...option.RequestOption) (res *CompanyBenefit, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) if benefitID == "" { err = errors.New("missing required benefit_id parameter") - return + return nil, err } path := fmt.Sprintf("employer/benefits/%s", benefitID) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) + return res, err } // Updates an existing company-wide deduction or contribution -func (r *HRISBenefitService) Update(ctx context.Context, benefitID string, body HRISBenefitUpdateParams, opts ...option.RequestOption) (res *UpdateCompanyBenefitResponse, err error) { - opts = slices.Concat(r.Options, opts) +func (r *HRISBenefitService) Update(ctx context.Context, benefitID string, params HRISBenefitUpdateParams, opts ...option.RequestOption) (res *UpdateCompanyBenefitResponse, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) if benefitID == "" { err = errors.New("missing required benefit_id parameter") - return + return nil, err } path := fmt.Sprintf("employer/benefits/%s", benefitID) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...) + return res, err } // List all company-wide deductions and contributions. -func (r *HRISBenefitService) List(ctx context.Context, opts ...option.RequestOption) (res *pagination.SinglePage[CompanyBenefit], err error) { +func (r *HRISBenefitService) List(ctx context.Context, query HRISBenefitListParams, opts ...option.RequestOption) (res *pagination.SinglePage[CompanyBenefit], err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) path := "employer/benefits" - cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, nil, &res, opts...) + cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, query, &res, opts...) if err != nil { return nil, err } @@ -90,17 +96,18 @@ func (r *HRISBenefitService) List(ctx context.Context, opts ...option.RequestOpt } // List all company-wide deductions and contributions. -func (r *HRISBenefitService) ListAutoPaging(ctx context.Context, opts ...option.RequestOption) *pagination.SinglePageAutoPager[CompanyBenefit] { - return pagination.NewSinglePageAutoPager(r.List(ctx, opts...)) +func (r *HRISBenefitService) ListAutoPaging(ctx context.Context, query HRISBenefitListParams, opts ...option.RequestOption) *pagination.SinglePageAutoPager[CompanyBenefit] { + return pagination.NewSinglePageAutoPager(r.List(ctx, query, opts...)) } // Get deductions metadata -func (r *HRISBenefitService) ListSupportedBenefits(ctx context.Context, opts ...option.RequestOption) (res *pagination.SinglePage[SupportedBenefit], err error) { +func (r *HRISBenefitService) ListSupportedBenefits(ctx context.Context, query HRISBenefitListSupportedBenefitsParams, opts ...option.RequestOption) (res *pagination.SinglePage[SupportedBenefit], err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) path := "employer/benefits/meta" - cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, nil, &res, opts...) + cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, query, &res, opts...) if err != nil { return nil, err } @@ -113,8 +120,18 @@ func (r *HRISBenefitService) ListSupportedBenefits(ctx context.Context, opts ... } // Get deductions metadata -func (r *HRISBenefitService) ListSupportedBenefitsAutoPaging(ctx context.Context, opts ...option.RequestOption) *pagination.SinglePageAutoPager[SupportedBenefit] { - return pagination.NewSinglePageAutoPager(r.ListSupportedBenefits(ctx, opts...)) +func (r *HRISBenefitService) ListSupportedBenefitsAutoPaging(ctx context.Context, query HRISBenefitListSupportedBenefitsParams, opts ...option.RequestOption) *pagination.SinglePageAutoPager[SupportedBenefit] { + return pagination.NewSinglePageAutoPager(r.ListSupportedBenefits(ctx, query, opts...)) +} + +// Register existing benefits from the customer on the provider, on Finch's end. +// Please use the `/provider` endpoint to view available types for each provider. +func (r *HRISBenefitService) Register(ctx context.Context, params HRISBenefitRegisterParams, opts ...option.RequestOption) (res *RegisterCompanyBenefitResponse, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) + path := "employer/benefits/register" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...) + return res, err } type BenefitFeaturesAndOperations struct { @@ -193,19 +210,19 @@ func (r BenefitType) IsKnown() bool { // Each benefit type and their supported features. If the benefit type is not // supported, the property will be null type BenefitsSupport struct { - Commuter BenefitFeaturesAndOperations `json:"commuter,nullable"` - CustomPostTax BenefitFeaturesAndOperations `json:"custom_post_tax,nullable"` - CustomPreTax BenefitFeaturesAndOperations `json:"custom_pre_tax,nullable"` - FsaDependentCare BenefitFeaturesAndOperations `json:"fsa_dependent_care,nullable"` - FsaMedical BenefitFeaturesAndOperations `json:"fsa_medical,nullable"` - HsaPost BenefitFeaturesAndOperations `json:"hsa_post,nullable"` - HsaPre BenefitFeaturesAndOperations `json:"hsa_pre,nullable"` - S125Dental BenefitFeaturesAndOperations `json:"s125_dental,nullable"` - S125Medical BenefitFeaturesAndOperations `json:"s125_medical,nullable"` - S125Vision BenefitFeaturesAndOperations `json:"s125_vision,nullable"` - Simple BenefitFeaturesAndOperations `json:"simple,nullable"` - SimpleIRA BenefitFeaturesAndOperations `json:"simple_ira,nullable"` - ExtraFields map[string]BenefitFeaturesAndOperations `json:"-,extras"` + Commuter BenefitFeaturesAndOperations `json:"commuter" api:"nullable"` + CustomPostTax BenefitFeaturesAndOperations `json:"custom_post_tax" api:"nullable"` + CustomPreTax BenefitFeaturesAndOperations `json:"custom_pre_tax" api:"nullable"` + FsaDependentCare BenefitFeaturesAndOperations `json:"fsa_dependent_care" api:"nullable"` + FsaMedical BenefitFeaturesAndOperations `json:"fsa_medical" api:"nullable"` + HsaPost BenefitFeaturesAndOperations `json:"hsa_post" api:"nullable"` + HsaPre BenefitFeaturesAndOperations `json:"hsa_pre" api:"nullable"` + S125Dental BenefitFeaturesAndOperations `json:"s125_dental" api:"nullable"` + S125Medical BenefitFeaturesAndOperations `json:"s125_medical" api:"nullable"` + S125Vision BenefitFeaturesAndOperations `json:"s125_vision" api:"nullable"` + Simple BenefitFeaturesAndOperations `json:"simple" api:"nullable"` + SimpleIRA BenefitFeaturesAndOperations `json:"simple_ira" api:"nullable"` + ExtraFields map[string]BenefitFeaturesAndOperations `json:"-" api:"extrafields"` JSON benefitsSupportJSON `json:"-"` } @@ -237,14 +254,14 @@ func (r benefitsSupportJSON) RawJSON() string { type CompanyBenefit struct { // The id of the benefit. - BenefitID string `json:"benefit_id,required" format:"uuid"` - Description string `json:"description,required,nullable"` + BenefitID string `json:"benefit_id" api:"required" format:"uuid"` + Description string `json:"description" api:"required,nullable"` // The frequency of the benefit deduction/contribution. - Frequency BenefitFrequency `json:"frequency,required,nullable"` + Frequency BenefitFrequency `json:"frequency" api:"required,nullable"` // Type of benefit. - Type BenefitType `json:"type,required,nullable"` + Type BenefitType `json:"type" api:"required,nullable"` // The company match for this benefit. - CompanyContribution CompanyBenefitCompanyContribution `json:"company_contribution,nullable"` + CompanyContribution CompanyBenefitCompanyContribution `json:"company_contribution" api:"nullable"` JSON companyBenefitJSON `json:"-"` } @@ -269,8 +286,8 @@ func (r companyBenefitJSON) RawJSON() string { // The company match for this benefit. type CompanyBenefitCompanyContribution struct { - Tiers []CompanyBenefitCompanyContributionTier `json:"tiers,required"` - Type CompanyBenefitCompanyContributionType `json:"type,required"` + Tiers []CompanyBenefitCompanyContributionTier `json:"tiers" api:"required"` + Type CompanyBenefitCompanyContributionType `json:"type" api:"required"` JSON companyBenefitCompanyContributionJSON `json:"-"` } @@ -292,8 +309,8 @@ func (r companyBenefitCompanyContributionJSON) RawJSON() string { } type CompanyBenefitCompanyContributionTier struct { - Match int64 `json:"match,required"` - Threshold int64 `json:"threshold,required"` + Match int64 `json:"match" api:"required"` + Threshold int64 `json:"threshold" api:"required"` JSON companyBenefitCompanyContributionTierJSON `json:"-"` } @@ -330,8 +347,8 @@ func (r CompanyBenefitCompanyContributionType) IsKnown() bool { type CreateCompanyBenefitsResponse struct { // The id of the benefit. - BenefitID string `json:"benefit_id,required" format:"uuid"` - JobID string `json:"job_id,required" format:"uuid"` + BenefitID string `json:"benefit_id" api:"required" format:"uuid"` + JobID string `json:"job_id" api:"required" format:"uuid"` JSON createCompanyBenefitsResponseJSON `json:"-"` } @@ -352,6 +369,30 @@ func (r createCompanyBenefitsResponseJSON) RawJSON() string { return r.raw } +type RegisterCompanyBenefitResponse struct { + // The id of the benefit. + BenefitID string `json:"benefit_id" api:"required" format:"uuid"` + JobID string `json:"job_id" api:"required" format:"uuid"` + JSON registerCompanyBenefitResponseJSON `json:"-"` +} + +// registerCompanyBenefitResponseJSON contains the JSON metadata for the struct +// [RegisterCompanyBenefitResponse] +type registerCompanyBenefitResponseJSON struct { + BenefitID apijson.Field + JobID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *RegisterCompanyBenefitResponse) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r registerCompanyBenefitResponseJSON) RawJSON() string { + return r.raw +} + type SupportPerBenefitType struct { CompanyBenefits shared.OperationSupportMatrix `json:"company_benefits"` IndividualBenefits shared.OperationSupportMatrix `json:"individual_benefits"` @@ -377,22 +418,22 @@ func (r supportPerBenefitTypeJSON) RawJSON() string { type SupportedBenefit struct { // Whether the provider supports an annual maximum for this benefit. - AnnualMaximum bool `json:"annual_maximum,required,nullable"` + AnnualMaximum bool `json:"annual_maximum" api:"required,nullable"` // Supported contribution types. An empty array indicates contributions are not // supported. - CompanyContribution []SupportedBenefitCompanyContribution `json:"company_contribution,required,nullable"` - Description string `json:"description,required,nullable"` + CompanyContribution []SupportedBenefitCompanyContribution `json:"company_contribution" api:"required,nullable"` + Description string `json:"description" api:"required,nullable"` // Supported deduction types. An empty array indicates deductions are not // supported. - EmployeeDeduction []SupportedBenefitEmployeeDeduction `json:"employee_deduction,required,nullable"` + EmployeeDeduction []SupportedBenefitEmployeeDeduction `json:"employee_deduction" api:"required,nullable"` // The list of frequencies supported by the provider for this benefit - Frequencies []BenefitFrequency `json:"frequencies,required"` + Frequencies []BenefitFrequency `json:"frequencies" api:"required"` // Whether the provider supports catch up for this benefit. This field will only be // true for retirement benefits. - CatchUp bool `json:"catch_up,nullable"` + CatchUp bool `json:"catch_up" api:"nullable"` // Whether the provider supports HSA contribution limits. Empty if this feature is // not supported for the benefit. This array only has values for HSA benefits. - HsaContributionLimit []SupportedBenefitHsaContributionLimit `json:"hsa_contribution_limit,nullable"` + HsaContributionLimit []SupportedBenefitHsaContributionLimit `json:"hsa_contribution_limit" api:"nullable"` JSON supportedBenefitJSON `json:"-"` } @@ -466,8 +507,8 @@ func (r SupportedBenefitHsaContributionLimit) IsKnown() bool { type UpdateCompanyBenefitResponse struct { // The id of the benefit. - BenefitID string `json:"benefit_id,required" format:"uuid"` - JobID string `json:"job_id,required" format:"uuid"` + BenefitID string `json:"benefit_id" api:"required" format:"uuid"` + JobID string `json:"job_id" api:"required" format:"uuid"` JSON updateCompanyBenefitResponseJSON `json:"-"` } @@ -489,6 +530,8 @@ func (r updateCompanyBenefitResponseJSON) RawJSON() string { } type HRISBenefitNewParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` // The company match for this benefit. CompanyContribution param.Field[HRISBenefitNewParamsCompanyContribution] `json:"company_contribution"` // Name of the benefit as it appears in the provider and pay statements. Recommend @@ -505,10 +548,18 @@ func (r HRISBenefitNewParams) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } +// URLQuery serializes [HRISBenefitNewParams]'s query parameters as `url.Values`. +func (r HRISBenefitNewParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + // The company match for this benefit. type HRISBenefitNewParamsCompanyContribution struct { - Tiers param.Field[[]HRISBenefitNewParamsCompanyContributionTier] `json:"tiers,required"` - Type param.Field[HRISBenefitNewParamsCompanyContributionType] `json:"type,required"` + Tiers param.Field[[]HRISBenefitNewParamsCompanyContributionTier] `json:"tiers" api:"required"` + Type param.Field[HRISBenefitNewParamsCompanyContributionType] `json:"type" api:"required"` } func (r HRISBenefitNewParamsCompanyContribution) MarshalJSON() (data []byte, err error) { @@ -516,8 +567,8 @@ func (r HRISBenefitNewParamsCompanyContribution) MarshalJSON() (data []byte, err } type HRISBenefitNewParamsCompanyContributionTier struct { - Match param.Field[int64] `json:"match,required"` - Threshold param.Field[int64] `json:"threshold,required"` + Match param.Field[int64] `json:"match" api:"required"` + Threshold param.Field[int64] `json:"threshold" api:"required"` } func (r HRISBenefitNewParamsCompanyContributionTier) MarshalJSON() (data []byte, err error) { @@ -538,7 +589,22 @@ func (r HRISBenefitNewParamsCompanyContributionType) IsKnown() bool { return false } +type HRISBenefitGetParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` +} + +// URLQuery serializes [HRISBenefitGetParams]'s query parameters as `url.Values`. +func (r HRISBenefitGetParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + type HRISBenefitUpdateParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` // Updated name or description. Description param.Field[string] `json:"description"` } @@ -546,3 +612,62 @@ type HRISBenefitUpdateParams struct { func (r HRISBenefitUpdateParams) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } + +// URLQuery serializes [HRISBenefitUpdateParams]'s query parameters as +// `url.Values`. +func (r HRISBenefitUpdateParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + +type HRISBenefitListParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` +} + +// URLQuery serializes [HRISBenefitListParams]'s query parameters as `url.Values`. +func (r HRISBenefitListParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + +type HRISBenefitListSupportedBenefitsParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` +} + +// URLQuery serializes [HRISBenefitListSupportedBenefitsParams]'s query parameters +// as `url.Values`. +func (r HRISBenefitListSupportedBenefitsParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + +type HRISBenefitRegisterParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` + Description param.Field[string] `json:"description"` + // The frequency of the benefit deduction/contribution. + Frequency param.Field[BenefitFrequency] `json:"frequency"` + // Type of benefit. + Type param.Field[BenefitType] `json:"type"` +} + +func (r HRISBenefitRegisterParams) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +// URLQuery serializes [HRISBenefitRegisterParams]'s query parameters as +// `url.Values`. +func (r HRISBenefitRegisterParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} diff --git a/hrisbenefit_test.go b/hrisbenefit_test.go index 92e0b822..d29326a4 100644 --- a/hrisbenefit_test.go +++ b/hrisbenefit_test.go @@ -24,8 +24,11 @@ func TestHRISBenefitNewWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Benefits.New(context.TODO(), finchgo.HRISBenefitNewParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), CompanyContribution: finchgo.F(finchgo.HRISBenefitNewParamsCompanyContribution{ Tiers: finchgo.F([]finchgo.HRISBenefitNewParamsCompanyContributionTier{{ Match: finchgo.F(int64(1)), @@ -46,7 +49,7 @@ func TestHRISBenefitNewWithOptionalParams(t *testing.T) { } } -func TestHRISBenefitGet(t *testing.T) { +func TestHRISBenefitGetWithOptionalParams(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -57,8 +60,16 @@ func TestHRISBenefitGet(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), + ) + _, err := client.HRIS.Benefits.Get( + context.TODO(), + "benefit_id", + finchgo.HRISBenefitGetParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), + }, ) - _, err := client.HRIS.Benefits.Get(context.TODO(), "benefit_id") if err != nil { var apierr *finchgo.Error if errors.As(err, &apierr) { @@ -79,11 +90,14 @@ func TestHRISBenefitUpdateWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Benefits.Update( context.TODO(), "benefit_id", finchgo.HRISBenefitUpdateParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), Description: finchgo.F("description"), }, ) @@ -96,7 +110,33 @@ func TestHRISBenefitUpdateWithOptionalParams(t *testing.T) { } } -func TestHRISBenefitList(t *testing.T) { +func TestHRISBenefitListWithOptionalParams(t *testing.T) { + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := finchgo.NewClient( + option.WithBaseURL(baseURL), + option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), + ) + _, err := client.HRIS.Benefits.List(context.TODO(), finchgo.HRISBenefitListParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), + }) + if err != nil { + var apierr *finchgo.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestHRISBenefitListSupportedBenefitsWithOptionalParams(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -107,8 +147,12 @@ func TestHRISBenefitList(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) - _, err := client.HRIS.Benefits.List(context.TODO()) + _, err := client.HRIS.Benefits.ListSupportedBenefits(context.TODO(), finchgo.HRISBenefitListSupportedBenefitsParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), + }) if err != nil { var apierr *finchgo.Error if errors.As(err, &apierr) { @@ -118,7 +162,7 @@ func TestHRISBenefitList(t *testing.T) { } } -func TestHRISBenefitListSupportedBenefits(t *testing.T) { +func TestHRISBenefitRegisterWithOptionalParams(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -129,8 +173,15 @@ func TestHRISBenefitListSupportedBenefits(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) - _, err := client.HRIS.Benefits.ListSupportedBenefits(context.TODO()) + _, err := client.HRIS.Benefits.Register(context.TODO(), finchgo.HRISBenefitRegisterParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), + Description: finchgo.F("description"), + Frequency: finchgo.F(finchgo.BenefitFrequencyEveryPaycheck), + Type: finchgo.F(finchgo.BenefitType_457), + }) if err != nil { var apierr *finchgo.Error if errors.As(err, &apierr) { diff --git a/hrisbenefitindividual.go b/hrisbenefitindividual.go index e1cb1900..9d139385 100644 --- a/hrisbenefitindividual.go +++ b/hrisbenefitindividual.go @@ -40,25 +40,27 @@ func NewHRISBenefitIndividualService(opts ...option.RequestOption) (r *HRISBenef } // Lists individuals currently enrolled in a given deduction. -func (r *HRISBenefitIndividualService) EnrolledIDs(ctx context.Context, benefitID string, opts ...option.RequestOption) (res *HRISBenefitIndividualEnrolledIDsResponse, err error) { - opts = slices.Concat(r.Options, opts) +func (r *HRISBenefitIndividualService) EnrolledIDs(ctx context.Context, benefitID string, query HRISBenefitIndividualEnrolledIDsParams, opts ...option.RequestOption) (res *HRISBenefitIndividualEnrolledIDsResponse, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) if benefitID == "" { err = errors.New("missing required benefit_id parameter") - return + return nil, err } path := fmt.Sprintf("employer/benefits/%s/enrolled", benefitID) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) + return res, err } // Get enrollment information for the given individuals. func (r *HRISBenefitIndividualService) GetManyBenefits(ctx context.Context, benefitID string, query HRISBenefitIndividualGetManyBenefitsParams, opts ...option.RequestOption) (res *pagination.SinglePage[IndividualBenefit], err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) if benefitID == "" { err = errors.New("missing required benefit_id parameter") - return + return nil, err } path := fmt.Sprintf("employer/benefits/%s/individuals", benefitID) cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, query, &res, opts...) @@ -79,21 +81,22 @@ func (r *HRISBenefitIndividualService) GetManyBenefitsAutoPaging(ctx context.Con } // Unenroll individuals from a deduction or contribution -func (r *HRISBenefitIndividualService) UnenrollMany(ctx context.Context, benefitID string, body HRISBenefitIndividualUnenrollManyParams, opts ...option.RequestOption) (res *UnenrolledIndividualBenefitResponse, err error) { - opts = slices.Concat(r.Options, opts) +func (r *HRISBenefitIndividualService) UnenrollMany(ctx context.Context, benefitID string, params HRISBenefitIndividualUnenrollManyParams, opts ...option.RequestOption) (res *UnenrolledIndividualBenefitResponse, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) if benefitID == "" { err = errors.New("missing required benefit_id parameter") - return + return nil, err } path := fmt.Sprintf("employer/benefits/%s/individuals", benefitID) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, body, &res, opts...) - return + err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, params, &res, opts...) + return res, err } type IndividualBenefit struct { - Body IndividualBenefitBody `json:"body,required"` - Code int64 `json:"code,required"` - IndividualID string `json:"individual_id,required"` + Body IndividualBenefitBody `json:"body" api:"required"` + Code int64 `json:"code" api:"required"` + IndividualID string `json:"individual_id" api:"required"` JSON individualBenefitJSON `json:"-"` } @@ -117,20 +120,20 @@ func (r individualBenefitJSON) RawJSON() string { type IndividualBenefitBody struct { // If the benefit supports annual maximum, the amount in cents for this individual. - AnnualMaximum int64 `json:"annual_maximum,nullable"` + AnnualMaximum int64 `json:"annual_maximum" api:"nullable"` // If the benefit supports catch up (401k, 403b, etc.), whether catch up is enabled // for this individual. - CatchUp bool `json:"catch_up,nullable"` + CatchUp bool `json:"catch_up" api:"nullable"` Code float64 `json:"code"` // This field can have the runtime type of - // [IndividualBenefitBodyObjectCompanyContribution]. + // [IndividualBenefitBodyIndividualBenefitCompanyContribution]. CompanyContribution interface{} `json:"company_contribution"` // This field can have the runtime type of - // [IndividualBenefitBodyObjectEmployeeDeduction]. + // [IndividualBenefitBodyIndividualBenefitEmployeeDeduction]. EmployeeDeduction interface{} `json:"employee_deduction"` FinchCode string `json:"finch_code"` // Type for HSA contribution limit if the benefit is a HSA. - HsaContributionLimit IndividualBenefitBodyHsaContributionLimit `json:"hsa_contribution_limit,nullable"` + HsaContributionLimit IndividualBenefitBodyHsaContributionLimit `json:"hsa_contribution_limit" api:"nullable"` Message string `json:"message"` Name string `json:"name"` JSON individualBenefitBodyJSON `json:"-"` @@ -169,13 +172,13 @@ func (r *IndividualBenefitBody) UnmarshalJSON(data []byte) (err error) { // AsUnion returns a [IndividualBenefitBodyUnion] interface which you can cast to // the specific types for more type safety. // -// Possible runtime types of the union are [IndividualBenefitBodyObject], -// [IndividualBenefitBodyBatchError]. +// Possible runtime types of the union are +// [IndividualBenefitBodyIndividualBenefit], [IndividualBenefitBodyBatchError]. func (r IndividualBenefitBody) AsUnion() IndividualBenefitBodyUnion { return r.union } -// Union satisfied by [IndividualBenefitBodyObject] or +// Union satisfied by [IndividualBenefitBodyIndividualBenefit] or // [IndividualBenefitBodyBatchError]. type IndividualBenefitBodyUnion interface { implementsIndividualBenefitBody() @@ -187,7 +190,7 @@ func init() { "", apijson.UnionVariant{ TypeFilter: gjson.JSON, - Type: reflect.TypeOf(IndividualBenefitBodyObject{}), + Type: reflect.TypeOf(IndividualBenefitBodyIndividualBenefit{}), }, apijson.UnionVariant{ TypeFilter: gjson.JSON, @@ -196,22 +199,27 @@ func init() { ) } -type IndividualBenefitBodyObject struct { +type IndividualBenefitBodyIndividualBenefit struct { // If the benefit supports annual maximum, the amount in cents for this individual. - AnnualMaximum int64 `json:"annual_maximum,required,nullable"` + AnnualMaximum int64 `json:"annual_maximum" api:"required,nullable"` // If the benefit supports catch up (401k, 403b, etc.), whether catch up is enabled // for this individual. - CatchUp bool `json:"catch_up,required,nullable"` - CompanyContribution IndividualBenefitBodyObjectCompanyContribution `json:"company_contribution,required,nullable"` - EmployeeDeduction IndividualBenefitBodyObjectEmployeeDeduction `json:"employee_deduction,required,nullable"` + CatchUp bool `json:"catch_up" api:"required,nullable"` + // Company contribution configuration. Supports fixed amounts (in cents), + // percentage-based contributions (in basis points where 100 = 1%), or tiered + // matching structures. + CompanyContribution IndividualBenefitBodyIndividualBenefitCompanyContribution `json:"company_contribution" api:"required,nullable"` + // Employee deduction configuration. Supports both fixed amounts (in cents) and + // percentage-based contributions (in basis points where 100 = 1%). + EmployeeDeduction IndividualBenefitBodyIndividualBenefitEmployeeDeduction `json:"employee_deduction" api:"required,nullable"` // Type for HSA contribution limit if the benefit is a HSA. - HsaContributionLimit IndividualBenefitBodyObjectHsaContributionLimit `json:"hsa_contribution_limit,nullable"` - JSON individualBenefitBodyObjectJSON `json:"-"` + HsaContributionLimit IndividualBenefitBodyIndividualBenefitHsaContributionLimit `json:"hsa_contribution_limit" api:"nullable"` + JSON individualBenefitBodyIndividualBenefitJSON `json:"-"` } -// individualBenefitBodyObjectJSON contains the JSON metadata for the struct -// [IndividualBenefitBodyObject] -type individualBenefitBodyObjectJSON struct { +// individualBenefitBodyIndividualBenefitJSON contains the JSON metadata for the +// struct [IndividualBenefitBodyIndividualBenefit] +type individualBenefitBodyIndividualBenefitJSON struct { AnnualMaximum apijson.Field CatchUp apijson.Field CompanyContribution apijson.Field @@ -221,31 +229,37 @@ type individualBenefitBodyObjectJSON struct { ExtraFields map[string]apijson.Field } -func (r *IndividualBenefitBodyObject) UnmarshalJSON(data []byte) (err error) { +func (r *IndividualBenefitBodyIndividualBenefit) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -func (r individualBenefitBodyObjectJSON) RawJSON() string { +func (r individualBenefitBodyIndividualBenefitJSON) RawJSON() string { return r.raw } -func (r IndividualBenefitBodyObject) implementsIndividualBenefitBody() {} +func (r IndividualBenefitBodyIndividualBenefit) implementsIndividualBenefitBody() {} -type IndividualBenefitBodyObjectCompanyContribution struct { - // Fixed contribution type. - Type IndividualBenefitBodyObjectCompanyContributionType `json:"type,required"` - // Contribution amount in cents. +// Company contribution configuration. Supports fixed amounts (in cents), +// percentage-based contributions (in basis points where 100 = 1%), or tiered +// matching structures. +type IndividualBenefitBodyIndividualBenefitCompanyContribution struct { + // Contribution type. Supported values: "fixed" (amount in cents), "percent" + // (amount in basis points), or "tiered" (multi-tier matching). + Type IndividualBenefitBodyIndividualBenefitCompanyContributionType `json:"type" api:"required"` + // Contribution amount in cents (for type=fixed) or basis points (for type=percent, + // where 100 = 1%). Not used for type=tiered. Amount int64 `json:"amount"` // This field can have the runtime type of - // [[]IndividualBenefitBodyObjectCompanyContributionObjectTier]. - Tiers interface{} `json:"tiers"` - JSON individualBenefitBodyObjectCompanyContributionJSON `json:"-"` - union IndividualBenefitBodyObjectCompanyContributionUnion + // [[]IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredTier]. + Tiers interface{} `json:"tiers"` + JSON individualBenefitBodyIndividualBenefitCompanyContributionJSON `json:"-"` + union IndividualBenefitBodyIndividualBenefitCompanyContributionUnion } -// individualBenefitBodyObjectCompanyContributionJSON contains the JSON metadata -// for the struct [IndividualBenefitBodyObjectCompanyContribution] -type individualBenefitBodyObjectCompanyContributionJSON struct { +// individualBenefitBodyIndividualBenefitCompanyContributionJSON contains the JSON +// metadata for the struct +// [IndividualBenefitBodyIndividualBenefitCompanyContribution] +type individualBenefitBodyIndividualBenefitCompanyContributionJSON struct { Type apijson.Field Amount apijson.Field Tiers apijson.Field @@ -253,12 +267,12 @@ type individualBenefitBodyObjectCompanyContributionJSON struct { ExtraFields map[string]apijson.Field } -func (r individualBenefitBodyObjectCompanyContributionJSON) RawJSON() string { +func (r individualBenefitBodyIndividualBenefitCompanyContributionJSON) RawJSON() string { return r.raw } -func (r *IndividualBenefitBodyObjectCompanyContribution) UnmarshalJSON(data []byte) (err error) { - *r = IndividualBenefitBodyObjectCompanyContribution{} +func (r *IndividualBenefitBodyIndividualBenefitCompanyContribution) UnmarshalJSON(data []byte) (err error) { + *r = IndividualBenefitBodyIndividualBenefitCompanyContribution{} err = apijson.UnmarshalRoot(data, &r.union) if err != nil { return err @@ -266,127 +280,262 @@ func (r *IndividualBenefitBodyObjectCompanyContribution) UnmarshalJSON(data []by return apijson.Port(r.union, &r) } -// AsUnion returns a [IndividualBenefitBodyObjectCompanyContributionUnion] -// interface which you can cast to the specific types for more type safety. +// AsUnion returns a +// [IndividualBenefitBodyIndividualBenefitCompanyContributionUnion] interface which +// you can cast to the specific types for more type safety. // // Possible runtime types of the union are -// [IndividualBenefitBodyObjectCompanyContributionObject], -// [IndividualBenefitBodyObjectCompanyContributionObject], -// [IndividualBenefitBodyObjectCompanyContributionObject]. -func (r IndividualBenefitBodyObjectCompanyContribution) AsUnion() IndividualBenefitBodyObjectCompanyContributionUnion { +// [IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixed], +// [IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercent], +// [IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTiered]. +func (r IndividualBenefitBodyIndividualBenefitCompanyContribution) AsUnion() IndividualBenefitBodyIndividualBenefitCompanyContributionUnion { return r.union } -// Union satisfied by [IndividualBenefitBodyObjectCompanyContributionObject], -// [IndividualBenefitBodyObjectCompanyContributionObject] or -// [IndividualBenefitBodyObjectCompanyContributionObject]. -type IndividualBenefitBodyObjectCompanyContributionUnion interface { - implementsIndividualBenefitBodyObjectCompanyContribution() +// Company contribution configuration. Supports fixed amounts (in cents), +// percentage-based contributions (in basis points where 100 = 1%), or tiered +// matching structures. +// +// Union satisfied by +// [IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixed], +// [IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercent] +// or +// [IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTiered]. +type IndividualBenefitBodyIndividualBenefitCompanyContributionUnion interface { + implementsIndividualBenefitBodyIndividualBenefitCompanyContribution() } func init() { apijson.RegisterUnion( - reflect.TypeOf((*IndividualBenefitBodyObjectCompanyContributionUnion)(nil)).Elem(), + reflect.TypeOf((*IndividualBenefitBodyIndividualBenefitCompanyContributionUnion)(nil)).Elem(), "", apijson.UnionVariant{ TypeFilter: gjson.JSON, - Type: reflect.TypeOf(IndividualBenefitBodyObjectCompanyContributionObject{}), + Type: reflect.TypeOf(IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixed{}), }, apijson.UnionVariant{ TypeFilter: gjson.JSON, - Type: reflect.TypeOf(IndividualBenefitBodyObjectCompanyContributionObject{}), + Type: reflect.TypeOf(IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercent{}), }, apijson.UnionVariant{ TypeFilter: gjson.JSON, - Type: reflect.TypeOf(IndividualBenefitBodyObjectCompanyContributionObject{}), + Type: reflect.TypeOf(IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTiered{}), }, ) } -type IndividualBenefitBodyObjectCompanyContributionObject struct { - // Contribution amount in cents. - Amount int64 `json:"amount,required"` - // Fixed contribution type. - Type IndividualBenefitBodyObjectCompanyContributionObjectType `json:"type,required"` - JSON individualBenefitBodyObjectCompanyContributionObjectJSON `json:"-"` +type IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixed struct { + // Contribution amount in cents (for type=fixed) or basis points (for type=percent, + // where 100 = 1%). Not used for type=tiered. + Amount int64 `json:"amount" api:"required"` + // Contribution type. Supported values: "fixed" (amount in cents), "percent" + // (amount in basis points), or "tiered" (multi-tier matching). + Type IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixedType `json:"type" api:"required"` + JSON individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixedJSON `json:"-"` } -// individualBenefitBodyObjectCompanyContributionObjectJSON contains the JSON -// metadata for the struct [IndividualBenefitBodyObjectCompanyContributionObject] -type individualBenefitBodyObjectCompanyContributionObjectJSON struct { +// individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixedJSON +// contains the JSON metadata for the struct +// [IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixed] +type individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixedJSON struct { Amount apijson.Field Type apijson.Field raw string ExtraFields map[string]apijson.Field } -func (r *IndividualBenefitBodyObjectCompanyContributionObject) UnmarshalJSON(data []byte) (err error) { +func (r *IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixed) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -func (r individualBenefitBodyObjectCompanyContributionObjectJSON) RawJSON() string { +func (r individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixedJSON) RawJSON() string { return r.raw } -func (r IndividualBenefitBodyObjectCompanyContributionObject) implementsIndividualBenefitBodyObjectCompanyContribution() { +func (r IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixed) implementsIndividualBenefitBodyIndividualBenefitCompanyContribution() { } -// Fixed contribution type. -type IndividualBenefitBodyObjectCompanyContributionObjectType string +// Contribution type. Supported values: "fixed" (amount in cents), "percent" +// (amount in basis points), or "tiered" (multi-tier matching). +type IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixedType string const ( - IndividualBenefitBodyObjectCompanyContributionObjectTypeFixed IndividualBenefitBodyObjectCompanyContributionObjectType = "fixed" + IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixedTypeFixed IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixedType = "fixed" ) -func (r IndividualBenefitBodyObjectCompanyContributionObjectType) IsKnown() bool { +func (r IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixedType) IsKnown() bool { switch r { - case IndividualBenefitBodyObjectCompanyContributionObjectTypeFixed: + case IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionFixedTypeFixed: return true } return false } -// Fixed contribution type. -type IndividualBenefitBodyObjectCompanyContributionType string +type IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercent struct { + // Contribution amount in cents (for type=fixed) or basis points (for type=percent, + // where 100 = 1%). Not used for type=tiered. + Amount int64 `json:"amount" api:"required"` + // Contribution type. Supported values: "fixed" (amount in cents), "percent" + // (amount in basis points), or "tiered" (multi-tier matching). + Type IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercentType `json:"type" api:"required"` + JSON individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercentJSON `json:"-"` +} + +// individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercentJSON +// contains the JSON metadata for the struct +// [IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercent] +type individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercentJSON struct { + Amount apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercent) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercentJSON) RawJSON() string { + return r.raw +} + +func (r IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercent) implementsIndividualBenefitBodyIndividualBenefitCompanyContribution() { +} + +// Contribution type. Supported values: "fixed" (amount in cents), "percent" +// (amount in basis points), or "tiered" (multi-tier matching). +type IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercentType string const ( - IndividualBenefitBodyObjectCompanyContributionTypeFixed IndividualBenefitBodyObjectCompanyContributionType = "fixed" - IndividualBenefitBodyObjectCompanyContributionTypePercent IndividualBenefitBodyObjectCompanyContributionType = "percent" - IndividualBenefitBodyObjectCompanyContributionTypeTiered IndividualBenefitBodyObjectCompanyContributionType = "tiered" + IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercentTypePercent IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercentType = "percent" ) -func (r IndividualBenefitBodyObjectCompanyContributionType) IsKnown() bool { +func (r IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercentType) IsKnown() bool { switch r { - case IndividualBenefitBodyObjectCompanyContributionTypeFixed, IndividualBenefitBodyObjectCompanyContributionTypePercent, IndividualBenefitBodyObjectCompanyContributionTypeTiered: + case IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionPercentTypePercent: return true } return false } -type IndividualBenefitBodyObjectEmployeeDeduction struct { - // Contribution amount in cents. - Amount int64 `json:"amount,required"` - // Fixed contribution type. - Type IndividualBenefitBodyObjectEmployeeDeductionType `json:"type,required"` - JSON individualBenefitBodyObjectEmployeeDeductionJSON `json:"-"` - union IndividualBenefitBodyObjectEmployeeDeductionUnion +type IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTiered struct { + // Array of tier objects defining employer match tiers based on employee + // contribution thresholds. Required when type=tiered. + Tiers []IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredTier `json:"tiers" api:"required"` + // Contribution type. Supported values: "fixed" (amount in cents), "percent" + // (amount in basis points), or "tiered" (multi-tier matching). + Type IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredType `json:"type" api:"required"` + JSON individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredJSON `json:"-"` +} + +// individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredJSON +// contains the JSON metadata for the struct +// [IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTiered] +type individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredJSON struct { + Tiers apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTiered) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredJSON) RawJSON() string { + return r.raw } -// individualBenefitBodyObjectEmployeeDeductionJSON contains the JSON metadata for -// the struct [IndividualBenefitBodyObjectEmployeeDeduction] -type individualBenefitBodyObjectEmployeeDeductionJSON struct { +func (r IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTiered) implementsIndividualBenefitBodyIndividualBenefitCompanyContribution() { +} + +type IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredTier struct { + Match int64 `json:"match" api:"required"` + Threshold int64 `json:"threshold" api:"required"` + JSON individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredTierJSON `json:"-"` +} + +// individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredTierJSON +// contains the JSON metadata for the struct +// [IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredTier] +type individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredTierJSON struct { + Match apijson.Field + Threshold apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredTier) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r individualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredTierJSON) RawJSON() string { + return r.raw +} + +// Contribution type. Supported values: "fixed" (amount in cents), "percent" +// (amount in basis points), or "tiered" (multi-tier matching). +type IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredType string + +const ( + IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredTypeTiered IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredType = "tiered" +) + +func (r IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredType) IsKnown() bool { + switch r { + case IndividualBenefitBodyIndividualBenefitCompanyContributionCompanyContributionTieredTypeTiered: + return true + } + return false +} + +// Contribution type. Supported values: "fixed" (amount in cents), "percent" +// (amount in basis points), or "tiered" (multi-tier matching). +type IndividualBenefitBodyIndividualBenefitCompanyContributionType string + +const ( + IndividualBenefitBodyIndividualBenefitCompanyContributionTypeFixed IndividualBenefitBodyIndividualBenefitCompanyContributionType = "fixed" + IndividualBenefitBodyIndividualBenefitCompanyContributionTypePercent IndividualBenefitBodyIndividualBenefitCompanyContributionType = "percent" + IndividualBenefitBodyIndividualBenefitCompanyContributionTypeTiered IndividualBenefitBodyIndividualBenefitCompanyContributionType = "tiered" +) + +func (r IndividualBenefitBodyIndividualBenefitCompanyContributionType) IsKnown() bool { + switch r { + case IndividualBenefitBodyIndividualBenefitCompanyContributionTypeFixed, IndividualBenefitBodyIndividualBenefitCompanyContributionTypePercent, IndividualBenefitBodyIndividualBenefitCompanyContributionTypeTiered: + return true + } + return false +} + +// Employee deduction configuration. Supports both fixed amounts (in cents) and +// percentage-based contributions (in basis points where 100 = 1%). +type IndividualBenefitBodyIndividualBenefitEmployeeDeduction struct { + // Contribution amount in cents (for type=fixed) or basis points (for type=percent, + // where 100 = 1%). + Amount int64 `json:"amount" api:"required"` + // Contribution type. Supported values: "fixed" (amount in cents) or "percent" + // (amount in basis points). + Type IndividualBenefitBodyIndividualBenefitEmployeeDeductionType `json:"type" api:"required"` + JSON individualBenefitBodyIndividualBenefitEmployeeDeductionJSON `json:"-"` + union IndividualBenefitBodyIndividualBenefitEmployeeDeductionUnion +} + +// individualBenefitBodyIndividualBenefitEmployeeDeductionJSON contains the JSON +// metadata for the struct +// [IndividualBenefitBodyIndividualBenefitEmployeeDeduction] +type individualBenefitBodyIndividualBenefitEmployeeDeductionJSON struct { Amount apijson.Field Type apijson.Field raw string ExtraFields map[string]apijson.Field } -func (r individualBenefitBodyObjectEmployeeDeductionJSON) RawJSON() string { +func (r individualBenefitBodyIndividualBenefitEmployeeDeductionJSON) RawJSON() string { return r.raw } -func (r *IndividualBenefitBodyObjectEmployeeDeduction) UnmarshalJSON(data []byte) (err error) { - *r = IndividualBenefitBodyObjectEmployeeDeduction{} +func (r *IndividualBenefitBodyIndividualBenefitEmployeeDeduction) UnmarshalJSON(data []byte) (err error) { + *r = IndividualBenefitBodyIndividualBenefitEmployeeDeduction{} err = apijson.UnmarshalRoot(data, &r.union) if err != nil { return err @@ -394,116 +543,173 @@ func (r *IndividualBenefitBodyObjectEmployeeDeduction) UnmarshalJSON(data []byte return apijson.Port(r.union, &r) } -// AsUnion returns a [IndividualBenefitBodyObjectEmployeeDeductionUnion] interface -// which you can cast to the specific types for more type safety. +// AsUnion returns a [IndividualBenefitBodyIndividualBenefitEmployeeDeductionUnion] +// interface which you can cast to the specific types for more type safety. // // Possible runtime types of the union are -// [IndividualBenefitBodyObjectEmployeeDeductionObject], -// [IndividualBenefitBodyObjectEmployeeDeductionObject]. -func (r IndividualBenefitBodyObjectEmployeeDeduction) AsUnion() IndividualBenefitBodyObjectEmployeeDeductionUnion { +// [IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixed], +// [IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercent]. +func (r IndividualBenefitBodyIndividualBenefitEmployeeDeduction) AsUnion() IndividualBenefitBodyIndividualBenefitEmployeeDeductionUnion { return r.union } -// Union satisfied by [IndividualBenefitBodyObjectEmployeeDeductionObject] or -// [IndividualBenefitBodyObjectEmployeeDeductionObject]. -type IndividualBenefitBodyObjectEmployeeDeductionUnion interface { - implementsIndividualBenefitBodyObjectEmployeeDeduction() +// Employee deduction configuration. Supports both fixed amounts (in cents) and +// percentage-based contributions (in basis points where 100 = 1%). +// +// Union satisfied by +// [IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixed] +// or +// [IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercent]. +type IndividualBenefitBodyIndividualBenefitEmployeeDeductionUnion interface { + implementsIndividualBenefitBodyIndividualBenefitEmployeeDeduction() } func init() { apijson.RegisterUnion( - reflect.TypeOf((*IndividualBenefitBodyObjectEmployeeDeductionUnion)(nil)).Elem(), + reflect.TypeOf((*IndividualBenefitBodyIndividualBenefitEmployeeDeductionUnion)(nil)).Elem(), "", apijson.UnionVariant{ TypeFilter: gjson.JSON, - Type: reflect.TypeOf(IndividualBenefitBodyObjectEmployeeDeductionObject{}), + Type: reflect.TypeOf(IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixed{}), }, apijson.UnionVariant{ TypeFilter: gjson.JSON, - Type: reflect.TypeOf(IndividualBenefitBodyObjectEmployeeDeductionObject{}), + Type: reflect.TypeOf(IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercent{}), }, ) } -type IndividualBenefitBodyObjectEmployeeDeductionObject struct { - // Contribution amount in cents. - Amount int64 `json:"amount,required"` - // Fixed contribution type. - Type IndividualBenefitBodyObjectEmployeeDeductionObjectType `json:"type,required"` - JSON individualBenefitBodyObjectEmployeeDeductionObjectJSON `json:"-"` +type IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixed struct { + // Contribution amount in cents (for type=fixed) or basis points (for type=percent, + // where 100 = 1%). + Amount int64 `json:"amount" api:"required"` + // Contribution type. Supported values: "fixed" (amount in cents) or "percent" + // (amount in basis points). + Type IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixedType `json:"type" api:"required"` + JSON individualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixedJSON `json:"-"` } -// individualBenefitBodyObjectEmployeeDeductionObjectJSON contains the JSON -// metadata for the struct [IndividualBenefitBodyObjectEmployeeDeductionObject] -type individualBenefitBodyObjectEmployeeDeductionObjectJSON struct { +// individualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixedJSON +// contains the JSON metadata for the struct +// [IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixed] +type individualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixedJSON struct { Amount apijson.Field Type apijson.Field raw string ExtraFields map[string]apijson.Field } -func (r *IndividualBenefitBodyObjectEmployeeDeductionObject) UnmarshalJSON(data []byte) (err error) { +func (r *IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixed) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -func (r individualBenefitBodyObjectEmployeeDeductionObjectJSON) RawJSON() string { +func (r individualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixedJSON) RawJSON() string { return r.raw } -func (r IndividualBenefitBodyObjectEmployeeDeductionObject) implementsIndividualBenefitBodyObjectEmployeeDeduction() { +func (r IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixed) implementsIndividualBenefitBodyIndividualBenefitEmployeeDeduction() { } -// Fixed contribution type. -type IndividualBenefitBodyObjectEmployeeDeductionObjectType string +// Contribution type. Supported values: "fixed" (amount in cents) or "percent" +// (amount in basis points). +type IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixedType string const ( - IndividualBenefitBodyObjectEmployeeDeductionObjectTypeFixed IndividualBenefitBodyObjectEmployeeDeductionObjectType = "fixed" + IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixedTypeFixed IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixedType = "fixed" ) -func (r IndividualBenefitBodyObjectEmployeeDeductionObjectType) IsKnown() bool { +func (r IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixedType) IsKnown() bool { switch r { - case IndividualBenefitBodyObjectEmployeeDeductionObjectTypeFixed: + case IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionFixedTypeFixed: return true } return false } -// Fixed contribution type. -type IndividualBenefitBodyObjectEmployeeDeductionType string +type IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercent struct { + // Contribution amount in cents (for type=fixed) or basis points (for type=percent, + // where 100 = 1%). + Amount int64 `json:"amount" api:"required"` + // Contribution type. Supported values: "fixed" (amount in cents) or "percent" + // (amount in basis points). + Type IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercentType `json:"type" api:"required"` + JSON individualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercentJSON `json:"-"` +} + +// individualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercentJSON +// contains the JSON metadata for the struct +// [IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercent] +type individualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercentJSON struct { + Amount apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercent) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r individualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercentJSON) RawJSON() string { + return r.raw +} + +func (r IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercent) implementsIndividualBenefitBodyIndividualBenefitEmployeeDeduction() { +} + +// Contribution type. Supported values: "fixed" (amount in cents) or "percent" +// (amount in basis points). +type IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercentType string + +const ( + IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercentTypePercent IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercentType = "percent" +) + +func (r IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercentType) IsKnown() bool { + switch r { + case IndividualBenefitBodyIndividualBenefitEmployeeDeductionEmployeeDeductionContributionPercentTypePercent: + return true + } + return false +} + +// Contribution type. Supported values: "fixed" (amount in cents) or "percent" +// (amount in basis points). +type IndividualBenefitBodyIndividualBenefitEmployeeDeductionType string const ( - IndividualBenefitBodyObjectEmployeeDeductionTypeFixed IndividualBenefitBodyObjectEmployeeDeductionType = "fixed" - IndividualBenefitBodyObjectEmployeeDeductionTypePercent IndividualBenefitBodyObjectEmployeeDeductionType = "percent" + IndividualBenefitBodyIndividualBenefitEmployeeDeductionTypeFixed IndividualBenefitBodyIndividualBenefitEmployeeDeductionType = "fixed" + IndividualBenefitBodyIndividualBenefitEmployeeDeductionTypePercent IndividualBenefitBodyIndividualBenefitEmployeeDeductionType = "percent" ) -func (r IndividualBenefitBodyObjectEmployeeDeductionType) IsKnown() bool { +func (r IndividualBenefitBodyIndividualBenefitEmployeeDeductionType) IsKnown() bool { switch r { - case IndividualBenefitBodyObjectEmployeeDeductionTypeFixed, IndividualBenefitBodyObjectEmployeeDeductionTypePercent: + case IndividualBenefitBodyIndividualBenefitEmployeeDeductionTypeFixed, IndividualBenefitBodyIndividualBenefitEmployeeDeductionTypePercent: return true } return false } // Type for HSA contribution limit if the benefit is a HSA. -type IndividualBenefitBodyObjectHsaContributionLimit string +type IndividualBenefitBodyIndividualBenefitHsaContributionLimit string const ( - IndividualBenefitBodyObjectHsaContributionLimitIndividual IndividualBenefitBodyObjectHsaContributionLimit = "individual" - IndividualBenefitBodyObjectHsaContributionLimitFamily IndividualBenefitBodyObjectHsaContributionLimit = "family" + IndividualBenefitBodyIndividualBenefitHsaContributionLimitIndividual IndividualBenefitBodyIndividualBenefitHsaContributionLimit = "individual" + IndividualBenefitBodyIndividualBenefitHsaContributionLimitFamily IndividualBenefitBodyIndividualBenefitHsaContributionLimit = "family" ) -func (r IndividualBenefitBodyObjectHsaContributionLimit) IsKnown() bool { +func (r IndividualBenefitBodyIndividualBenefitHsaContributionLimit) IsKnown() bool { switch r { - case IndividualBenefitBodyObjectHsaContributionLimitIndividual, IndividualBenefitBodyObjectHsaContributionLimitFamily: + case IndividualBenefitBodyIndividualBenefitHsaContributionLimitIndividual, IndividualBenefitBodyIndividualBenefitHsaContributionLimitFamily: return true } return false } type IndividualBenefitBodyBatchError struct { - Code float64 `json:"code,required"` - Message string `json:"message,required"` - Name string `json:"name,required"` + Code float64 `json:"code" api:"required"` + Message string `json:"message" api:"required"` + Name string `json:"name" api:"required"` FinchCode string `json:"finch_code"` JSON individualBenefitBodyBatchErrorJSON `json:"-"` } @@ -546,7 +752,7 @@ func (r IndividualBenefitBodyHsaContributionLimit) IsKnown() bool { } type UnenrolledIndividualBenefitResponse struct { - JobID string `json:"job_id,required" format:"uuid"` + JobID string `json:"job_id" api:"required" format:"uuid"` JSON unenrolledIndividualBenefitResponseJSON `json:"-"` } @@ -568,8 +774,8 @@ func (r unenrolledIndividualBenefitResponseJSON) RawJSON() string { type HRISBenefitIndividualEnrolledIDsResponse struct { // The id of the benefit. - BenefitID string `json:"benefit_id,required" format:"uuid"` - IndividualIDs []string `json:"individual_ids,required" format:"uuid"` + BenefitID string `json:"benefit_id" api:"required" format:"uuid"` + IndividualIDs []string `json:"individual_ids" api:"required" format:"uuid"` JSON hrisBenefitIndividualEnrolledIDsResponseJSON `json:"-"` } @@ -590,7 +796,23 @@ func (r hrisBenefitIndividualEnrolledIDsResponseJSON) RawJSON() string { return r.raw } +type HRISBenefitIndividualEnrolledIDsParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` +} + +// URLQuery serializes [HRISBenefitIndividualEnrolledIDsParams]'s query parameters +// as `url.Values`. +func (r HRISBenefitIndividualEnrolledIDsParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + type HRISBenefitIndividualGetManyBenefitsParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` // comma-delimited list of stable Finch uuids for each individual. If empty, // defaults to all individuals IndividualIDs param.Field[string] `query:"individual_ids"` @@ -606,6 +828,8 @@ func (r HRISBenefitIndividualGetManyBenefitsParams) URLQuery() (v url.Values) { } type HRISBenefitIndividualUnenrollManyParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` // Array of individual_ids to unenroll. IndividualIDs param.Field[[]string] `json:"individual_ids"` } @@ -613,3 +837,12 @@ type HRISBenefitIndividualUnenrollManyParams struct { func (r HRISBenefitIndividualUnenrollManyParams) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } + +// URLQuery serializes [HRISBenefitIndividualUnenrollManyParams]'s query parameters +// as `url.Values`. +func (r HRISBenefitIndividualUnenrollManyParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} diff --git a/hrisbenefitindividual_test.go b/hrisbenefitindividual_test.go index 90207dad..a0aaff09 100644 --- a/hrisbenefitindividual_test.go +++ b/hrisbenefitindividual_test.go @@ -13,7 +13,7 @@ import ( "github.com/Finch-API/finch-api-go/option" ) -func TestHRISBenefitIndividualEnrolledIDs(t *testing.T) { +func TestHRISBenefitIndividualEnrolledIDsWithOptionalParams(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -24,8 +24,16 @@ func TestHRISBenefitIndividualEnrolledIDs(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), + ) + _, err := client.HRIS.Benefits.Individuals.EnrolledIDs( + context.TODO(), + "benefit_id", + finchgo.HRISBenefitIndividualEnrolledIDsParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), + }, ) - _, err := client.HRIS.Benefits.Individuals.EnrolledIDs(context.TODO(), "benefit_id") if err != nil { var apierr *finchgo.Error if errors.As(err, &apierr) { @@ -46,11 +54,14 @@ func TestHRISBenefitIndividualGetManyBenefitsWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Benefits.Individuals.GetManyBenefits( context.TODO(), "benefit_id", finchgo.HRISBenefitIndividualGetManyBenefitsParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), IndividualIDs: finchgo.F("d675d2b7-6d7b-41a8-b2d3-001eb3fb88f6,d02a6346-1f08-4312-a064-49ff3cafaa7a"), }, ) @@ -74,11 +85,14 @@ func TestHRISBenefitIndividualUnenrollManyWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Benefits.Individuals.UnenrollMany( context.TODO(), "benefit_id", finchgo.HRISBenefitIndividualUnenrollManyParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), IndividualIDs: finchgo.F([]string{"string"}), }, ) diff --git a/hriscompany.go b/hriscompany.go index 875b651d..cfdbffa7 100644 --- a/hriscompany.go +++ b/hriscompany.go @@ -5,9 +5,12 @@ package finchgo import ( "context" "net/http" + "net/url" "slices" "github.com/Finch-API/finch-api-go/internal/apijson" + "github.com/Finch-API/finch-api-go/internal/apiquery" + "github.com/Finch-API/finch-api-go/internal/param" "github.com/Finch-API/finch-api-go/internal/requestconfig" "github.com/Finch-API/finch-api-go/option" ) @@ -34,32 +37,33 @@ func NewHRISCompanyService(opts ...option.RequestOption) (r *HRISCompanyService) } // Read basic company data -func (r *HRISCompanyService) Get(ctx context.Context, opts ...option.RequestOption) (res *Company, err error) { - opts = slices.Concat(r.Options, opts) +func (r *HRISCompanyService) Get(ctx context.Context, query HRISCompanyGetParams, opts ...option.RequestOption) (res *Company, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "employer/company" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) + return res, err } type Company struct { // A stable Finch `id` (UUID v4) for the company. - ID string `json:"id,required" format:"uuid"` + ID string `json:"id" api:"required" format:"uuid"` // An array of bank account objects associated with the payroll/HRIS system. - Accounts []CompanyAccount `json:"accounts,required,nullable"` + Accounts []CompanyAccount `json:"accounts" api:"required,nullable"` // The array of company departments. - Departments []CompanyDepartment `json:"departments,required,nullable"` + Departments []CompanyDepartment `json:"departments" api:"required,nullable"` // The employer identification number. - Ein string `json:"ein,required,nullable"` + Ein string `json:"ein" api:"required,nullable"` // The entity type object. - Entity CompanyEntity `json:"entity,required,nullable"` + Entity CompanyEntity `json:"entity" api:"required,nullable"` // The legal name of the company. - LegalName string `json:"legal_name,required,nullable"` - Locations []Location `json:"locations,required,nullable"` + LegalName string `json:"legal_name" api:"required,nullable"` + Locations []Location `json:"locations" api:"required,nullable"` // The email of the main administrator on the account. - PrimaryEmail string `json:"primary_email,required,nullable" format:"email"` + PrimaryEmail string `json:"primary_email" api:"required,nullable" format:"email"` // The phone number of the main administrator on the account. Format: E.164, with // extension where applicable, e.g. `+NNNNNNNNNNN xExtension` - PrimaryPhoneNumber string `json:"primary_phone_number,required,nullable"` + PrimaryPhoneNumber string `json:"primary_phone_number" api:"required,nullable"` JSON companyJSON `json:"-"` } @@ -88,16 +92,16 @@ func (r companyJSON) RawJSON() string { type CompanyAccount struct { // The name of the bank associated in the payroll/HRIS system. - AccountName string `json:"account_name,required,nullable"` + AccountName string `json:"account_name" api:"required,nullable"` // 10-12 digit number to specify the bank account - AccountNumber string `json:"account_number,required,nullable"` + AccountNumber string `json:"account_number" api:"required,nullable"` // The type of bank account. - AccountType CompanyAccountsAccountType `json:"account_type,required,nullable"` + AccountType CompanyAccountsAccountType `json:"account_type" api:"required,nullable"` // Name of the banking institution. - InstitutionName string `json:"institution_name,required,nullable"` + InstitutionName string `json:"institution_name" api:"required,nullable"` // A nine-digit code that's based on the U.S. Bank location where your account was // opened. - RoutingNumber string `json:"routing_number,required,nullable"` + RoutingNumber string `json:"routing_number" api:"required,nullable"` JSON companyAccountJSON `json:"-"` } @@ -138,9 +142,9 @@ func (r CompanyAccountsAccountType) IsKnown() bool { type CompanyDepartment struct { // The department name. - Name string `json:"name,required,nullable"` + Name string `json:"name" api:"required,nullable"` // The parent department, if present. - Parent CompanyDepartmentsParent `json:"parent,required,nullable"` + Parent CompanyDepartmentsParent `json:"parent" api:"required,nullable"` JSON companyDepartmentJSON `json:"-"` } @@ -164,7 +168,7 @@ func (r companyDepartmentJSON) RawJSON() string { // The parent department, if present. type CompanyDepartmentsParent struct { // The parent department's name. - Name string `json:"name,required,nullable"` + Name string `json:"name" api:"required,nullable"` JSON companyDepartmentsParentJSON `json:"-"` } @@ -187,9 +191,9 @@ func (r companyDepartmentsParentJSON) RawJSON() string { // The entity type object. type CompanyEntity struct { // The tax payer subtype of the company. - Subtype CompanyEntitySubtype `json:"subtype,required,nullable"` + Subtype CompanyEntitySubtype `json:"subtype" api:"required,nullable"` // The tax payer type of the company. - Type CompanyEntityType `json:"type,required,nullable"` + Type CompanyEntityType `json:"type" api:"required,nullable"` JSON companyEntityJSON `json:"-"` } @@ -246,3 +250,16 @@ func (r CompanyEntityType) IsKnown() bool { } return false } + +type HRISCompanyGetParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` +} + +// URLQuery serializes [HRISCompanyGetParams]'s query parameters as `url.Values`. +func (r HRISCompanyGetParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} diff --git a/hriscompany_test.go b/hriscompany_test.go index fb80f54a..c9af8ec1 100644 --- a/hriscompany_test.go +++ b/hriscompany_test.go @@ -13,7 +13,7 @@ import ( "github.com/Finch-API/finch-api-go/option" ) -func TestHRISCompanyGet(t *testing.T) { +func TestHRISCompanyGetWithOptionalParams(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -24,8 +24,12 @@ func TestHRISCompanyGet(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) - _, err := client.HRIS.Company.Get(context.TODO()) + _, err := client.HRIS.Company.Get(context.TODO(), finchgo.HRISCompanyGetParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), + }) if err != nil { var apierr *finchgo.Error if errors.As(err, &apierr) { diff --git a/hriscompanypaystatementitem.go b/hriscompanypaystatementitem.go index ce57c602..ae061876 100644 --- a/hriscompanypaystatementitem.go +++ b/hriscompanypaystatementitem.go @@ -38,12 +38,12 @@ func NewHRISCompanyPayStatementItemService(opts ...option.RequestOption) (r *HRI return } -// **Beta:** this endpoint currently serves employers onboarded after March 4th and -// historical support will be added soon Retrieve a list of detailed pay statement -// items for the access token's connection account. +// Retrieve a list of detailed pay statement items for the access token's +// connection account. func (r *HRISCompanyPayStatementItemService) List(ctx context.Context, query HRISCompanyPayStatementItemListParams, opts ...option.RequestOption) (res *pagination.ResponsesPage[HRISCompanyPayStatementItemListResponse], err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) path := "employer/pay-statement-item" cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, query, &res, opts...) @@ -58,20 +58,19 @@ func (r *HRISCompanyPayStatementItemService) List(ctx context.Context, query HRI return res, nil } -// **Beta:** this endpoint currently serves employers onboarded after March 4th and -// historical support will be added soon Retrieve a list of detailed pay statement -// items for the access token's connection account. +// Retrieve a list of detailed pay statement items for the access token's +// connection account. func (r *HRISCompanyPayStatementItemService) ListAutoPaging(ctx context.Context, query HRISCompanyPayStatementItemListParams, opts ...option.RequestOption) *pagination.ResponsesPageAutoPager[HRISCompanyPayStatementItemListResponse] { return pagination.NewResponsesPageAutoPager(r.List(ctx, query, opts...)) } type HRISCompanyPayStatementItemListResponse struct { // The attributes of the pay statement item. - Attributes HRISCompanyPayStatementItemListResponseAttributes `json:"attributes,required"` + Attributes HRISCompanyPayStatementItemListResponseAttributes `json:"attributes" api:"required"` // The category of the pay statement item. - Category HRISCompanyPayStatementItemListResponseCategory `json:"category,required"` + Category HRISCompanyPayStatementItemListResponseCategory `json:"category" api:"required"` // The name of the pay statement item. - Name string `json:"name,required"` + Name string `json:"name" api:"required"` JSON hrisCompanyPayStatementItemListResponseJSON `json:"-"` } @@ -97,15 +96,15 @@ func (r hrisCompanyPayStatementItemListResponseJSON) RawJSON() string { type HRISCompanyPayStatementItemListResponseAttributes struct { // The metadata of the pay statement item derived by the rules engine if available. // Each attribute will be a key-value pair defined by a rule. - Metadata map[string]interface{} `json:"metadata,required,nullable"` + Metadata map[string]interface{} `json:"metadata" api:"required,nullable"` // `true` if the amount is paid by the employers. This field is only available for // taxes. - Employer bool `json:"employer,nullable"` + Employer bool `json:"employer" api:"nullable"` // `true` if the pay statement item is pre-tax. This field is only available for // employee deductions. - PreTax bool `json:"pre_tax,nullable"` + PreTax bool `json:"pre_tax" api:"nullable"` // The type of the pay statement item. - Type string `json:"type,nullable"` + Type string `json:"type" api:"nullable"` JSON hrisCompanyPayStatementItemListResponseAttributesJSON `json:"-"` } @@ -153,6 +152,8 @@ type HRISCompanyPayStatementItemListParams struct { // The end date to retrieve pay statement items by via their last seen pay date in // `YYYY-MM-DD` format. EndDate param.Field[time.Time] `query:"end_date" format:"date"` + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` // Case-insensitive partial match search by pay statement item name. Name param.Field[string] `query:"name"` // The start date to retrieve pay statement items by via their last seen pay date diff --git a/hriscompanypaystatementitem_test.go b/hriscompanypaystatementitem_test.go index b827b4d0..0685b08c 100644 --- a/hriscompanypaystatementitem_test.go +++ b/hriscompanypaystatementitem_test.go @@ -25,10 +25,13 @@ func TestHRISCompanyPayStatementItemListWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Company.PayStatementItem.List(context.TODO(), finchgo.HRISCompanyPayStatementItemListParams{ Categories: finchgo.F([]finchgo.HRISCompanyPayStatementItemListParamsCategory{finchgo.HRISCompanyPayStatementItemListParamsCategoryEarnings}), EndDate: finchgo.F(time.Now()), + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), Name: finchgo.F("name"), StartDate: finchgo.F(time.Now()), Type: finchgo.F("base_compensation"), diff --git a/hriscompanypaystatementitemrule.go b/hriscompanypaystatementitemrule.go index 88e3c7da..3e4dc00c 100644 --- a/hriscompanypaystatementitemrule.go +++ b/hriscompanypaystatementitemrule.go @@ -7,10 +7,12 @@ import ( "errors" "fmt" "net/http" + "net/url" "slices" "time" "github.com/Finch-API/finch-api-go/internal/apijson" + "github.com/Finch-API/finch-api-go/internal/apiquery" "github.com/Finch-API/finch-api-go/internal/param" "github.com/Finch-API/finch-api-go/internal/requestconfig" "github.com/Finch-API/finch-api-go/option" @@ -36,40 +38,39 @@ func NewHRISCompanyPayStatementItemRuleService(opts ...option.RequestOption) (r return } -// **Beta:** this endpoint currently serves employers onboarded after March 4th and -// historical support will be added soon Custom rules can be created to associate -// specific attributes to pay statement items depending on the use case. For -// example, pay statement items that meet certain conditions can be labeled as a -// pre-tax 401k. This metadata can be retrieved where pay statement item -// information is available. -func (r *HRISCompanyPayStatementItemRuleService) New(ctx context.Context, body HRISCompanyPayStatementItemRuleNewParams, opts ...option.RequestOption) (res *HRISCompanyPayStatementItemRuleNewResponse, err error) { - opts = slices.Concat(r.Options, opts) +// Custom rules can be created to associate specific attributes to pay statement +// items depending on the use case. For example, pay statement items that meet +// certain conditions can be labeled as a pre-tax 401k. This metadata can be +// retrieved where pay statement item information is available. +func (r *HRISCompanyPayStatementItemRuleService) New(ctx context.Context, params HRISCompanyPayStatementItemRuleNewParams, opts ...option.RequestOption) (res *HRISCompanyPayStatementItemRuleNewResponse, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "employer/pay-statement-item/rule" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...) + return res, err } -// **Beta:** this endpoint currently serves employers onboarded after March 4th and -// historical support will be added soon Update a rule for a pay statement item. -func (r *HRISCompanyPayStatementItemRuleService) Update(ctx context.Context, ruleID string, body HRISCompanyPayStatementItemRuleUpdateParams, opts ...option.RequestOption) (res *HRISCompanyPayStatementItemRuleUpdateResponse, err error) { - opts = slices.Concat(r.Options, opts) +// Update a rule for a pay statement item. +func (r *HRISCompanyPayStatementItemRuleService) Update(ctx context.Context, ruleID string, params HRISCompanyPayStatementItemRuleUpdateParams, opts ...option.RequestOption) (res *HRISCompanyPayStatementItemRuleUpdateResponse, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) if ruleID == "" { err = errors.New("missing required rule_id parameter") - return + return nil, err } path := fmt.Sprintf("employer/pay-statement-item/rule/%s", ruleID) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, &res, opts...) - return + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, params, &res, opts...) + return res, err } -// **Beta:** this endpoint currently serves employers onboarded after March 4th and -// historical support will be added soon List all rules of a connection account. -func (r *HRISCompanyPayStatementItemRuleService) List(ctx context.Context, opts ...option.RequestOption) (res *pagination.ResponsesPage[HRISCompanyPayStatementItemRuleListResponse], err error) { +// List all rules of a connection account. +func (r *HRISCompanyPayStatementItemRuleService) List(ctx context.Context, query HRISCompanyPayStatementItemRuleListParams, opts ...option.RequestOption) (res *pagination.ResponsesPage[HRISCompanyPayStatementItemRuleListResponse], err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) path := "employer/pay-statement-item/rule" - cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, nil, &res, opts...) + cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, query, &res, opts...) if err != nil { return nil, err } @@ -81,23 +82,22 @@ func (r *HRISCompanyPayStatementItemRuleService) List(ctx context.Context, opts return res, nil } -// **Beta:** this endpoint currently serves employers onboarded after March 4th and -// historical support will be added soon List all rules of a connection account. -func (r *HRISCompanyPayStatementItemRuleService) ListAutoPaging(ctx context.Context, opts ...option.RequestOption) *pagination.ResponsesPageAutoPager[HRISCompanyPayStatementItemRuleListResponse] { - return pagination.NewResponsesPageAutoPager(r.List(ctx, opts...)) +// List all rules of a connection account. +func (r *HRISCompanyPayStatementItemRuleService) ListAutoPaging(ctx context.Context, query HRISCompanyPayStatementItemRuleListParams, opts ...option.RequestOption) *pagination.ResponsesPageAutoPager[HRISCompanyPayStatementItemRuleListResponse] { + return pagination.NewResponsesPageAutoPager(r.List(ctx, query, opts...)) } -// **Beta:** this endpoint currently serves employers onboarded after March 4th and -// historical support will be added soon Delete a rule for a pay statement item. -func (r *HRISCompanyPayStatementItemRuleService) Delete(ctx context.Context, ruleID string, opts ...option.RequestOption) (res *HRISCompanyPayStatementItemRuleDeleteResponse, err error) { - opts = slices.Concat(r.Options, opts) +// Delete a rule for a pay statement item. +func (r *HRISCompanyPayStatementItemRuleService) Delete(ctx context.Context, ruleID string, body HRISCompanyPayStatementItemRuleDeleteParams, opts ...option.RequestOption) (res *HRISCompanyPayStatementItemRuleDeleteResponse, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) if ruleID == "" { err = errors.New("missing required rule_id parameter") - return + return nil, err } path := fmt.Sprintf("employer/pay-statement-item/rule/%s", ruleID) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, &res, opts...) - return + err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, body, &res, opts...) + return res, err } type HRISCompanyPayStatementItemRuleNewResponse struct { @@ -109,9 +109,9 @@ type HRISCompanyPayStatementItemRuleNewResponse struct { // The datetime when the rule was created. CreatedAt time.Time `json:"created_at" format:"date-time"` // Specifies when the rules should stop applying rules based on the date. - EffectiveEndDate string `json:"effective_end_date,nullable"` + EffectiveEndDate string `json:"effective_end_date" api:"nullable"` // Specifies when the rule should begin applying based on the date. - EffectiveStartDate string `json:"effective_start_date,nullable"` + EffectiveStartDate string `json:"effective_start_date" api:"nullable"` // The entity type to which the rule is applied. EntityType HRISCompanyPayStatementItemRuleNewResponseEntityType `json:"entity_type"` // The priority of the rule. @@ -236,9 +236,9 @@ type HRISCompanyPayStatementItemRuleUpdateResponse struct { // The datetime when the rule was created. CreatedAt time.Time `json:"created_at" format:"date-time"` // Specifies when the rules should stop applying rules based on the date. - EffectiveEndDate string `json:"effective_end_date,nullable"` + EffectiveEndDate string `json:"effective_end_date" api:"nullable"` // Specifies when the rule should begin applying based on the date. - EffectiveStartDate string `json:"effective_start_date,nullable"` + EffectiveStartDate string `json:"effective_start_date" api:"nullable"` // The entity type to which the rule is applied. EntityType HRISCompanyPayStatementItemRuleUpdateResponseEntityType `json:"entity_type"` // The priority of the rule. @@ -364,9 +364,9 @@ type HRISCompanyPayStatementItemRuleListResponse struct { // The datetime when the rule was created. CreatedAt time.Time `json:"created_at" format:"date-time"` // Specifies when the rules should stop applying rules based on the date. - EffectiveEndDate string `json:"effective_end_date,nullable"` + EffectiveEndDate string `json:"effective_end_date" api:"nullable"` // Specifies when the rule should begin applying based on the date. - EffectiveStartDate string `json:"effective_start_date,nullable"` + EffectiveStartDate string `json:"effective_start_date" api:"nullable"` // The entity type to which the rule is applied. EntityType HRISCompanyPayStatementItemRuleListResponseEntityType `json:"entity_type"` // The priority of the rule. @@ -493,9 +493,9 @@ type HRISCompanyPayStatementItemRuleDeleteResponse struct { // The datetime when the rule was deleted. DeletedAt time.Time `json:"deleted_at" format:"date-time"` // Specifies when the rules should stop applying rules based on the date. - EffectiveEndDate string `json:"effective_end_date,nullable"` + EffectiveEndDate string `json:"effective_end_date" api:"nullable"` // Specifies when the rule should begin applying based on the date. - EffectiveStartDate string `json:"effective_start_date,nullable"` + EffectiveStartDate string `json:"effective_start_date" api:"nullable"` // The entity type to which the rule is applied. EntityType HRISCompanyPayStatementItemRuleDeleteResponseEntityType `json:"entity_type"` // The priority of the rule. @@ -614,6 +614,8 @@ func (r HRISCompanyPayStatementItemRuleDeleteResponseEntityType) IsKnown() bool } type HRISCompanyPayStatementItemRuleNewParams struct { + // The entity IDs to create the rule for. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` // Specifies the fields to be applied when the condition is met. Attributes param.Field[HRISCompanyPayStatementItemRuleNewParamsAttributes] `json:"attributes"` Conditions param.Field[[]HRISCompanyPayStatementItemRuleNewParamsCondition] `json:"conditions"` @@ -629,6 +631,15 @@ func (r HRISCompanyPayStatementItemRuleNewParams) MarshalJSON() (data []byte, er return apijson.MarshalRoot(r) } +// URLQuery serializes [HRISCompanyPayStatementItemRuleNewParams]'s query +// parameters as `url.Values`. +func (r HRISCompanyPayStatementItemRuleNewParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + // Specifies the fields to be applied when the condition is met. type HRISCompanyPayStatementItemRuleNewParamsAttributes struct { // The metadata to be attached in the entity. It is a key-value pairs where the @@ -684,9 +695,48 @@ func (r HRISCompanyPayStatementItemRuleNewParamsEntityType) IsKnown() bool { } type HRISCompanyPayStatementItemRuleUpdateParams struct { + // The entity IDs to update the rule for. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` OptionalProperty param.Field[interface{}] `json:"optionalProperty"` } func (r HRISCompanyPayStatementItemRuleUpdateParams) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } + +// URLQuery serializes [HRISCompanyPayStatementItemRuleUpdateParams]'s query +// parameters as `url.Values`. +func (r HRISCompanyPayStatementItemRuleUpdateParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + +type HRISCompanyPayStatementItemRuleListParams struct { + // The entity IDs to retrieve rules for. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` +} + +// URLQuery serializes [HRISCompanyPayStatementItemRuleListParams]'s query +// parameters as `url.Values`. +func (r HRISCompanyPayStatementItemRuleListParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + +type HRISCompanyPayStatementItemRuleDeleteParams struct { + // The entity IDs to delete the rule for. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` +} + +// URLQuery serializes [HRISCompanyPayStatementItemRuleDeleteParams]'s query +// parameters as `url.Values`. +func (r HRISCompanyPayStatementItemRuleDeleteParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} diff --git a/hriscompanypaystatementitemrule_test.go b/hriscompanypaystatementitemrule_test.go index b4c8e3e2..7d61be57 100644 --- a/hriscompanypaystatementitemrule_test.go +++ b/hriscompanypaystatementitemrule_test.go @@ -24,8 +24,11 @@ func TestHRISCompanyPayStatementItemRuleNewWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Company.PayStatementItem.Rules.New(context.TODO(), finchgo.HRISCompanyPayStatementItemRuleNewParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), Attributes: finchgo.F(finchgo.HRISCompanyPayStatementItemRuleNewParamsAttributes{ Metadata: finchgo.F(map[string]interface{}{ "foo": "bar", @@ -60,11 +63,14 @@ func TestHRISCompanyPayStatementItemRuleUpdateWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Company.PayStatementItem.Rules.Update( context.TODO(), "rule_id", finchgo.HRISCompanyPayStatementItemRuleUpdateParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), OptionalProperty: finchgo.F[any](map[string]interface{}{}), }, ) @@ -77,7 +83,7 @@ func TestHRISCompanyPayStatementItemRuleUpdateWithOptionalParams(t *testing.T) { } } -func TestHRISCompanyPayStatementItemRuleList(t *testing.T) { +func TestHRISCompanyPayStatementItemRuleListWithOptionalParams(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -88,8 +94,12 @@ func TestHRISCompanyPayStatementItemRuleList(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) - _, err := client.HRIS.Company.PayStatementItem.Rules.List(context.TODO()) + _, err := client.HRIS.Company.PayStatementItem.Rules.List(context.TODO(), finchgo.HRISCompanyPayStatementItemRuleListParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), + }) if err != nil { var apierr *finchgo.Error if errors.As(err, &apierr) { @@ -99,7 +109,7 @@ func TestHRISCompanyPayStatementItemRuleList(t *testing.T) { } } -func TestHRISCompanyPayStatementItemRuleDelete(t *testing.T) { +func TestHRISCompanyPayStatementItemRuleDeleteWithOptionalParams(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -110,8 +120,16 @@ func TestHRISCompanyPayStatementItemRuleDelete(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), + ) + _, err := client.HRIS.Company.PayStatementItem.Rules.Delete( + context.TODO(), + "rule_id", + finchgo.HRISCompanyPayStatementItemRuleDeleteParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), + }, ) - _, err := client.HRIS.Company.PayStatementItem.Rules.Delete(context.TODO(), "rule_id") if err != nil { var apierr *finchgo.Error if errors.As(err, &apierr) { diff --git a/hrisdirectory.go b/hrisdirectory.go index ff2ca223..f80fb4f3 100644 --- a/hrisdirectory.go +++ b/hrisdirectory.go @@ -39,7 +39,8 @@ func NewHRISDirectoryService(opts ...option.RequestOption) (r *HRISDirectoryServ // Read company directory and organization structure func (r *HRISDirectoryService) List(ctx context.Context, query HRISDirectoryListParams, opts ...option.RequestOption) (res *IndividualsPage, err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) path := "employer/directory" cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, query, &res, opts...) @@ -64,7 +65,8 @@ func (r *HRISDirectoryService) ListAutoPaging(ctx context.Context, query HRISDir // Deprecated: use `List` instead func (r *HRISDirectoryService) ListIndividuals(ctx context.Context, body HRISDirectoryListIndividualsParams, opts ...option.RequestOption) (res *IndividualsPage, err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) path := "employer/directory" cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, body, &res, opts...) @@ -89,7 +91,7 @@ func (r *HRISDirectoryService) ListIndividualsAutoPaging(ctx context.Context, bo type IndividualsPage struct { // The array of employees. Individuals []IndividualInDirectory `json:"individuals"` - Paging shared.Paging `json:"paging,required"` + Paging shared.Paging `json:"paging" api:"required"` JSON individualsPageJSON `json:"-"` cfg *requestconfig.RequestConfig res *http.Response @@ -120,7 +122,9 @@ func (r *IndividualsPage) GetNextPage() (res *IndividualsPage, err error) { } cfg := r.cfg.Clone(r.cfg.Context) - next := r.Paging.Offset + offset := r.Paging.Offset + length := int64(len(r.Individuals)) + next := offset + length if next < r.Paging.Count && next != 0 { err = cfg.Apply(option.WithQuery("offset", strconv.FormatInt(next, 10))) @@ -195,19 +199,19 @@ func (r *IndividualsPageAutoPager) Index() int { type IndividualInDirectory struct { // A stable Finch `id` (UUID v4) for an individual in the company. - ID string `json:"id,required" format:"uuid"` + ID string `json:"id" api:"required" format:"uuid"` // The department object. - Department IndividualInDirectoryDepartment `json:"department,required,nullable"` + Department IndividualInDirectoryDepartment `json:"department" api:"required,nullable"` // The legal first name of the individual. - FirstName string `json:"first_name,required,nullable"` + FirstName string `json:"first_name" api:"required,nullable"` // `true` if the individual is an active employee or contractor at the company. - IsActive bool `json:"is_active,required,nullable"` + IsActive bool `json:"is_active" api:"required,nullable"` // The legal last name of the individual. - LastName string `json:"last_name,required,nullable"` + LastName string `json:"last_name" api:"required,nullable"` // The manager object. - Manager IndividualInDirectoryManager `json:"manager,required,nullable"` + Manager IndividualInDirectoryManager `json:"manager" api:"required,nullable"` // The legal middle name of the individual. - MiddleName string `json:"middle_name,required,nullable"` + MiddleName string `json:"middle_name" api:"required,nullable"` JSON individualInDirectoryJSON `json:"-"` } @@ -236,7 +240,7 @@ func (r individualInDirectoryJSON) RawJSON() string { // The department object. type IndividualInDirectoryDepartment struct { // The name of the department. - Name string `json:"name,nullable"` + Name string `json:"name" api:"nullable"` JSON individualInDirectoryDepartmentJSON `json:"-"` } @@ -259,7 +263,7 @@ func (r individualInDirectoryDepartmentJSON) RawJSON() string { // The manager object. type IndividualInDirectoryManager struct { // A stable Finch `id` (UUID v4) for an individual in the company. - ID string `json:"id,required" format:"uuid"` + ID string `json:"id" api:"required" format:"uuid"` JSON individualInDirectoryManagerJSON `json:"-"` } @@ -280,6 +284,8 @@ func (r individualInDirectoryManagerJSON) RawJSON() string { } type HRISDirectoryListParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` // Number of employees to return (defaults to all) Limit param.Field[int64] `query:"limit"` // Index to start from (defaults to 0) @@ -296,6 +302,8 @@ func (r HRISDirectoryListParams) URLQuery() (v url.Values) { } type HRISDirectoryListIndividualsParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` // Number of employees to return (defaults to all) Limit param.Field[int64] `query:"limit"` // Index to start from (defaults to 0) diff --git a/hrisdirectory_test.go b/hrisdirectory_test.go index 656a8360..70d90d5b 100644 --- a/hrisdirectory_test.go +++ b/hrisdirectory_test.go @@ -24,10 +24,13 @@ func TestHRISDirectoryListWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Directory.List(context.TODO(), finchgo.HRISDirectoryListParams{ - Limit: finchgo.F(int64(0)), - Offset: finchgo.F(int64(0)), + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), + Limit: finchgo.F(int64(0)), + Offset: finchgo.F(int64(0)), }) if err != nil { var apierr *finchgo.Error @@ -49,10 +52,13 @@ func TestHRISDirectoryListIndividualsWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Directory.ListIndividuals(context.TODO(), finchgo.HRISDirectoryListIndividualsParams{ - Limit: finchgo.F(int64(0)), - Offset: finchgo.F(int64(0)), + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), + Limit: finchgo.F(int64(0)), + Offset: finchgo.F(int64(0)), }) if err != nil { var apierr *finchgo.Error diff --git a/hrisdocument.go b/hrisdocument.go index 46683b26..d04d412e 100644 --- a/hrisdocument.go +++ b/hrisdocument.go @@ -42,38 +42,40 @@ func NewHRISDocumentService(opts ...option.RequestOption) (r *HRISDocumentServic // **Beta:** This endpoint is in beta and may change. Retrieve a list of // company-wide documents. func (r *HRISDocumentService) List(ctx context.Context, query HRISDocumentListParams, opts ...option.RequestOption) (res *HRISDocumentListResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "employer/documents" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) - return + return res, err } // **Beta:** This endpoint is in beta and may change. Retrieve details of a // specific document by its ID. -func (r *HRISDocumentService) Retreive(ctx context.Context, documentID string, opts ...option.RequestOption) (res *HRISDocumentRetreiveResponse, err error) { - opts = slices.Concat(r.Options, opts) +func (r *HRISDocumentService) Retreive(ctx context.Context, documentID string, query HRISDocumentRetreiveParams, opts ...option.RequestOption) (res *HRISDocumentRetreiveResponse, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) if documentID == "" { err = errors.New("missing required document_id parameter") - return + return nil, err } path := fmt.Sprintf("employer/documents/%s", documentID) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) + return res, err } type DocumentResponse struct { // A stable Finch id for the document. - ID string `json:"id,required" format:"uuid"` + ID string `json:"id" api:"required" format:"uuid"` // The ID of the individual associated with the document. This will be null for // employer-level documents. - IndividualID string `json:"individual_id,required,nullable"` + IndividualID string `json:"individual_id" api:"required,nullable"` // The type of document. - Type DocumentResponseType `json:"type,required"` + Type DocumentResponseType `json:"type" api:"required"` // A URL to access the document. Format: // `https://api.tryfinch.com/employer/documents/:document_id`. - URL string `json:"url,required" format:"uri"` + URL string `json:"url" api:"required" format:"uri"` // The year the document applies to, if available. - Year float64 `json:"year,required"` + Year float64 `json:"year" api:"required"` JSON documentResponseJSON `json:"-"` } @@ -117,11 +119,11 @@ func (r DocumentResponseType) IsKnown() bool { // filing status, dependents, and withholding details. type W42005 struct { // Detailed information specific to the 2005 W4 form. - Data W42005Data `json:"data,required"` + Data W42005Data `json:"data" api:"required"` // Specifies the form type, indicating that this document is a 2005 W4 form. - Type W42005Type `json:"type,required"` + Type W42005Type `json:"type" api:"required"` // The tax year this W4 document applies to. - Year float64 `json:"year,required"` + Year float64 `json:"year" api:"required"` JSON w42005JSON `json:"-"` } @@ -147,15 +149,15 @@ func (r W42005) implementsHRISDocumentRetreiveResponse() {} // Detailed information specific to the 2005 W4 form. type W42005Data struct { // Additional withholding amount (in cents). - AdditionalWithholding int64 `json:"additional_withholding,required"` + AdditionalWithholding int64 `json:"additional_withholding" api:"required"` // Indicates exemption status from federal tax withholding. - Exemption W42005DataExemption `json:"exemption,required,nullable"` + Exemption W42005DataExemption `json:"exemption" api:"required,nullable"` // The individual's filing status for tax purposes. - FilingStatus W42005DataFilingStatus `json:"filing_status,required,nullable"` + FilingStatus W42005DataFilingStatus `json:"filing_status" api:"required,nullable"` // The unique identifier for the individual associated with this 2005 W4 form. - IndividualID string `json:"individual_id,required" format:"uuid"` + IndividualID string `json:"individual_id" api:"required" format:"uuid"` // Total number of allowances claimed (in cents). - TotalNumberOfAllowances int64 `json:"total_number_of_allowances,required"` + TotalNumberOfAllowances int64 `json:"total_number_of_allowances" api:"required"` JSON w42005DataJSON `json:"-"` } @@ -230,11 +232,11 @@ func (r W42005Type) IsKnown() bool { // filing status, dependents, and withholding details. type W42020 struct { // Detailed information specific to the 2020 W4 form. - Data W42020Data `json:"data,required"` + Data W42020Data `json:"data" api:"required"` // Specifies the form type, indicating that this document is a 2020 W4 form. - Type W42020Type `json:"type,required"` + Type W42020Type `json:"type" api:"required"` // The tax year this W4 document applies to. - Year float64 `json:"year,required"` + Year float64 `json:"year" api:"required"` JSON w42020JSON `json:"-"` } @@ -261,21 +263,21 @@ func (r W42020) implementsHRISDocumentRetreiveResponse() {} type W42020Data struct { // Amount claimed for dependents other than qualifying children under 17 (in // cents). - AmountForOtherDependents int64 `json:"amount_for_other_dependents,required"` + AmountForOtherDependents int64 `json:"amount_for_other_dependents" api:"required"` // Amount claimed for dependents under 17 years old (in cents). - AmountForQualifyingChildrenUnder17 int64 `json:"amount_for_qualifying_children_under_17,required"` + AmountForQualifyingChildrenUnder17 int64 `json:"amount_for_qualifying_children_under_17" api:"required"` // Deductible expenses (in cents). - Deductions int64 `json:"deductions,required"` + Deductions int64 `json:"deductions" api:"required"` // Additional withholding amount (in cents). - ExtraWithholding int64 `json:"extra_withholding,required"` + ExtraWithholding int64 `json:"extra_withholding" api:"required"` // The individual's filing status for tax purposes. - FilingStatus W42020DataFilingStatus `json:"filing_status,required,nullable"` + FilingStatus W42020DataFilingStatus `json:"filing_status" api:"required,nullable"` // The unique identifier for the individual associated with this document. - IndividualID string `json:"individual_id,required" format:"uuid"` + IndividualID string `json:"individual_id" api:"required" format:"uuid"` // Additional income from sources outside of primary employment (in cents). - OtherIncome int64 `json:"other_income,required"` + OtherIncome int64 `json:"other_income" api:"required"` // Total amount claimed for dependents and other credits (in cents). - TotalClaimDependentAndOtherCredits int64 `json:"total_claim_dependent_and_other_credits,required"` + TotalClaimDependentAndOtherCredits int64 `json:"total_claim_dependent_and_other_credits" api:"required"` JSON w42020DataJSON `json:"-"` } @@ -334,8 +336,8 @@ func (r W42020Type) IsKnown() bool { } type HRISDocumentListResponse struct { - Documents []DocumentResponse `json:"documents,required"` - Paging shared.Paging `json:"paging,required"` + Documents []DocumentResponse `json:"documents" api:"required"` + Paging shared.Paging `json:"paging" api:"required"` JSON hrisDocumentListResponseJSON `json:"-"` } @@ -360,11 +362,11 @@ func (r hrisDocumentListResponseJSON) RawJSON() string { // filing status, dependents, and withholding details. type HRISDocumentRetreiveResponse struct { // This field can have the runtime type of [W42020Data], [W42005Data]. - Data interface{} `json:"data,required"` + Data interface{} `json:"data" api:"required"` // Specifies the form type, indicating that this document is a 2020 W4 form. - Type HRISDocumentRetreiveResponseType `json:"type,required"` + Type HRISDocumentRetreiveResponseType `json:"type" api:"required"` // The tax year this W4 document applies to. - Year float64 `json:"year,required"` + Year float64 `json:"year" api:"required"` JSON hrisDocumentRetreiveResponseJSON `json:"-"` union HRISDocumentRetreiveResponseUnion } @@ -442,6 +444,8 @@ func (r HRISDocumentRetreiveResponseType) IsKnown() bool { } type HRISDocumentListParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` // Comma-delimited list of stable Finch uuids for each individual. If empty, // defaults to all individuals IndividualIDs param.Field[[]string] `query:"individual_ids"` @@ -476,3 +480,17 @@ func (r HRISDocumentListParamsType) IsKnown() bool { } return false } + +type HRISDocumentRetreiveParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` +} + +// URLQuery serializes [HRISDocumentRetreiveParams]'s query parameters as +// `url.Values`. +func (r HRISDocumentRetreiveParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} diff --git a/hrisdocument_test.go b/hrisdocument_test.go index 294ebfd7..9b1257cc 100644 --- a/hrisdocument_test.go +++ b/hrisdocument_test.go @@ -24,8 +24,11 @@ func TestHRISDocumentListWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Documents.List(context.TODO(), finchgo.HRISDocumentListParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), IndividualIDs: finchgo.F([]string{"string"}), Limit: finchgo.F(int64(0)), Offset: finchgo.F(int64(0)), @@ -40,7 +43,7 @@ func TestHRISDocumentListWithOptionalParams(t *testing.T) { } } -func TestHRISDocumentRetreive(t *testing.T) { +func TestHRISDocumentRetreiveWithOptionalParams(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -51,8 +54,16 @@ func TestHRISDocumentRetreive(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), + ) + _, err := client.HRIS.Documents.Retreive( + context.TODO(), + "document_id", + finchgo.HRISDocumentRetreiveParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), + }, ) - _, err := client.HRIS.Documents.Retreive(context.TODO(), "document_id") if err != nil { var apierr *finchgo.Error if errors.As(err, &apierr) { diff --git a/hrisemployment.go b/hrisemployment.go index 25b213e2..244d6d1f 100644 --- a/hrisemployment.go +++ b/hrisemployment.go @@ -5,10 +5,12 @@ package finchgo import ( "context" "net/http" + "net/url" "reflect" "slices" "github.com/Finch-API/finch-api-go/internal/apijson" + "github.com/Finch-API/finch-api-go/internal/apiquery" "github.com/Finch-API/finch-api-go/internal/param" "github.com/Finch-API/finch-api-go/internal/requestconfig" "github.com/Finch-API/finch-api-go/option" @@ -37,12 +39,13 @@ func NewHRISEmploymentService(opts ...option.RequestOption) (r *HRISEmploymentSe } // Read individual employment and income data -func (r *HRISEmploymentService) GetMany(ctx context.Context, body HRISEmploymentGetManyParams, opts ...option.RequestOption) (res *pagination.ResponsesPage[EmploymentDataResponse], err error) { +func (r *HRISEmploymentService) GetMany(ctx context.Context, params HRISEmploymentGetManyParams, opts ...option.RequestOption) (res *pagination.ResponsesPage[EmploymentDataResponse], err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) path := "employer/employment" - cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodPost, path, body, &res, opts...) + cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodPost, path, params, &res, opts...) if err != nil { return nil, err } @@ -55,56 +58,62 @@ func (r *HRISEmploymentService) GetMany(ctx context.Context, body HRISEmployment } // Read individual employment and income data -func (r *HRISEmploymentService) GetManyAutoPaging(ctx context.Context, body HRISEmploymentGetManyParams, opts ...option.RequestOption) *pagination.ResponsesPageAutoPager[EmploymentDataResponse] { - return pagination.NewResponsesPageAutoPager(r.GetMany(ctx, body, opts...)) +func (r *HRISEmploymentService) GetManyAutoPaging(ctx context.Context, params HRISEmploymentGetManyParams, opts ...option.RequestOption) *pagination.ResponsesPageAutoPager[EmploymentDataResponse] { + return pagination.NewResponsesPageAutoPager(r.GetMany(ctx, params, opts...)) } type EmploymentData struct { // A stable Finch `id` (UUID v4) for an individual in the company. ID string `json:"id" format:"uuid"` // Worker's compensation classification code for this employee - ClassCode string `json:"class_code,nullable"` + ClassCode string `json:"class_code" api:"nullable"` Code float64 `json:"code"` - // This field can have the runtime type of [[]EmploymentDataObjectCustomField]. + // This field can have the runtime type of + // [[]EmploymentDataEmploymentDataResponseBodyCustomField]. CustomFields interface{} `json:"custom_fields"` - // This field can have the runtime type of [EmploymentDataObjectDepartment]. + // This field can have the runtime type of + // [EmploymentDataEmploymentDataResponseBodyDepartment]. Department interface{} `json:"department"` - // This field can have the runtime type of [EmploymentDataObjectEmployment]. + // This field can have the runtime type of + // [EmploymentDataEmploymentDataResponseBodyEmployment]. Employment interface{} `json:"employment"` - // The detailed employment status of the individual. Available options: `active`, - // `deceased`, `leave`, `onboarding`, `prehire`, `retired`, `terminated`. - EmploymentStatus EmploymentDataEmploymentStatus `json:"employment_status,nullable"` - EndDate string `json:"end_date,nullable"` + // The detailed employment status of the individual. + EmploymentStatus EmploymentDataEmploymentStatus `json:"employment_status" api:"nullable"` + EndDate string `json:"end_date" api:"nullable"` FinchCode string `json:"finch_code"` // The legal first name of the individual. - FirstName string `json:"first_name,nullable"` + FirstName string `json:"first_name" api:"nullable"` + // The FLSA status of the individual. Available options: `exempt`, `non_exempt`, + // `unknown`. + FlsaStatus EmploymentDataFlsaStatus `json:"flsa_status" api:"nullable"` // The employee's income as reported by the provider. This may not always be // annualized income, but may be in units of bi-weekly, semi-monthly, daily, etc, // depending on what information the provider returns. - Income Income `json:"income,nullable"` + Income Income `json:"income" api:"nullable"` // This field can have the runtime type of [[]Income]. IncomeHistory interface{} `json:"income_history"` // `true` if the individual an an active employee or contractor at the company. - IsActive bool `json:"is_active,nullable"` + IsActive bool `json:"is_active" api:"nullable"` // The legal last name of the individual. - LastName string `json:"last_name,nullable"` - LatestRehireDate string `json:"latest_rehire_date,nullable"` - Location Location `json:"location,nullable"` - // This field can have the runtime type of [EmploymentDataObjectManager]. + LastName string `json:"last_name" api:"nullable"` + LatestRehireDate string `json:"latest_rehire_date" api:"nullable"` + Location Location `json:"location" api:"nullable"` + // This field can have the runtime type of + // [EmploymentDataEmploymentDataResponseBodyManager]. Manager interface{} `json:"manager"` Message string `json:"message"` // The legal middle name of the individual. - MiddleName string `json:"middle_name,nullable"` + MiddleName string `json:"middle_name" api:"nullable"` Name string `json:"name"` // The source system's unique employment identifier for this individual - SourceID string `json:"source_id,nullable"` - StartDate string `json:"start_date,nullable"` + SourceID string `json:"source_id" api:"nullable"` + StartDate string `json:"start_date" api:"nullable"` // The current title of the individual. - Title string `json:"title,nullable"` + Title string `json:"title" api:"nullable"` // This field is deprecated in favour of `source_id` // // Deprecated: deprecated - WorkID string `json:"work_id,nullable"` + WorkID string `json:"work_id" api:"nullable"` JSON employmentDataJSON `json:"-"` union EmploymentDataUnion } @@ -121,6 +130,7 @@ type employmentDataJSON struct { EndDate apijson.Field FinchCode apijson.Field FirstName apijson.Field + FlsaStatus apijson.Field Income apijson.Field IncomeHistory apijson.Field IsActive apijson.Field @@ -155,13 +165,14 @@ func (r *EmploymentData) UnmarshalJSON(data []byte) (err error) { // AsUnion returns a [EmploymentDataUnion] interface which you can cast to the // specific types for more type safety. // -// Possible runtime types of the union are [EmploymentDataObject], -// [EmploymentDataBatchError]. +// Possible runtime types of the union are +// [EmploymentDataEmploymentDataResponseBody], [EmploymentDataBatchError]. func (r EmploymentData) AsUnion() EmploymentDataUnion { return r.union } -// Union satisfied by [EmploymentDataObject] or [EmploymentDataBatchError]. +// Union satisfied by [EmploymentDataEmploymentDataResponseBody] or +// [EmploymentDataBatchError]. type EmploymentDataUnion interface { implementsEmploymentData() } @@ -172,7 +183,7 @@ func init() { "", apijson.UnionVariant{ TypeFilter: gjson.JSON, - Type: reflect.TypeOf(EmploymentDataObject{}), + Type: reflect.TypeOf(EmploymentDataEmploymentDataResponseBody{}), }, apijson.UnionVariant{ TypeFilter: gjson.JSON, @@ -181,56 +192,58 @@ func init() { ) } -type EmploymentDataObject struct { +type EmploymentDataEmploymentDataResponseBody struct { // A stable Finch `id` (UUID v4) for an individual in the company. - ID string `json:"id,required" format:"uuid"` + ID string `json:"id" api:"required" format:"uuid"` // Worker's compensation classification code for this employee - ClassCode string `json:"class_code,required,nullable"` + ClassCode string `json:"class_code" api:"required,nullable"` // The department object. - Department EmploymentDataObjectDepartment `json:"department,required,nullable"` + Department EmploymentDataEmploymentDataResponseBodyDepartment `json:"department" api:"required,nullable"` // The employment object. - Employment EmploymentDataObjectEmployment `json:"employment,required,nullable"` - // The detailed employment status of the individual. Available options: `active`, - // `deceased`, `leave`, `onboarding`, `prehire`, `retired`, `terminated`. - EmploymentStatus EmploymentDataObjectEmploymentStatus `json:"employment_status,required,nullable"` - EndDate string `json:"end_date,required,nullable"` + Employment EmploymentDataEmploymentDataResponseBodyEmployment `json:"employment" api:"required,nullable"` + // The detailed employment status of the individual. + EmploymentStatus EmploymentDataEmploymentDataResponseBodyEmploymentStatus `json:"employment_status" api:"required,nullable"` + EndDate string `json:"end_date" api:"required,nullable"` // The legal first name of the individual. - FirstName string `json:"first_name,required,nullable"` + FirstName string `json:"first_name" api:"required,nullable"` + // The FLSA status of the individual. Available options: `exempt`, `non_exempt`, + // `unknown`. + FlsaStatus EmploymentDataEmploymentDataResponseBodyFlsaStatus `json:"flsa_status" api:"required,nullable"` // `true` if the individual an an active employee or contractor at the company. - IsActive bool `json:"is_active,required,nullable"` + IsActive bool `json:"is_active" api:"required,nullable"` // The legal last name of the individual. - LastName string `json:"last_name,required,nullable"` - LatestRehireDate string `json:"latest_rehire_date,required,nullable"` - Location Location `json:"location,required,nullable"` + LastName string `json:"last_name" api:"required,nullable"` + LatestRehireDate string `json:"latest_rehire_date" api:"required,nullable"` + Location Location `json:"location" api:"required,nullable"` // The manager object representing the manager of the individual within the org. - Manager EmploymentDataObjectManager `json:"manager,required,nullable"` + Manager EmploymentDataEmploymentDataResponseBodyManager `json:"manager" api:"required,nullable"` // The legal middle name of the individual. - MiddleName string `json:"middle_name,required,nullable"` - StartDate string `json:"start_date,required,nullable"` + MiddleName string `json:"middle_name" api:"required,nullable"` + StartDate string `json:"start_date" api:"required,nullable"` // The current title of the individual. - Title string `json:"title,required,nullable"` + Title string `json:"title" api:"required,nullable"` // Custom fields for the individual. These are fields which are defined by the // employer in the system. Custom fields are not currently supported for assisted // connections. - CustomFields []EmploymentDataObjectCustomField `json:"custom_fields,nullable"` + CustomFields []EmploymentDataEmploymentDataResponseBodyCustomField `json:"custom_fields" api:"nullable"` // The employee's income as reported by the provider. This may not always be // annualized income, but may be in units of bi-weekly, semi-monthly, daily, etc, // depending on what information the provider returns. - Income Income `json:"income,nullable"` + Income Income `json:"income" api:"nullable"` // The array of income history. - IncomeHistory []Income `json:"income_history,nullable"` + IncomeHistory []Income `json:"income_history" api:"nullable"` // The source system's unique employment identifier for this individual - SourceID string `json:"source_id,nullable"` + SourceID string `json:"source_id" api:"nullable"` // This field is deprecated in favour of `source_id` // // Deprecated: deprecated - WorkID string `json:"work_id,nullable"` - JSON employmentDataObjectJSON `json:"-"` + WorkID string `json:"work_id" api:"nullable"` + JSON employmentDataEmploymentDataResponseBodyJSON `json:"-"` } -// employmentDataObjectJSON contains the JSON metadata for the struct -// [EmploymentDataObject] -type employmentDataObjectJSON struct { +// employmentDataEmploymentDataResponseBodyJSON contains the JSON metadata for the +// struct [EmploymentDataEmploymentDataResponseBody] +type employmentDataEmploymentDataResponseBodyJSON struct { ID apijson.Field ClassCode apijson.Field Department apijson.Field @@ -238,6 +251,7 @@ type employmentDataObjectJSON struct { EmploymentStatus apijson.Field EndDate apijson.Field FirstName apijson.Field + FlsaStatus apijson.Field IsActive apijson.Field LastName apijson.Field LatestRehireDate apijson.Field @@ -255,181 +269,198 @@ type employmentDataObjectJSON struct { ExtraFields map[string]apijson.Field } -func (r *EmploymentDataObject) UnmarshalJSON(data []byte) (err error) { +func (r *EmploymentDataEmploymentDataResponseBody) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -func (r employmentDataObjectJSON) RawJSON() string { +func (r employmentDataEmploymentDataResponseBodyJSON) RawJSON() string { return r.raw } -func (r EmploymentDataObject) implementsEmploymentData() {} +func (r EmploymentDataEmploymentDataResponseBody) implementsEmploymentData() {} // The department object. -type EmploymentDataObjectDepartment struct { +type EmploymentDataEmploymentDataResponseBodyDepartment struct { // The name of the department associated with the individual. - Name string `json:"name,required,nullable"` - JSON employmentDataObjectDepartmentJSON `json:"-"` + Name string `json:"name" api:"required,nullable"` + JSON employmentDataEmploymentDataResponseBodyDepartmentJSON `json:"-"` } -// employmentDataObjectDepartmentJSON contains the JSON metadata for the struct -// [EmploymentDataObjectDepartment] -type employmentDataObjectDepartmentJSON struct { +// employmentDataEmploymentDataResponseBodyDepartmentJSON contains the JSON +// metadata for the struct [EmploymentDataEmploymentDataResponseBodyDepartment] +type employmentDataEmploymentDataResponseBodyDepartmentJSON struct { Name apijson.Field raw string ExtraFields map[string]apijson.Field } -func (r *EmploymentDataObjectDepartment) UnmarshalJSON(data []byte) (err error) { +func (r *EmploymentDataEmploymentDataResponseBodyDepartment) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -func (r employmentDataObjectDepartmentJSON) RawJSON() string { +func (r employmentDataEmploymentDataResponseBodyDepartmentJSON) RawJSON() string { return r.raw } // The employment object. -type EmploymentDataObjectEmployment struct { +type EmploymentDataEmploymentDataResponseBodyEmployment struct { // The secondary employment type of the individual. Options: `full_time`, // `part_time`, `intern`, `temp`, `seasonal` and `individual_contractor`. - Subtype EmploymentDataObjectEmploymentSubtype `json:"subtype,required,nullable"` + Subtype EmploymentDataEmploymentDataResponseBodyEmploymentSubtype `json:"subtype" api:"required,nullable"` // The main employment type of the individual. - Type EmploymentDataObjectEmploymentType `json:"type,required,nullable"` - JSON employmentDataObjectEmploymentJSON `json:"-"` + Type EmploymentDataEmploymentDataResponseBodyEmploymentType `json:"type" api:"required,nullable"` + JSON employmentDataEmploymentDataResponseBodyEmploymentJSON `json:"-"` } -// employmentDataObjectEmploymentJSON contains the JSON metadata for the struct -// [EmploymentDataObjectEmployment] -type employmentDataObjectEmploymentJSON struct { +// employmentDataEmploymentDataResponseBodyEmploymentJSON contains the JSON +// metadata for the struct [EmploymentDataEmploymentDataResponseBodyEmployment] +type employmentDataEmploymentDataResponseBodyEmploymentJSON struct { Subtype apijson.Field Type apijson.Field raw string ExtraFields map[string]apijson.Field } -func (r *EmploymentDataObjectEmployment) UnmarshalJSON(data []byte) (err error) { +func (r *EmploymentDataEmploymentDataResponseBodyEmployment) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -func (r employmentDataObjectEmploymentJSON) RawJSON() string { +func (r employmentDataEmploymentDataResponseBodyEmploymentJSON) RawJSON() string { return r.raw } // The secondary employment type of the individual. Options: `full_time`, // `part_time`, `intern`, `temp`, `seasonal` and `individual_contractor`. -type EmploymentDataObjectEmploymentSubtype string +type EmploymentDataEmploymentDataResponseBodyEmploymentSubtype string const ( - EmploymentDataObjectEmploymentSubtypeFullTime EmploymentDataObjectEmploymentSubtype = "full_time" - EmploymentDataObjectEmploymentSubtypeIntern EmploymentDataObjectEmploymentSubtype = "intern" - EmploymentDataObjectEmploymentSubtypePartTime EmploymentDataObjectEmploymentSubtype = "part_time" - EmploymentDataObjectEmploymentSubtypeTemp EmploymentDataObjectEmploymentSubtype = "temp" - EmploymentDataObjectEmploymentSubtypeSeasonal EmploymentDataObjectEmploymentSubtype = "seasonal" - EmploymentDataObjectEmploymentSubtypeIndividualContractor EmploymentDataObjectEmploymentSubtype = "individual_contractor" + EmploymentDataEmploymentDataResponseBodyEmploymentSubtypeFullTime EmploymentDataEmploymentDataResponseBodyEmploymentSubtype = "full_time" + EmploymentDataEmploymentDataResponseBodyEmploymentSubtypeIntern EmploymentDataEmploymentDataResponseBodyEmploymentSubtype = "intern" + EmploymentDataEmploymentDataResponseBodyEmploymentSubtypePartTime EmploymentDataEmploymentDataResponseBodyEmploymentSubtype = "part_time" + EmploymentDataEmploymentDataResponseBodyEmploymentSubtypeTemp EmploymentDataEmploymentDataResponseBodyEmploymentSubtype = "temp" + EmploymentDataEmploymentDataResponseBodyEmploymentSubtypeSeasonal EmploymentDataEmploymentDataResponseBodyEmploymentSubtype = "seasonal" + EmploymentDataEmploymentDataResponseBodyEmploymentSubtypeIndividualContractor EmploymentDataEmploymentDataResponseBodyEmploymentSubtype = "individual_contractor" ) -func (r EmploymentDataObjectEmploymentSubtype) IsKnown() bool { +func (r EmploymentDataEmploymentDataResponseBodyEmploymentSubtype) IsKnown() bool { switch r { - case EmploymentDataObjectEmploymentSubtypeFullTime, EmploymentDataObjectEmploymentSubtypeIntern, EmploymentDataObjectEmploymentSubtypePartTime, EmploymentDataObjectEmploymentSubtypeTemp, EmploymentDataObjectEmploymentSubtypeSeasonal, EmploymentDataObjectEmploymentSubtypeIndividualContractor: + case EmploymentDataEmploymentDataResponseBodyEmploymentSubtypeFullTime, EmploymentDataEmploymentDataResponseBodyEmploymentSubtypeIntern, EmploymentDataEmploymentDataResponseBodyEmploymentSubtypePartTime, EmploymentDataEmploymentDataResponseBodyEmploymentSubtypeTemp, EmploymentDataEmploymentDataResponseBodyEmploymentSubtypeSeasonal, EmploymentDataEmploymentDataResponseBodyEmploymentSubtypeIndividualContractor: return true } return false } // The main employment type of the individual. -type EmploymentDataObjectEmploymentType string +type EmploymentDataEmploymentDataResponseBodyEmploymentType string + +const ( + EmploymentDataEmploymentDataResponseBodyEmploymentTypeEmployee EmploymentDataEmploymentDataResponseBodyEmploymentType = "employee" + EmploymentDataEmploymentDataResponseBodyEmploymentTypeContractor EmploymentDataEmploymentDataResponseBodyEmploymentType = "contractor" +) + +func (r EmploymentDataEmploymentDataResponseBodyEmploymentType) IsKnown() bool { + switch r { + case EmploymentDataEmploymentDataResponseBodyEmploymentTypeEmployee, EmploymentDataEmploymentDataResponseBodyEmploymentTypeContractor: + return true + } + return false +} + +// The detailed employment status of the individual. +type EmploymentDataEmploymentDataResponseBodyEmploymentStatus string const ( - EmploymentDataObjectEmploymentTypeEmployee EmploymentDataObjectEmploymentType = "employee" - EmploymentDataObjectEmploymentTypeContractor EmploymentDataObjectEmploymentType = "contractor" + EmploymentDataEmploymentDataResponseBodyEmploymentStatusActive EmploymentDataEmploymentDataResponseBodyEmploymentStatus = "active" + EmploymentDataEmploymentDataResponseBodyEmploymentStatusDeceased EmploymentDataEmploymentDataResponseBodyEmploymentStatus = "deceased" + EmploymentDataEmploymentDataResponseBodyEmploymentStatusLeave EmploymentDataEmploymentDataResponseBodyEmploymentStatus = "leave" + EmploymentDataEmploymentDataResponseBodyEmploymentStatusOnboarding EmploymentDataEmploymentDataResponseBodyEmploymentStatus = "onboarding" + EmploymentDataEmploymentDataResponseBodyEmploymentStatusPrehire EmploymentDataEmploymentDataResponseBodyEmploymentStatus = "prehire" + EmploymentDataEmploymentDataResponseBodyEmploymentStatusRetired EmploymentDataEmploymentDataResponseBodyEmploymentStatus = "retired" + EmploymentDataEmploymentDataResponseBodyEmploymentStatusTerminated EmploymentDataEmploymentDataResponseBodyEmploymentStatus = "terminated" ) -func (r EmploymentDataObjectEmploymentType) IsKnown() bool { +func (r EmploymentDataEmploymentDataResponseBodyEmploymentStatus) IsKnown() bool { switch r { - case EmploymentDataObjectEmploymentTypeEmployee, EmploymentDataObjectEmploymentTypeContractor: + case EmploymentDataEmploymentDataResponseBodyEmploymentStatusActive, EmploymentDataEmploymentDataResponseBodyEmploymentStatusDeceased, EmploymentDataEmploymentDataResponseBodyEmploymentStatusLeave, EmploymentDataEmploymentDataResponseBodyEmploymentStatusOnboarding, EmploymentDataEmploymentDataResponseBodyEmploymentStatusPrehire, EmploymentDataEmploymentDataResponseBodyEmploymentStatusRetired, EmploymentDataEmploymentDataResponseBodyEmploymentStatusTerminated: return true } return false } -// The detailed employment status of the individual. Available options: `active`, -// `deceased`, `leave`, `onboarding`, `prehire`, `retired`, `terminated`. -type EmploymentDataObjectEmploymentStatus string +// The FLSA status of the individual. Available options: `exempt`, `non_exempt`, +// `unknown`. +type EmploymentDataEmploymentDataResponseBodyFlsaStatus string const ( - EmploymentDataObjectEmploymentStatusActive EmploymentDataObjectEmploymentStatus = "active" - EmploymentDataObjectEmploymentStatusDeceased EmploymentDataObjectEmploymentStatus = "deceased" - EmploymentDataObjectEmploymentStatusLeave EmploymentDataObjectEmploymentStatus = "leave" - EmploymentDataObjectEmploymentStatusOnboarding EmploymentDataObjectEmploymentStatus = "onboarding" - EmploymentDataObjectEmploymentStatusPrehire EmploymentDataObjectEmploymentStatus = "prehire" - EmploymentDataObjectEmploymentStatusRetired EmploymentDataObjectEmploymentStatus = "retired" - EmploymentDataObjectEmploymentStatusTerminated EmploymentDataObjectEmploymentStatus = "terminated" + EmploymentDataEmploymentDataResponseBodyFlsaStatusExempt EmploymentDataEmploymentDataResponseBodyFlsaStatus = "exempt" + EmploymentDataEmploymentDataResponseBodyFlsaStatusNonExempt EmploymentDataEmploymentDataResponseBodyFlsaStatus = "non_exempt" + EmploymentDataEmploymentDataResponseBodyFlsaStatusUnknown EmploymentDataEmploymentDataResponseBodyFlsaStatus = "unknown" ) -func (r EmploymentDataObjectEmploymentStatus) IsKnown() bool { +func (r EmploymentDataEmploymentDataResponseBodyFlsaStatus) IsKnown() bool { switch r { - case EmploymentDataObjectEmploymentStatusActive, EmploymentDataObjectEmploymentStatusDeceased, EmploymentDataObjectEmploymentStatusLeave, EmploymentDataObjectEmploymentStatusOnboarding, EmploymentDataObjectEmploymentStatusPrehire, EmploymentDataObjectEmploymentStatusRetired, EmploymentDataObjectEmploymentStatusTerminated: + case EmploymentDataEmploymentDataResponseBodyFlsaStatusExempt, EmploymentDataEmploymentDataResponseBodyFlsaStatusNonExempt, EmploymentDataEmploymentDataResponseBodyFlsaStatusUnknown: return true } return false } // The manager object representing the manager of the individual within the org. -type EmploymentDataObjectManager struct { +type EmploymentDataEmploymentDataResponseBodyManager struct { // A stable Finch `id` (UUID v4) for an individual in the company. - ID string `json:"id,required" format:"uuid"` - JSON employmentDataObjectManagerJSON `json:"-"` + ID string `json:"id" api:"required" format:"uuid"` + JSON employmentDataEmploymentDataResponseBodyManagerJSON `json:"-"` } -// employmentDataObjectManagerJSON contains the JSON metadata for the struct -// [EmploymentDataObjectManager] -type employmentDataObjectManagerJSON struct { +// employmentDataEmploymentDataResponseBodyManagerJSON contains the JSON metadata +// for the struct [EmploymentDataEmploymentDataResponseBodyManager] +type employmentDataEmploymentDataResponseBodyManagerJSON struct { ID apijson.Field raw string ExtraFields map[string]apijson.Field } -func (r *EmploymentDataObjectManager) UnmarshalJSON(data []byte) (err error) { +func (r *EmploymentDataEmploymentDataResponseBodyManager) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -func (r employmentDataObjectManagerJSON) RawJSON() string { +func (r employmentDataEmploymentDataResponseBodyManagerJSON) RawJSON() string { return r.raw } -type EmploymentDataObjectCustomField struct { - Name string `json:"name,nullable"` - Value EmploymentDataObjectCustomFieldsValueUnion `json:"value,nullable"` - JSON employmentDataObjectCustomFieldJSON `json:"-"` +type EmploymentDataEmploymentDataResponseBodyCustomField struct { + Name string `json:"name" api:"nullable"` + Value EmploymentDataEmploymentDataResponseBodyCustomFieldsValueUnion `json:"value" api:"nullable"` + JSON employmentDataEmploymentDataResponseBodyCustomFieldJSON `json:"-"` } -// employmentDataObjectCustomFieldJSON contains the JSON metadata for the struct -// [EmploymentDataObjectCustomField] -type employmentDataObjectCustomFieldJSON struct { +// employmentDataEmploymentDataResponseBodyCustomFieldJSON contains the JSON +// metadata for the struct [EmploymentDataEmploymentDataResponseBodyCustomField] +type employmentDataEmploymentDataResponseBodyCustomFieldJSON struct { Name apijson.Field Value apijson.Field raw string ExtraFields map[string]apijson.Field } -func (r *EmploymentDataObjectCustomField) UnmarshalJSON(data []byte) (err error) { +func (r *EmploymentDataEmploymentDataResponseBodyCustomField) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -func (r employmentDataObjectCustomFieldJSON) RawJSON() string { +func (r employmentDataEmploymentDataResponseBodyCustomFieldJSON) RawJSON() string { return r.raw } // Union satisfied by [shared.UnionString], -// [EmploymentDataObjectCustomFieldsValueArray], [shared.UnionFloat] or -// [shared.UnionBool]. -type EmploymentDataObjectCustomFieldsValueUnion interface { - ImplementsEmploymentDataObjectCustomFieldsValueUnion() +// [EmploymentDataEmploymentDataResponseBodyCustomFieldsValueArray], +// [shared.UnionFloat] or [shared.UnionBool]. +type EmploymentDataEmploymentDataResponseBodyCustomFieldsValueUnion interface { + ImplementsEmploymentDataEmploymentDataResponseBodyCustomFieldsValueUnion() } func init() { apijson.RegisterUnion( - reflect.TypeOf((*EmploymentDataObjectCustomFieldsValueUnion)(nil)).Elem(), + reflect.TypeOf((*EmploymentDataEmploymentDataResponseBodyCustomFieldsValueUnion)(nil)).Elem(), "", apijson.UnionVariant{ TypeFilter: gjson.String, @@ -437,7 +468,7 @@ func init() { }, apijson.UnionVariant{ TypeFilter: gjson.JSON, - Type: reflect.TypeOf(EmploymentDataObjectCustomFieldsValueArray{}), + Type: reflect.TypeOf(EmploymentDataEmploymentDataResponseBodyCustomFieldsValueArray{}), }, apijson.UnionVariant{ TypeFilter: gjson.Number, @@ -454,15 +485,15 @@ func init() { ) } -type EmploymentDataObjectCustomFieldsValueArray []interface{} +type EmploymentDataEmploymentDataResponseBodyCustomFieldsValueArray []interface{} -func (r EmploymentDataObjectCustomFieldsValueArray) ImplementsEmploymentDataObjectCustomFieldsValueUnion() { +func (r EmploymentDataEmploymentDataResponseBodyCustomFieldsValueArray) ImplementsEmploymentDataEmploymentDataResponseBodyCustomFieldsValueUnion() { } type EmploymentDataBatchError struct { - Code float64 `json:"code,required"` - Message string `json:"message,required"` - Name string `json:"name,required"` + Code float64 `json:"code" api:"required"` + Message string `json:"message" api:"required"` + Name string `json:"name" api:"required"` FinchCode string `json:"finch_code"` JSON employmentDataBatchErrorJSON `json:"-"` } @@ -488,8 +519,7 @@ func (r employmentDataBatchErrorJSON) RawJSON() string { func (r EmploymentDataBatchError) implementsEmploymentData() {} -// The detailed employment status of the individual. Available options: `active`, -// `deceased`, `leave`, `onboarding`, `prehire`, `retired`, `terminated`. +// The detailed employment status of the individual. type EmploymentDataEmploymentStatus string const ( @@ -510,11 +540,29 @@ func (r EmploymentDataEmploymentStatus) IsKnown() bool { return false } +// The FLSA status of the individual. Available options: `exempt`, `non_exempt`, +// `unknown`. +type EmploymentDataFlsaStatus string + +const ( + EmploymentDataFlsaStatusExempt EmploymentDataFlsaStatus = "exempt" + EmploymentDataFlsaStatusNonExempt EmploymentDataFlsaStatus = "non_exempt" + EmploymentDataFlsaStatusUnknown EmploymentDataFlsaStatus = "unknown" +) + +func (r EmploymentDataFlsaStatus) IsKnown() bool { + switch r { + case EmploymentDataFlsaStatusExempt, EmploymentDataFlsaStatusNonExempt, EmploymentDataFlsaStatusUnknown: + return true + } + return false +} + type EmploymentDataResponse struct { - Body EmploymentData `json:"body,required"` - Code int64 `json:"code,required"` + Body EmploymentData `json:"body" api:"required"` + Code int64 `json:"code" api:"required"` // A stable Finch `id` (UUID v4) for an individual in the company. - IndividualID string `json:"individual_id,required" format:"uuid"` + IndividualID string `json:"individual_id" api:"required" format:"uuid"` JSON employmentDataResponseJSON `json:"-"` } @@ -538,18 +586,29 @@ func (r employmentDataResponseJSON) RawJSON() string { type HRISEmploymentGetManyParams struct { // The array of batch requests. - Requests param.Field[[]HRISEmploymentGetManyParamsRequest] `json:"requests,required"` + Requests param.Field[[]HRISEmploymentGetManyParamsRequest] `json:"requests" api:"required"` + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` } func (r HRISEmploymentGetManyParams) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } +// URLQuery serializes [HRISEmploymentGetManyParams]'s query parameters as +// `url.Values`. +func (r HRISEmploymentGetManyParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + type HRISEmploymentGetManyParamsRequest struct { // A stable Finch `id` (UUID v4) for an individual in the company. There is no // limit to the number of `individual_id` to send per request. It is preferantial // to send all ids in a single request for Finch to optimize provider rate-limits. - IndividualID param.Field[string] `json:"individual_id,required"` + IndividualID param.Field[string] `json:"individual_id" api:"required"` } func (r HRISEmploymentGetManyParamsRequest) MarshalJSON() (data []byte, err error) { diff --git a/hrisemployment_test.go b/hrisemployment_test.go index 8ae337c8..e3d618e7 100644 --- a/hrisemployment_test.go +++ b/hrisemployment_test.go @@ -13,7 +13,7 @@ import ( "github.com/Finch-API/finch-api-go/option" ) -func TestHRISEmploymentGetMany(t *testing.T) { +func TestHRISEmploymentGetManyWithOptionalParams(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -24,11 +24,14 @@ func TestHRISEmploymentGetMany(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Employments.GetMany(context.TODO(), finchgo.HRISEmploymentGetManyParams{ Requests: finchgo.F([]finchgo.HRISEmploymentGetManyParamsRequest{{ IndividualID: finchgo.F("individual_id"), }}), + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), }) if err != nil { var apierr *finchgo.Error diff --git a/hrisindividual.go b/hrisindividual.go index 11098ad2..7cfa92e2 100644 --- a/hrisindividual.go +++ b/hrisindividual.go @@ -5,10 +5,12 @@ package finchgo import ( "context" "net/http" + "net/url" "reflect" "slices" "github.com/Finch-API/finch-api-go/internal/apijson" + "github.com/Finch-API/finch-api-go/internal/apiquery" "github.com/Finch-API/finch-api-go/internal/param" "github.com/Finch-API/finch-api-go/internal/requestconfig" "github.com/Finch-API/finch-api-go/option" @@ -36,12 +38,13 @@ func NewHRISIndividualService(opts ...option.RequestOption) (r *HRISIndividualSe } // Read individual data, excluding income and employment data -func (r *HRISIndividualService) GetMany(ctx context.Context, body HRISIndividualGetManyParams, opts ...option.RequestOption) (res *pagination.ResponsesPage[IndividualResponse], err error) { +func (r *HRISIndividualService) GetMany(ctx context.Context, params HRISIndividualGetManyParams, opts ...option.RequestOption) (res *pagination.ResponsesPage[IndividualResponse], err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) path := "employer/individual" - cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodPost, path, body, &res, opts...) + cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodPost, path, params, &res, opts...) if err != nil { return nil, err } @@ -54,44 +57,46 @@ func (r *HRISIndividualService) GetMany(ctx context.Context, body HRISIndividual } // Read individual data, excluding income and employment data -func (r *HRISIndividualService) GetManyAutoPaging(ctx context.Context, body HRISIndividualGetManyParams, opts ...option.RequestOption) *pagination.ResponsesPageAutoPager[IndividualResponse] { - return pagination.NewResponsesPageAutoPager(r.GetMany(ctx, body, opts...)) +func (r *HRISIndividualService) GetManyAutoPaging(ctx context.Context, params HRISIndividualGetManyParams, opts ...option.RequestOption) *pagination.ResponsesPageAutoPager[IndividualResponse] { + return pagination.NewResponsesPageAutoPager(r.GetMany(ctx, params, opts...)) } type Individual struct { // A stable Finch `id` (UUID v4) for an individual in the company. ID string `json:"id" format:"uuid"` Code float64 `json:"code"` - Dob string `json:"dob,nullable"` - // This field can have the runtime type of [[]IndividualObjectEmail]. + Dob string `json:"dob" api:"nullable"` + // This field can have the runtime type of + // [[]IndividualIndividualResponseBodyEmail]. Emails interface{} `json:"emails"` // Social Security Number of the individual in **encrypted** format. This field is // only available with the `ssn` scope enabled and the // `options: { include: ['ssn'] }` param set in the body. - EncryptedSsn string `json:"encrypted_ssn,nullable"` + EncryptedSsn string `json:"encrypted_ssn" api:"nullable"` // The EEOC-defined ethnicity of the individual. - Ethnicity IndividualEthnicity `json:"ethnicity,nullable"` + Ethnicity IndividualEthnicity `json:"ethnicity" api:"nullable"` FinchCode string `json:"finch_code"` // The legal first name of the individual. - FirstName string `json:"first_name,nullable"` + FirstName string `json:"first_name" api:"nullable"` // The gender of the individual. - Gender IndividualGender `json:"gender,nullable"` + Gender IndividualGender `json:"gender" api:"nullable"` // The legal last name of the individual. - LastName string `json:"last_name,nullable"` + LastName string `json:"last_name" api:"nullable"` Message string `json:"message"` // The legal middle name of the individual. - MiddleName string `json:"middle_name,nullable"` + MiddleName string `json:"middle_name" api:"nullable"` Name string `json:"name"` - // This field can have the runtime type of [[]IndividualObjectPhoneNumber]. + // This field can have the runtime type of + // [[]IndividualIndividualResponseBodyPhoneNumber]. PhoneNumbers interface{} `json:"phone_numbers"` // The preferred name of the individual. - PreferredName string `json:"preferred_name,nullable"` - Residence Location `json:"residence,nullable"` + PreferredName string `json:"preferred_name" api:"nullable"` + Residence Location `json:"residence" api:"nullable"` // Social Security Number of the individual. This field is only available with the // `ssn` scope enabled and the `options: { include: ['ssn'] }` param set in the // body. // [Click here to learn more about enabling the SSN field](/developer-resources/Enable-SSN-Field). - Ssn string `json:"ssn,nullable"` + Ssn string `json:"ssn" api:"nullable"` JSON individualJSON `json:"-"` union IndividualUnion } @@ -135,13 +140,13 @@ func (r *Individual) UnmarshalJSON(data []byte) (err error) { // AsUnion returns a [IndividualUnion] interface which you can cast to the specific // types for more type safety. // -// Possible runtime types of the union are [IndividualObject], +// Possible runtime types of the union are [IndividualIndividualResponseBody], // [IndividualBatchError]. func (r Individual) AsUnion() IndividualUnion { return r.union } -// Union satisfied by [IndividualObject] or [IndividualBatchError]. +// Union satisfied by [IndividualIndividualResponseBody] or [IndividualBatchError]. type IndividualUnion interface { implementsIndividual() } @@ -152,7 +157,7 @@ func init() { "", apijson.UnionVariant{ TypeFilter: gjson.JSON, - Type: reflect.TypeOf(IndividualObject{}), + Type: reflect.TypeOf(IndividualIndividualResponseBody{}), }, apijson.UnionVariant{ TypeFilter: gjson.JSON, @@ -161,40 +166,40 @@ func init() { ) } -type IndividualObject struct { +type IndividualIndividualResponseBody struct { // A stable Finch `id` (UUID v4) for an individual in the company. - ID string `json:"id,required" format:"uuid"` - Dob string `json:"dob,required,nullable"` + ID string `json:"id" api:"required" format:"uuid"` + Dob string `json:"dob" api:"required,nullable"` // The EEOC-defined ethnicity of the individual. - Ethnicity IndividualObjectEthnicity `json:"ethnicity,required,nullable"` + Ethnicity IndividualIndividualResponseBodyEthnicity `json:"ethnicity" api:"required,nullable"` // The legal first name of the individual. - FirstName string `json:"first_name,required,nullable"` + FirstName string `json:"first_name" api:"required,nullable"` // The gender of the individual. - Gender IndividualObjectGender `json:"gender,required,nullable"` + Gender IndividualIndividualResponseBodyGender `json:"gender" api:"required,nullable"` // The legal last name of the individual. - LastName string `json:"last_name,required,nullable"` + LastName string `json:"last_name" api:"required,nullable"` // The legal middle name of the individual. - MiddleName string `json:"middle_name,required,nullable"` - PhoneNumbers []IndividualObjectPhoneNumber `json:"phone_numbers,required,nullable"` + MiddleName string `json:"middle_name" api:"required,nullable"` + PhoneNumbers []IndividualIndividualResponseBodyPhoneNumber `json:"phone_numbers" api:"required,nullable"` // The preferred name of the individual. - PreferredName string `json:"preferred_name,required,nullable"` - Residence Location `json:"residence,required,nullable"` - Emails []IndividualObjectEmail `json:"emails,nullable"` + PreferredName string `json:"preferred_name" api:"required,nullable"` + Residence Location `json:"residence" api:"required,nullable"` + Emails []IndividualIndividualResponseBodyEmail `json:"emails" api:"nullable"` // Social Security Number of the individual in **encrypted** format. This field is // only available with the `ssn` scope enabled and the // `options: { include: ['ssn'] }` param set in the body. - EncryptedSsn string `json:"encrypted_ssn,nullable"` + EncryptedSsn string `json:"encrypted_ssn" api:"nullable"` // Social Security Number of the individual. This field is only available with the // `ssn` scope enabled and the `options: { include: ['ssn'] }` param set in the // body. // [Click here to learn more about enabling the SSN field](/developer-resources/Enable-SSN-Field). - Ssn string `json:"ssn,nullable"` - JSON individualObjectJSON `json:"-"` + Ssn string `json:"ssn" api:"nullable"` + JSON individualIndividualResponseBodyJSON `json:"-"` } -// individualObjectJSON contains the JSON metadata for the struct -// [IndividualObject] -type individualObjectJSON struct { +// individualIndividualResponseBodyJSON contains the JSON metadata for the struct +// [IndividualIndividualResponseBody] +type individualIndividualResponseBodyJSON struct { ID apijson.Field Dob apijson.Field Ethnicity apijson.Field @@ -212,136 +217,136 @@ type individualObjectJSON struct { ExtraFields map[string]apijson.Field } -func (r *IndividualObject) UnmarshalJSON(data []byte) (err error) { +func (r *IndividualIndividualResponseBody) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -func (r individualObjectJSON) RawJSON() string { +func (r individualIndividualResponseBodyJSON) RawJSON() string { return r.raw } -func (r IndividualObject) implementsIndividual() {} +func (r IndividualIndividualResponseBody) implementsIndividual() {} // The EEOC-defined ethnicity of the individual. -type IndividualObjectEthnicity string +type IndividualIndividualResponseBodyEthnicity string const ( - IndividualObjectEthnicityAsian IndividualObjectEthnicity = "asian" - IndividualObjectEthnicityWhite IndividualObjectEthnicity = "white" - IndividualObjectEthnicityBlackOrAfricanAmerican IndividualObjectEthnicity = "black_or_african_american" - IndividualObjectEthnicityNativeHawaiianOrPacificIslander IndividualObjectEthnicity = "native_hawaiian_or_pacific_islander" - IndividualObjectEthnicityAmericanIndianOrAlaskaNative IndividualObjectEthnicity = "american_indian_or_alaska_native" - IndividualObjectEthnicityHispanicOrLatino IndividualObjectEthnicity = "hispanic_or_latino" - IndividualObjectEthnicityTwoOrMoreRaces IndividualObjectEthnicity = "two_or_more_races" - IndividualObjectEthnicityDeclineToSpecify IndividualObjectEthnicity = "decline_to_specify" + IndividualIndividualResponseBodyEthnicityAsian IndividualIndividualResponseBodyEthnicity = "asian" + IndividualIndividualResponseBodyEthnicityWhite IndividualIndividualResponseBodyEthnicity = "white" + IndividualIndividualResponseBodyEthnicityBlackOrAfricanAmerican IndividualIndividualResponseBodyEthnicity = "black_or_african_american" + IndividualIndividualResponseBodyEthnicityNativeHawaiianOrPacificIslander IndividualIndividualResponseBodyEthnicity = "native_hawaiian_or_pacific_islander" + IndividualIndividualResponseBodyEthnicityAmericanIndianOrAlaskaNative IndividualIndividualResponseBodyEthnicity = "american_indian_or_alaska_native" + IndividualIndividualResponseBodyEthnicityHispanicOrLatino IndividualIndividualResponseBodyEthnicity = "hispanic_or_latino" + IndividualIndividualResponseBodyEthnicityTwoOrMoreRaces IndividualIndividualResponseBodyEthnicity = "two_or_more_races" + IndividualIndividualResponseBodyEthnicityDeclineToSpecify IndividualIndividualResponseBodyEthnicity = "decline_to_specify" ) -func (r IndividualObjectEthnicity) IsKnown() bool { +func (r IndividualIndividualResponseBodyEthnicity) IsKnown() bool { switch r { - case IndividualObjectEthnicityAsian, IndividualObjectEthnicityWhite, IndividualObjectEthnicityBlackOrAfricanAmerican, IndividualObjectEthnicityNativeHawaiianOrPacificIslander, IndividualObjectEthnicityAmericanIndianOrAlaskaNative, IndividualObjectEthnicityHispanicOrLatino, IndividualObjectEthnicityTwoOrMoreRaces, IndividualObjectEthnicityDeclineToSpecify: + case IndividualIndividualResponseBodyEthnicityAsian, IndividualIndividualResponseBodyEthnicityWhite, IndividualIndividualResponseBodyEthnicityBlackOrAfricanAmerican, IndividualIndividualResponseBodyEthnicityNativeHawaiianOrPacificIslander, IndividualIndividualResponseBodyEthnicityAmericanIndianOrAlaskaNative, IndividualIndividualResponseBodyEthnicityHispanicOrLatino, IndividualIndividualResponseBodyEthnicityTwoOrMoreRaces, IndividualIndividualResponseBodyEthnicityDeclineToSpecify: return true } return false } // The gender of the individual. -type IndividualObjectGender string +type IndividualIndividualResponseBodyGender string const ( - IndividualObjectGenderFemale IndividualObjectGender = "female" - IndividualObjectGenderMale IndividualObjectGender = "male" - IndividualObjectGenderOther IndividualObjectGender = "other" - IndividualObjectGenderDeclineToSpecify IndividualObjectGender = "decline_to_specify" + IndividualIndividualResponseBodyGenderFemale IndividualIndividualResponseBodyGender = "female" + IndividualIndividualResponseBodyGenderMale IndividualIndividualResponseBodyGender = "male" + IndividualIndividualResponseBodyGenderOther IndividualIndividualResponseBodyGender = "other" + IndividualIndividualResponseBodyGenderDeclineToSpecify IndividualIndividualResponseBodyGender = "decline_to_specify" ) -func (r IndividualObjectGender) IsKnown() bool { +func (r IndividualIndividualResponseBodyGender) IsKnown() bool { switch r { - case IndividualObjectGenderFemale, IndividualObjectGenderMale, IndividualObjectGenderOther, IndividualObjectGenderDeclineToSpecify: + case IndividualIndividualResponseBodyGenderFemale, IndividualIndividualResponseBodyGenderMale, IndividualIndividualResponseBodyGenderOther, IndividualIndividualResponseBodyGenderDeclineToSpecify: return true } return false } -type IndividualObjectPhoneNumber struct { - Data string `json:"data,required,nullable"` - Type IndividualObjectPhoneNumbersType `json:"type,required,nullable"` - JSON individualObjectPhoneNumberJSON `json:"-"` +type IndividualIndividualResponseBodyPhoneNumber struct { + Data string `json:"data" api:"required,nullable"` + Type IndividualIndividualResponseBodyPhoneNumbersType `json:"type" api:"required,nullable"` + JSON individualIndividualResponseBodyPhoneNumberJSON `json:"-"` } -// individualObjectPhoneNumberJSON contains the JSON metadata for the struct -// [IndividualObjectPhoneNumber] -type individualObjectPhoneNumberJSON struct { +// individualIndividualResponseBodyPhoneNumberJSON contains the JSON metadata for +// the struct [IndividualIndividualResponseBodyPhoneNumber] +type individualIndividualResponseBodyPhoneNumberJSON struct { Data apijson.Field Type apijson.Field raw string ExtraFields map[string]apijson.Field } -func (r *IndividualObjectPhoneNumber) UnmarshalJSON(data []byte) (err error) { +func (r *IndividualIndividualResponseBodyPhoneNumber) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -func (r individualObjectPhoneNumberJSON) RawJSON() string { +func (r individualIndividualResponseBodyPhoneNumberJSON) RawJSON() string { return r.raw } -type IndividualObjectPhoneNumbersType string +type IndividualIndividualResponseBodyPhoneNumbersType string const ( - IndividualObjectPhoneNumbersTypeWork IndividualObjectPhoneNumbersType = "work" - IndividualObjectPhoneNumbersTypePersonal IndividualObjectPhoneNumbersType = "personal" + IndividualIndividualResponseBodyPhoneNumbersTypeWork IndividualIndividualResponseBodyPhoneNumbersType = "work" + IndividualIndividualResponseBodyPhoneNumbersTypePersonal IndividualIndividualResponseBodyPhoneNumbersType = "personal" ) -func (r IndividualObjectPhoneNumbersType) IsKnown() bool { +func (r IndividualIndividualResponseBodyPhoneNumbersType) IsKnown() bool { switch r { - case IndividualObjectPhoneNumbersTypeWork, IndividualObjectPhoneNumbersTypePersonal: + case IndividualIndividualResponseBodyPhoneNumbersTypeWork, IndividualIndividualResponseBodyPhoneNumbersTypePersonal: return true } return false } -type IndividualObjectEmail struct { - Data string `json:"data,required"` - Type IndividualObjectEmailsType `json:"type,required,nullable"` - JSON individualObjectEmailJSON `json:"-"` +type IndividualIndividualResponseBodyEmail struct { + Data string `json:"data" api:"required"` + Type IndividualIndividualResponseBodyEmailsType `json:"type" api:"required,nullable"` + JSON individualIndividualResponseBodyEmailJSON `json:"-"` } -// individualObjectEmailJSON contains the JSON metadata for the struct -// [IndividualObjectEmail] -type individualObjectEmailJSON struct { +// individualIndividualResponseBodyEmailJSON contains the JSON metadata for the +// struct [IndividualIndividualResponseBodyEmail] +type individualIndividualResponseBodyEmailJSON struct { Data apijson.Field Type apijson.Field raw string ExtraFields map[string]apijson.Field } -func (r *IndividualObjectEmail) UnmarshalJSON(data []byte) (err error) { +func (r *IndividualIndividualResponseBodyEmail) UnmarshalJSON(data []byte) (err error) { return apijson.UnmarshalRoot(data, r) } -func (r individualObjectEmailJSON) RawJSON() string { +func (r individualIndividualResponseBodyEmailJSON) RawJSON() string { return r.raw } -type IndividualObjectEmailsType string +type IndividualIndividualResponseBodyEmailsType string const ( - IndividualObjectEmailsTypeWork IndividualObjectEmailsType = "work" - IndividualObjectEmailsTypePersonal IndividualObjectEmailsType = "personal" + IndividualIndividualResponseBodyEmailsTypeWork IndividualIndividualResponseBodyEmailsType = "work" + IndividualIndividualResponseBodyEmailsTypePersonal IndividualIndividualResponseBodyEmailsType = "personal" ) -func (r IndividualObjectEmailsType) IsKnown() bool { +func (r IndividualIndividualResponseBodyEmailsType) IsKnown() bool { switch r { - case IndividualObjectEmailsTypeWork, IndividualObjectEmailsTypePersonal: + case IndividualIndividualResponseBodyEmailsTypeWork, IndividualIndividualResponseBodyEmailsTypePersonal: return true } return false } type IndividualBatchError struct { - Code float64 `json:"code,required"` - Message string `json:"message,required"` - Name string `json:"name,required"` + Code float64 `json:"code" api:"required"` + Message string `json:"message" api:"required"` + Name string `json:"name" api:"required"` FinchCode string `json:"finch_code"` JSON individualBatchErrorJSON `json:"-"` } @@ -408,9 +413,9 @@ func (r IndividualGender) IsKnown() bool { } type IndividualResponse struct { - Body Individual `json:"body,required"` - Code int64 `json:"code,required"` - IndividualID string `json:"individual_id,required"` + Body Individual `json:"body" api:"required"` + Code int64 `json:"code" api:"required"` + IndividualID string `json:"individual_id" api:"required"` JSON individualResponseJSON `json:"-"` } @@ -433,14 +438,25 @@ func (r individualResponseJSON) RawJSON() string { } type HRISIndividualGetManyParams struct { - Options param.Field[HRISIndividualGetManyParamsOptions] `json:"options"` - Requests param.Field[[]HRISIndividualGetManyParamsRequest] `json:"requests"` + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` + Options param.Field[HRISIndividualGetManyParamsOptions] `json:"options"` + Requests param.Field[[]HRISIndividualGetManyParamsRequest] `json:"requests"` } func (r HRISIndividualGetManyParams) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } +// URLQuery serializes [HRISIndividualGetManyParams]'s query parameters as +// `url.Values`. +func (r HRISIndividualGetManyParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + type HRISIndividualGetManyParamsOptions struct { Include param.Field[[]string] `json:"include"` } diff --git a/hrisindividual_test.go b/hrisindividual_test.go index fed2a754..52b4b245 100644 --- a/hrisindividual_test.go +++ b/hrisindividual_test.go @@ -24,8 +24,11 @@ func TestHRISIndividualGetManyWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Individuals.GetMany(context.TODO(), finchgo.HRISIndividualGetManyParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), Options: finchgo.F(finchgo.HRISIndividualGetManyParamsOptions{ Include: finchgo.F([]string{"string"}), }), diff --git a/hrispayment.go b/hrispayment.go index d9a9bf94..288d7d63 100644 --- a/hrispayment.go +++ b/hrispayment.go @@ -39,7 +39,8 @@ func NewHRISPaymentService(opts ...option.RequestOption) (r *HRISPaymentService) // Read payroll and contractor related payments by the company. func (r *HRISPaymentService) List(ctx context.Context, query HRISPaymentListParams, opts ...option.RequestOption) (res *pagination.SinglePage[Payment], err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) path := "employer/payment" cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, query, &res, opts...) @@ -61,22 +62,22 @@ func (r *HRISPaymentService) ListAutoPaging(ctx context.Context, query HRISPayme type Payment struct { // The unique id for the payment. - ID string `json:"id,required" format:"uuid"` - CompanyDebit Money `json:"company_debit,required,nullable"` - DebitDate string `json:"debit_date,required,nullable"` - EmployeeTaxes Money `json:"employee_taxes,required,nullable"` - EmployerTaxes Money `json:"employer_taxes,required,nullable"` - GrossPay Money `json:"gross_pay,required,nullable"` + ID string `json:"id" api:"required" format:"uuid"` + CompanyDebit Money `json:"company_debit" api:"required,nullable"` + DebitDate string `json:"debit_date" api:"required,nullable"` + EmployeeTaxes Money `json:"employee_taxes" api:"required,nullable"` + EmployerTaxes Money `json:"employer_taxes" api:"required,nullable"` + GrossPay Money `json:"gross_pay" api:"required,nullable"` // Array of every individual on this payment. - IndividualIDs []string `json:"individual_ids,required,nullable" format:"uuid"` - NetPay Money `json:"net_pay,required,nullable"` - PayDate string `json:"pay_date,required,nullable"` + IndividualIDs []string `json:"individual_ids" api:"required,nullable" format:"uuid"` + NetPay Money `json:"net_pay" api:"required,nullable"` + PayDate string `json:"pay_date" api:"required,nullable"` // List of pay frequencies associated with this payment. - PayFrequencies []PaymentPayFrequency `json:"pay_frequencies,required,nullable"` + PayFrequencies []PaymentPayFrequency `json:"pay_frequencies" api:"required,nullable"` // Array of the Finch id (uuidv4) of every pay group associated with this payment. - PayGroupIDs []string `json:"pay_group_ids,required,nullable" format:"uuid"` + PayGroupIDs []string `json:"pay_group_ids" api:"required,nullable" format:"uuid"` // The pay period object. - PayPeriod PaymentPayPeriod `json:"pay_period,required,nullable"` + PayPeriod PaymentPayPeriod `json:"pay_period" api:"required,nullable"` JSON paymentJSON `json:"-"` } @@ -130,8 +131,8 @@ func (r PaymentPayFrequency) IsKnown() bool { // The pay period object. type PaymentPayPeriod struct { - EndDate string `json:"end_date,required,nullable"` - StartDate string `json:"start_date,required,nullable"` + EndDate string `json:"end_date" api:"required,nullable"` + StartDate string `json:"start_date" api:"required,nullable"` JSON paymentPayPeriodJSON `json:"-"` } @@ -154,11 +155,13 @@ func (r paymentPayPeriodJSON) RawJSON() string { type HRISPaymentListParams struct { // The end date to retrieve payments by a company (inclusive) in `YYYY-MM-DD` - // format. - EndDate param.Field[time.Time] `query:"end_date,required" format:"date"` + // format. Filters payments by their **pay_date** field. + EndDate param.Field[time.Time] `query:"end_date" api:"required" format:"date"` // The start date to retrieve payments by a company (inclusive) in `YYYY-MM-DD` - // format. - StartDate param.Field[time.Time] `query:"start_date,required" format:"date"` + // format. Filters payments by their **pay_date** field. + StartDate param.Field[time.Time] `query:"start_date" api:"required" format:"date"` + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` } // URLQuery serializes [HRISPaymentListParams]'s query parameters as `url.Values`. diff --git a/hrispayment_test.go b/hrispayment_test.go index df34cbde..89244d4d 100644 --- a/hrispayment_test.go +++ b/hrispayment_test.go @@ -14,7 +14,7 @@ import ( "github.com/Finch-API/finch-api-go/option" ) -func TestHRISPaymentList(t *testing.T) { +func TestHRISPaymentListWithOptionalParams(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -25,10 +25,13 @@ func TestHRISPaymentList(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.Payments.List(context.TODO(), finchgo.HRISPaymentListParams{ EndDate: finchgo.F(time.Now()), StartDate: finchgo.F(time.Now()), + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), }) if err != nil { var apierr *finchgo.Error diff --git a/hrispaystatement.go b/hrispaystatement.go index 270b490f..18ed7500 100644 --- a/hrispaystatement.go +++ b/hrispaystatement.go @@ -5,10 +5,12 @@ package finchgo import ( "context" "net/http" + "net/url" "reflect" "slices" "github.com/Finch-API/finch-api-go/internal/apijson" + "github.com/Finch-API/finch-api-go/internal/apiquery" "github.com/Finch-API/finch-api-go/internal/param" "github.com/Finch-API/finch-api-go/internal/requestconfig" "github.com/Finch-API/finch-api-go/option" @@ -39,12 +41,13 @@ func NewHRISPayStatementService(opts ...option.RequestOption) (r *HRISPayStateme // // Deduction and contribution types are supported by the payroll systems that // supports Benefits. -func (r *HRISPayStatementService) GetMany(ctx context.Context, body HRISPayStatementGetManyParams, opts ...option.RequestOption) (res *pagination.ResponsesPage[PayStatementResponse], err error) { +func (r *HRISPayStatementService) GetMany(ctx context.Context, params HRISPayStatementGetManyParams, opts ...option.RequestOption) (res *pagination.ResponsesPage[PayStatementResponse], err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) path := "employer/pay-statement" - cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodPost, path, body, &res, opts...) + cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodPost, path, params, &res, opts...) if err != nil { return nil, err } @@ -60,28 +63,28 @@ func (r *HRISPayStatementService) GetMany(ctx context.Context, body HRISPayState // // Deduction and contribution types are supported by the payroll systems that // supports Benefits. -func (r *HRISPayStatementService) GetManyAutoPaging(ctx context.Context, body HRISPayStatementGetManyParams, opts ...option.RequestOption) *pagination.ResponsesPageAutoPager[PayStatementResponse] { - return pagination.NewResponsesPageAutoPager(r.GetMany(ctx, body, opts...)) +func (r *HRISPayStatementService) GetManyAutoPaging(ctx context.Context, params HRISPayStatementGetManyParams, opts ...option.RequestOption) *pagination.ResponsesPageAutoPager[PayStatementResponse] { + return pagination.NewResponsesPageAutoPager(r.GetMany(ctx, params, opts...)) } type PayStatement struct { // The array of earnings objects associated with this pay statement - Earnings []PayStatementEarning `json:"earnings,required,nullable"` + Earnings []PayStatementEarning `json:"earnings" api:"required,nullable"` // The array of deductions objects associated with this pay statement. - EmployeeDeductions []PayStatementEmployeeDeduction `json:"employee_deductions,required,nullable"` - EmployerContributions []PayStatementEmployerContribution `json:"employer_contributions,required,nullable"` - GrossPay Money `json:"gross_pay,required,nullable"` + EmployeeDeductions []PayStatementEmployeeDeduction `json:"employee_deductions" api:"required,nullable"` + EmployerContributions []PayStatementEmployerContribution `json:"employer_contributions" api:"required,nullable"` + GrossPay Money `json:"gross_pay" api:"required,nullable"` // A stable Finch `id` (UUID v4) for an individual in the company - IndividualID string `json:"individual_id,required"` - NetPay Money `json:"net_pay,required,nullable"` + IndividualID string `json:"individual_id" api:"required"` + NetPay Money `json:"net_pay" api:"required,nullable"` // The payment method. - PaymentMethod PayStatementPaymentMethod `json:"payment_method,required,nullable"` + PaymentMethod PayStatementPaymentMethod `json:"payment_method" api:"required,nullable"` // The array of taxes objects associated with this pay statement. - Taxes []PayStatementTax `json:"taxes,required,nullable"` + Taxes []PayStatementTax `json:"taxes" api:"required,nullable"` // The number of hours worked for this pay period - TotalHours float64 `json:"total_hours,required,nullable"` + TotalHours float64 `json:"total_hours" api:"required,nullable"` // The type of the payment associated with the pay statement. - Type PayStatementType `json:"type,required,nullable"` + Type PayStatementType `json:"type" api:"required,nullable"` JSON payStatementJSON `json:"-"` } @@ -111,17 +114,17 @@ func (r payStatementJSON) RawJSON() string { type PayStatementEarning struct { // The earnings amount in cents. - Amount int64 `json:"amount,required,nullable"` + Amount int64 `json:"amount" api:"required,nullable"` // The earnings currency code. - Currency string `json:"currency,required,nullable"` + Currency string `json:"currency" api:"required,nullable"` // The number of hours associated with this earning. (For salaried employees, this // could be hours per pay period, `0` or `null`, depending on the provider). - Hours float64 `json:"hours,required,nullable"` + Hours float64 `json:"hours" api:"required,nullable"` // The exact name of the deduction from the pay statement. - Name string `json:"name,required,nullable"` + Name string `json:"name" api:"required,nullable"` // The type of earning. - Type PayStatementEarningsType `json:"type,required,nullable"` - Attributes PayStatementEarningsAttributes `json:"attributes,nullable"` + Type PayStatementEarningsType `json:"type" api:"required,nullable"` + Attributes PayStatementEarningsAttributes `json:"attributes" api:"nullable"` JSON payStatementEarningJSON `json:"-"` } @@ -177,7 +180,7 @@ type PayStatementEarningsAttributes struct { // The metadata to be attached to the entity by existing rules. It is a key-value // pairs where the values can be of any type (string, number, boolean, object, // array, etc.). - Metadata map[string]interface{} `json:"metadata,required"` + Metadata map[string]interface{} `json:"metadata" api:"required"` JSON payStatementEarningsAttributesJSON `json:"-"` } @@ -199,16 +202,16 @@ func (r payStatementEarningsAttributesJSON) RawJSON() string { type PayStatementEmployeeDeduction struct { // The deduction amount in cents. - Amount int64 `json:"amount,required,nullable"` + Amount int64 `json:"amount" api:"required,nullable"` // The deduction currency. - Currency string `json:"currency,required,nullable"` + Currency string `json:"currency" api:"required,nullable"` // The deduction name from the pay statement. - Name string `json:"name,required,nullable"` + Name string `json:"name" api:"required,nullable"` // Boolean indicating if the deduction is pre-tax. - PreTax bool `json:"pre_tax,required,nullable"` + PreTax bool `json:"pre_tax" api:"required,nullable"` // Type of benefit. - Type BenefitType `json:"type,required,nullable"` - Attributes PayStatementEmployeeDeductionsAttributes `json:"attributes,nullable"` + Type BenefitType `json:"type" api:"required,nullable"` + Attributes PayStatementEmployeeDeductionsAttributes `json:"attributes" api:"nullable"` JSON payStatementEmployeeDeductionJSON `json:"-"` } @@ -237,7 +240,7 @@ type PayStatementEmployeeDeductionsAttributes struct { // The metadata to be attached to the entity by existing rules. It is a key-value // pairs where the values can be of any type (string, number, boolean, object, // array, etc.). - Metadata map[string]interface{} `json:"metadata,required"` + Metadata map[string]interface{} `json:"metadata" api:"required"` JSON payStatementEmployeeDeductionsAttributesJSON `json:"-"` } @@ -259,14 +262,14 @@ func (r payStatementEmployeeDeductionsAttributesJSON) RawJSON() string { type PayStatementEmployerContribution struct { // The contribution currency. - Currency string `json:"currency,required,nullable"` + Currency string `json:"currency" api:"required,nullable"` // The contribution name from the pay statement. - Name string `json:"name,required,nullable"` + Name string `json:"name" api:"required,nullable"` // Type of benefit. - Type BenefitType `json:"type,required,nullable"` + Type BenefitType `json:"type" api:"required,nullable"` // The contribution amount in cents. - Amount int64 `json:"amount,nullable"` - Attributes PayStatementEmployerContributionsAttributes `json:"attributes,nullable"` + Amount int64 `json:"amount" api:"nullable"` + Attributes PayStatementEmployerContributionsAttributes `json:"attributes" api:"nullable"` JSON payStatementEmployerContributionJSON `json:"-"` } @@ -294,7 +297,7 @@ type PayStatementEmployerContributionsAttributes struct { // The metadata to be attached to the entity by existing rules. It is a key-value // pairs where the values can be of any type (string, number, boolean, object, // array, etc.). - Metadata map[string]interface{} `json:"metadata,required"` + Metadata map[string]interface{} `json:"metadata" api:"required"` JSON payStatementEmployerContributionsAttributesJSON `json:"-"` } @@ -333,16 +336,16 @@ func (r PayStatementPaymentMethod) IsKnown() bool { type PayStatementTax struct { // The currency code. - Currency string `json:"currency,required,nullable"` + Currency string `json:"currency" api:"required,nullable"` // `true` if the amount is paid by the employers. - Employer bool `json:"employer,required,nullable"` + Employer bool `json:"employer" api:"required,nullable"` // The exact name of tax from the pay statement. - Name string `json:"name,required,nullable"` + Name string `json:"name" api:"required,nullable"` // The type of taxes. - Type PayStatementTaxesType `json:"type,required,nullable"` + Type PayStatementTaxesType `json:"type" api:"required,nullable"` // The tax amount in cents. - Amount int64 `json:"amount,nullable"` - Attributes PayStatementTaxesAttributes `json:"attributes,nullable"` + Amount int64 `json:"amount" api:"nullable"` + Attributes PayStatementTaxesAttributes `json:"attributes" api:"nullable"` JSON payStatementTaxJSON `json:"-"` } @@ -388,7 +391,7 @@ type PayStatementTaxesAttributes struct { // The metadata to be attached to the entity by existing rules. It is a key-value // pairs where the values can be of any type (string, number, boolean, object, // array, etc.). - Metadata map[string]interface{} `json:"metadata,required"` + Metadata map[string]interface{} `json:"metadata" api:"required"` JSON payStatementTaxesAttributesJSON `json:"-"` } @@ -426,10 +429,10 @@ func (r PayStatementType) IsKnown() bool { } type PayStatementDataSyncInProgress struct { - Code PayStatementDataSyncInProgressCode `json:"code,required"` - FinchCode PayStatementDataSyncInProgressFinchCode `json:"finch_code,required"` - Message PayStatementDataSyncInProgressMessage `json:"message,required"` - Name PayStatementDataSyncInProgressName `json:"name,required"` + Code PayStatementDataSyncInProgressCode `json:"code" api:"required"` + FinchCode PayStatementDataSyncInProgressFinchCode `json:"finch_code" api:"required"` + Message PayStatementDataSyncInProgressMessage `json:"message" api:"required"` + Name PayStatementDataSyncInProgressName `json:"name" api:"required"` JSON payStatementDataSyncInProgressJSON `json:"-"` } @@ -511,9 +514,9 @@ func (r PayStatementDataSyncInProgressName) IsKnown() bool { } type PayStatementResponse struct { - Body PayStatementResponseBody `json:"body,required"` - Code int64 `json:"code,required"` - PaymentID string `json:"payment_id,required"` + Body PayStatementResponseBody `json:"body" api:"required"` + Code int64 `json:"code" api:"required"` + PaymentID string `json:"payment_id" api:"required"` JSON payStatementResponseJSON `json:"-"` } @@ -609,9 +612,9 @@ func init() { } type PayStatementResponseBodyBatchError struct { - Code float64 `json:"code,required"` - Message string `json:"message,required"` - Name string `json:"name,required"` + Code float64 `json:"code" api:"required"` + Message string `json:"message" api:"required"` + Name string `json:"name" api:"required"` FinchCode string `json:"finch_code"` JSON payStatementResponseBodyBatchErrorJSON `json:"-"` } @@ -639,16 +642,27 @@ func (r PayStatementResponseBodyBatchError) implementsPayStatementResponseBody() type HRISPayStatementGetManyParams struct { // The array of batch requests. - Requests param.Field[[]HRISPayStatementGetManyParamsRequest] `json:"requests,required"` + Requests param.Field[[]HRISPayStatementGetManyParamsRequest] `json:"requests" api:"required"` + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` } func (r HRISPayStatementGetManyParams) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } +// URLQuery serializes [HRISPayStatementGetManyParams]'s query parameters as +// `url.Values`. +func (r HRISPayStatementGetManyParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + type HRISPayStatementGetManyParamsRequest struct { // A stable Finch `id` (UUID v4) for a payment. - PaymentID param.Field[string] `json:"payment_id,required" format:"uuid"` + PaymentID param.Field[string] `json:"payment_id" api:"required" format:"uuid"` // Number of pay statements to return (defaults to all). Limit param.Field[int64] `json:"limit"` // Index to start from. diff --git a/hrispaystatement_test.go b/hrispaystatement_test.go index 6408a786..455ffa4f 100644 --- a/hrispaystatement_test.go +++ b/hrispaystatement_test.go @@ -13,7 +13,7 @@ import ( "github.com/Finch-API/finch-api-go/option" ) -func TestHRISPayStatementGetMany(t *testing.T) { +func TestHRISPayStatementGetManyWithOptionalParams(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -24,6 +24,8 @@ func TestHRISPayStatementGetMany(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.HRIS.PayStatements.GetMany(context.TODO(), finchgo.HRISPayStatementGetManyParams{ Requests: finchgo.F([]finchgo.HRISPayStatementGetManyParamsRequest{{ @@ -31,6 +33,7 @@ func TestHRISPayStatementGetMany(t *testing.T) { Limit: finchgo.F(int64(50)), Offset: finchgo.F(int64(0)), }}), + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), }) if err != nil { var apierr *finchgo.Error diff --git a/internal/apiform/encoder.go b/internal/apiform/encoder.go index a856c79f..2a551205 100644 --- a/internal/apiform/encoder.go +++ b/internal/apiform/encoder.go @@ -42,7 +42,7 @@ type encoderField struct { } type encoderEntry struct { - reflect.Type + typ reflect.Type dateFormat string root bool } @@ -59,7 +59,7 @@ func (e *encoder) marshal(value interface{}, writer *multipart.Writer) error { func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { entry := encoderEntry{ - Type: t, + typ: t, dateFormat: e.dateFormat, root: e.root, } diff --git a/internal/apiform/form_test.go b/internal/apiform/form_test.go index 39d1460c..5ef44214 100644 --- a/internal/apiform/form_test.go +++ b/internal/apiform/form_test.go @@ -39,18 +39,18 @@ type DateTime struct { type AdditionalProperties struct { A bool `form:"a"` - Extras map[string]interface{} `form:"-,extras"` + Extras map[string]interface{} `form:"-" api:"extrafields"` } type TypedAdditionalProperties struct { A bool `form:"a"` - Extras map[string]int `form:"-,extras"` + Extras map[string]int `form:"-" api:"extrafields"` } type EmbeddedStructs struct { AdditionalProperties A *int `form:"number2"` - Extras map[string]interface{} `form:"-,extras"` + Extras map[string]interface{} `form:"-" api:"extrafields"` } type Recursive struct { diff --git a/internal/apiform/tag.go b/internal/apiform/tag.go index b22e054f..b79da178 100644 --- a/internal/apiform/tag.go +++ b/internal/apiform/tag.go @@ -5,6 +5,7 @@ import ( "strings" ) +const apiStructTag = "api" const jsonStructTag = "json" const formStructTag = "form" const formatStructTag = "format" @@ -22,7 +23,7 @@ func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool raw, ok = field.Tag.Lookup(jsonStructTag) } if !ok { - return + return tag, ok } parts := strings.Split(raw, ",") if len(parts) == 0 { @@ -39,10 +40,31 @@ func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool tag.metadata = true } } - return + + // the `api` struct tag is only used alongside `json` for custom behaviour + parseApiStructTag(field, &tag) + return tag, ok +} + +func parseApiStructTag(field reflect.StructField, tag *parsedStructTag) { + raw, ok := field.Tag.Lookup(apiStructTag) + if !ok { + return + } + parts := strings.Split(raw, ",") + for _, part := range parts { + switch part { + case "extrafields": + tag.extras = true + case "required": + tag.required = true + case "metadata": + tag.metadata = true + } + } } func parseFormatStructTag(field reflect.StructField) (format string, ok bool) { format, ok = field.Tag.Lookup(formatStructTag) - return + return format, ok } diff --git a/internal/apijson/decoder.go b/internal/apijson/decoder.go index bf01bf6b..f5fe7e3c 100644 --- a/internal/apijson/decoder.go +++ b/internal/apijson/decoder.go @@ -75,7 +75,7 @@ type decoderField struct { } type decoderEntry struct { - reflect.Type + typ reflect.Type dateFormat string root bool } @@ -91,7 +91,7 @@ func (d *decoderBuilder) unmarshal(raw []byte, to any) error { func (d *decoderBuilder) typeDecoder(t reflect.Type) decoderFunc { entry := decoderEntry{ - Type: t, + typ: t, dateFormat: d.dateFormat, root: d.root, } diff --git a/internal/apijson/encoder.go b/internal/apijson/encoder.go index 220a43d4..e7a4a558 100644 --- a/internal/apijson/encoder.go +++ b/internal/apijson/encoder.go @@ -46,7 +46,7 @@ type encoderField struct { } type encoderEntry struct { - reflect.Type + typ reflect.Type dateFormat string root bool } @@ -63,7 +63,7 @@ func (e *encoder) marshal(value interface{}) ([]byte, error) { func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { entry := encoderEntry{ - Type: t, + typ: t, dateFormat: e.dateFormat, root: e.root, } @@ -292,7 +292,7 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { return nil, err } } - return + return json, err } } diff --git a/internal/apijson/json_test.go b/internal/apijson/json_test.go index d4c62835..86dee4ee 100644 --- a/internal/apijson/json_test.go +++ b/internal/apijson/json_test.go @@ -40,12 +40,12 @@ type DateTime struct { type AdditionalProperties struct { A bool `json:"a"` - ExtraFields map[string]interface{} `json:"-,extras"` + ExtraFields map[string]interface{} `json:"-" api:"extrafields"` } type TypedAdditionalProperties struct { A bool `json:"a"` - ExtraFields map[string]int `json:"-,extras"` + ExtraFields map[string]int `json:"-" api:"extrafields"` } type EmbeddedStruct struct { @@ -65,7 +65,7 @@ type EmbeddedStructJSON struct { type EmbeddedStructs struct { EmbeddedStruct A *int `json:"a"` - ExtraFields map[string]interface{} `json:"-,extras"` + ExtraFields map[string]interface{} `json:"-" api:"extrafields"` JSON EmbeddedStructsJSON } @@ -86,7 +86,7 @@ type JSONFieldStruct struct { B int64 `json:"b"` C string `json:"c"` D string `json:"d"` - ExtraFields map[string]int64 `json:"-,extras"` + ExtraFields map[string]int64 `json:"-" api:"extrafields"` JSON JSONFieldStructJSON `json:"-,metadata"` } @@ -268,7 +268,7 @@ type MarshallingUnionStruct struct { func (r *MarshallingUnionStruct) UnmarshalJSON(data []byte) (err error) { *r = MarshallingUnionStruct{} err = UnmarshalRoot(data, &r.Union) - return + return err } func (r MarshallingUnionStruct) MarshalJSON() (data []byte, err error) { diff --git a/internal/apijson/tag.go b/internal/apijson/tag.go index 812fb3ca..17b21302 100644 --- a/internal/apijson/tag.go +++ b/internal/apijson/tag.go @@ -5,6 +5,7 @@ import ( "strings" ) +const apiStructTag = "api" const jsonStructTag = "json" const formatStructTag = "format" @@ -19,7 +20,7 @@ type parsedStructTag struct { func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { raw, ok := field.Tag.Lookup(jsonStructTag) if !ok { - return + return tag, ok } parts := strings.Split(raw, ",") if len(parts) == 0 { @@ -38,10 +39,31 @@ func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool tag.inline = true } } - return + + // the `api` struct tag is only used alongside `json` for custom behaviour + parseApiStructTag(field, &tag) + return tag, ok +} + +func parseApiStructTag(field reflect.StructField, tag *parsedStructTag) { + raw, ok := field.Tag.Lookup(apiStructTag) + if !ok { + return + } + parts := strings.Split(raw, ",") + for _, part := range parts { + switch part { + case "extrafields": + tag.extras = true + case "required": + tag.required = true + case "metadata": + tag.metadata = true + } + } } func parseFormatStructTag(field reflect.StructField) (format string, ok bool) { format, ok = field.Tag.Lookup(formatStructTag) - return + return format, ok } diff --git a/internal/apiquery/encoder.go b/internal/apiquery/encoder.go index 5032bc77..9f2a9285 100644 --- a/internal/apiquery/encoder.go +++ b/internal/apiquery/encoder.go @@ -29,7 +29,7 @@ type encoderField struct { } type encoderEntry struct { - reflect.Type + typ reflect.Type dateFormat string root bool settings QuerySettings @@ -42,7 +42,7 @@ type Pair struct { func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { entry := encoderEntry{ - Type: t, + typ: t, dateFormat: e.dateFormat, root: e.root, settings: e.settings, @@ -94,10 +94,10 @@ func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc { encoder := e.typeEncoder(t.Elem()) return func(key string, value reflect.Value) (pairs []Pair) { if !value.IsValid() || value.IsNil() { - return + return pairs } pairs = encoder(key, value.Elem()) - return + return pairs } case reflect.Struct: return e.newStructTypeEncoder(t) @@ -175,7 +175,7 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { field := value.FieldByIndex(ef.idx) pairs = append(pairs, ef.fn(subkey, field)...) } - return + return pairs } } @@ -193,7 +193,7 @@ func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc { keyPath := e.renderKeyPath(key, subkey) pairs = append(pairs, elementEncoder(keyPath, iter.Value())...) } - return + return pairs } } @@ -232,7 +232,14 @@ func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { return pairs } case ArrayQueryFormatIndices: - panic("The array indices format is not supported yet") + innerEncoder := e.typeEncoder(t.Elem()) + return func(key string, value reflect.Value) []Pair { + pairs := make([]Pair, 0, value.Len()) + for i := 0; i < value.Len(); i++ { + pairs = append(pairs, innerEncoder(fmt.Sprintf("%s[%d]", key, i), value.Index(i))...) + } + return pairs + } case ArrayQueryFormatBrackets: innerEncoder := e.typeEncoder(t.Elem()) return func(key string, value reflect.Value) []Pair { diff --git a/internal/apiquery/tag.go b/internal/apiquery/tag.go index 7ccd739c..c96c3735 100644 --- a/internal/apiquery/tag.go +++ b/internal/apiquery/tag.go @@ -17,7 +17,7 @@ type parsedStructTag struct { func parseQueryStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { raw, ok := field.Tag.Lookup(queryStructTag) if !ok { - return + return tag, ok } parts := strings.Split(raw, ",") if len(parts) == 0 { @@ -32,10 +32,10 @@ func parseQueryStructTag(field reflect.StructField) (tag parsedStructTag, ok boo tag.inline = true } } - return + return tag, ok } func parseFormatStructTag(field reflect.StructField) (format string, ok bool) { format, ok = field.Tag.Lookup(formatStructTag) - return + return format, ok } diff --git a/internal/requestconfig/requestconfig.go b/internal/requestconfig/requestconfig.go index f03d7f97..3ec2a788 100644 --- a/internal/requestconfig/requestconfig.go +++ b/internal/requestconfig/requestconfig.go @@ -5,6 +5,7 @@ package requestconfig import ( "bytes" "context" + "encoding/base64" "encoding/json" "fmt" "io" @@ -118,7 +119,13 @@ func NewRequestConfig(ctx context.Context, method string, u string, body interfa hasSerializationFunc = true params := body.URLQuery().Encode() if params != "" { - u = u + "?" + params + parsed, _ := url.Parse(u) + if parsed.RawQuery != "" { + parsed.RawQuery = parsed.RawQuery + "&" + params + u = parsed.String() + } else { + u = u + "?" + params + } } } if body, ok := body.([]byte); ok { @@ -168,11 +175,18 @@ func NewRequestConfig(ctx context.Context, method string, u string, body interfa Body: reader, } cfg.ResponseBodyInto = dst + cfg.Security = Security{ + BearerAuth: true, + BasicAuth: true, + } err = cfg.Apply(opts...) if err != nil { return nil, err } + // This must run after `cfg.Apply(...)` above so we know which specific security scheme to add + ApplySecurity(cfg) + // This must run after `cfg.Apply(...)` above in case the request timeout gets modified. We also only // apply our own logic for it if it's still "0" from above. If it's not, then it was deleted or modified // by the user and we should respect that. @@ -220,6 +234,8 @@ type RequestConfig struct { ClientID string ClientSecret string WebhookSecret string + // Configure which security scheme(s) should be enabled for this request + Security Security // If ResponseBodyInto not nil, then we will attempt to deserialize into // ResponseBodyInto. If Destination is a []byte, then it will return the body as // is. @@ -362,11 +378,9 @@ func (b *bodyWithTimeout) Close() error { } func retryDelay(res *http.Response, retryCount int) time.Duration { - // If the API asks us to wait a certain amount of time (and it's a reasonable amount), - // just do what it says. - - if retryAfterDelay, ok := parseRetryAfterHeader(res); ok && 0 <= retryAfterDelay && retryAfterDelay < time.Minute { - return retryAfterDelay + // If the backend tells us to wait a certain amount of time, use that value + if retryAfterDelay, ok := parseRetryAfterHeader(res); ok { + return max(0, retryAfterDelay) } maxDelay := 8 * time.Second @@ -470,10 +484,14 @@ func (cfg *RequestConfig) Execute() (err error) { // Close the response body before retrying to prevent connection leaks if res != nil && res.Body != nil { - res.Body.Close() + _ = res.Body.Close() } - time.Sleep(retryDelay(res, retryCount)) + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(retryDelay(res, retryCount)): + } } // Save *http.Response if it is requested to, even if there was an error making the request. This is @@ -494,7 +512,7 @@ func (cfg *RequestConfig) Execute() (err error) { if res.StatusCode >= 400 { contents, err := io.ReadAll(res.Body) - res.Body.Close() + _ = res.Body.Close() if err != nil { return err } @@ -525,7 +543,7 @@ func (cfg *RequestConfig) Execute() (err error) { } contents, err := io.ReadAll(res.Body) - res.Body.Close() + _ = res.Body.Close() if err != nil { return fmt.Errorf("error reading response body: %w", err) } @@ -642,3 +660,49 @@ func WithDefaultBaseURL(baseURL string) RequestOption { return nil }) } + +type Security struct { + BearerAuth bool + BasicAuth bool +} + +func WithSecurity(security Security) RequestOption { + return RequestOptionFunc(func(r *RequestConfig) error { + r.Security = security + return nil + }) +} + +// WithBearerAuthSecurity() should only be used within a method, not provided to at +// the client-level. +func WithBearerAuthSecurity() RequestOption { + return RequestOptionFunc(func(r *RequestConfig) error { + r.Security = Security{ + BearerAuth: true, + BasicAuth: false, + } + return nil + }) +} + +// WithBasicAuthSecurity() should only be used within a method, not provided to at +// the client-level. +func WithBasicAuthSecurity() RequestOption { + return RequestOptionFunc(func(r *RequestConfig) error { + r.Security = Security{ + BearerAuth: false, + BasicAuth: true, + } + return nil + }) +} + +func ApplySecurity(r RequestConfig) { + if r.Security.BearerAuth && r.AccessToken != "" && r.Request.Header.Get("Authorization") == "" { + r.Request.Header.Set("authorization", fmt.Sprintf("Bearer %s", r.AccessToken)) + } + + if r.Security.BasicAuth && r.ClientID != "" && r.ClientSecret != "" && r.Request.Header.Get("Authorization") == "" { + r.Request.Header.Set("authorization", fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(r.ClientID+":"+r.ClientSecret)))) + } +} diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go index 826d266f..31103e9e 100644 --- a/internal/testutil/testutil.go +++ b/internal/testutil/testutil.go @@ -16,10 +16,10 @@ func CheckTestServer(t *testing.T, url string) bool { t.Fatalf("strconv.ParseBool(os.LookupEnv(%s)) failed: %s", SKIP_MOCK_TESTS, err) } if skip { - t.Skip("The test will not run without a mock Prism server running against your OpenAPI spec") + t.Skip("The test will not run without a mock server running against your OpenAPI spec") return false } - t.Errorf("The test will not run without a mock Prism server running against your OpenAPI spec. You can set the environment variable %s to true to skip running any tests that require the mock server", SKIP_MOCK_TESTS) + t.Errorf("The test will not run without a mock server running against your OpenAPI spec. You can set the environment variable %s to true to skip running any tests that require the mock server", SKIP_MOCK_TESTS) return false } } diff --git a/internal/version.go b/internal/version.go index 6b316ac4..33b1bc6b 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "1.34.1" // x-release-please-version +const PackageVersion = "1.35.0" // x-release-please-version diff --git a/jobautomated.go b/jobautomated.go index 11439f0f..61165a83 100644 --- a/jobautomated.go +++ b/jobautomated.go @@ -52,56 +52,59 @@ func NewJobAutomatedService(opts ...option.RequestOption) (r *JobAutomatedServic // This endpoint is available for _Scale_ tier customers as an add-on. To request // access to this endpoint, please contact your Finch account manager. func (r *JobAutomatedService) New(ctx context.Context, body JobAutomatedNewParams, opts ...option.RequestOption) (res *JobAutomatedNewResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "jobs/automated" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } // Get an automated job by `job_id`. func (r *JobAutomatedService) Get(ctx context.Context, jobID string, opts ...option.RequestOption) (res *AutomatedAsyncJob, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) if jobID == "" { err = errors.New("missing required job_id parameter") - return + return nil, err } path := fmt.Sprintf("jobs/automated/%s", jobID) err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + return res, err } // Get all automated jobs. Automated jobs are completed by a machine. By default, // jobs are sorted in descending order by submission time. For scheduled jobs such // as data syncs, only the next scheduled job is shown. func (r *JobAutomatedService) List(ctx context.Context, query JobAutomatedListParams, opts ...option.RequestOption) (res *JobAutomatedListResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "jobs/automated" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) - return + return res, err } type AutomatedAsyncJob struct { // The datetime the job completed. - CompletedAt time.Time `json:"completed_at,required,nullable" format:"date-time"` + CompletedAt time.Time `json:"completed_at" api:"required,nullable" format:"date-time"` // The datetime when the job was created. for scheduled jobs, this will be the // initial connection time. For ad-hoc jobs, this will be the time the creation // request was received. - CreatedAt time.Time `json:"created_at,required" format:"date-time"` + CreatedAt time.Time `json:"created_at" api:"required" format:"date-time"` // The id of the job that has been created. - JobID string `json:"job_id,required" format:"uuid"` + JobID string `json:"job_id" api:"required" format:"uuid"` // The url that can be used to retrieve the job status - JobURL string `json:"job_url,required"` + JobURL string `json:"job_url" api:"required"` // The input parameters for the job. - Params AutomatedAsyncJobParams `json:"params,required,nullable"` + Params AutomatedAsyncJobParams `json:"params" api:"required,nullable"` // The datetime a job is scheduled to be run. For scheduled jobs, this datetime can // be in the future if the job has not yet been enqueued. For ad-hoc jobs, this // field will be null. - ScheduledAt time.Time `json:"scheduled_at,required,nullable" format:"date-time"` + ScheduledAt time.Time `json:"scheduled_at" api:"required,nullable" format:"date-time"` // The datetime a job entered into the job queue. - StartedAt time.Time `json:"started_at,required,nullable" format:"date-time"` - Status AutomatedAsyncJobStatus `json:"status,required"` + StartedAt time.Time `json:"started_at" api:"required,nullable" format:"date-time"` + Status AutomatedAsyncJobStatus `json:"status" api:"required"` // The type of automated job - Type AutomatedAsyncJobType `json:"type,required"` + Type AutomatedAsyncJobType `json:"type" api:"required"` JSON automatedAsyncJobJSON `json:"-"` } @@ -189,9 +192,9 @@ func (r AutomatedAsyncJobType) IsKnown() bool { type JobAutomatedNewResponse struct { // The number of allowed refreshes per hour (per hour, fixed window) - AllowedRefreshes int64 `json:"allowed_refreshes,required"` + AllowedRefreshes int64 `json:"allowed_refreshes" api:"required"` // The number of remaining refreshes available (per hour, fixed window) - RemainingRefreshes int64 `json:"remaining_refreshes,required"` + RemainingRefreshes int64 `json:"remaining_refreshes" api:"required"` // The id of the job that has been created. JobID string `json:"job_id" format:"uuid"` // The url that can be used to retrieve the job status @@ -222,8 +225,8 @@ func (r jobAutomatedNewResponseJSON) RawJSON() string { } type JobAutomatedListResponse struct { - Data []AutomatedAsyncJob `json:"data,required"` - Meta JobAutomatedListResponseMeta `json:"meta,required"` + Data []AutomatedAsyncJob `json:"data" api:"required"` + Meta JobAutomatedListResponseMeta `json:"meta" api:"required"` JSON jobAutomatedListResponseJSON `json:"-"` } @@ -325,7 +328,7 @@ type JobAutomatedNewParams interface { type JobAutomatedNewParamsDataSyncAll struct { // The type of job to start. - Type param.Field[JobAutomatedNewParamsDataSyncAllType] `json:"type,required"` + Type param.Field[JobAutomatedNewParamsDataSyncAllType] `json:"type" api:"required"` } func (r JobAutomatedNewParamsDataSyncAll) MarshalJSON() (data []byte, err error) { @@ -352,9 +355,9 @@ func (r JobAutomatedNewParamsDataSyncAllType) IsKnown() bool { } type JobAutomatedNewParamsW4FormEmployeeSync struct { - Params param.Field[JobAutomatedNewParamsW4FormEmployeeSyncParams] `json:"params,required"` + Params param.Field[JobAutomatedNewParamsW4FormEmployeeSyncParams] `json:"params" api:"required"` // The type of job to start. - Type param.Field[JobAutomatedNewParamsW4FormEmployeeSyncType] `json:"type,required"` + Type param.Field[JobAutomatedNewParamsW4FormEmployeeSyncType] `json:"type" api:"required"` } func (r JobAutomatedNewParamsW4FormEmployeeSync) MarshalJSON() (data []byte, err error) { @@ -367,7 +370,7 @@ func (JobAutomatedNewParamsW4FormEmployeeSync) ImplementsJobAutomatedNewParams() type JobAutomatedNewParamsW4FormEmployeeSyncParams struct { // The unique ID of the individual for W-4 data sync. - IndividualID param.Field[string] `json:"individual_id,required"` + IndividualID param.Field[string] `json:"individual_id" api:"required"` } func (r JobAutomatedNewParamsW4FormEmployeeSyncParams) MarshalJSON() (data []byte, err error) { diff --git a/jobautomated_test.go b/jobautomated_test.go index d7a8792f..a4363544 100644 --- a/jobautomated_test.go +++ b/jobautomated_test.go @@ -24,6 +24,8 @@ func TestJobAutomatedNew(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Jobs.Automated.New(context.TODO(), finchgo.JobAutomatedNewParamsDataSyncAll{ Type: finchgo.F(finchgo.JobAutomatedNewParamsDataSyncAllTypeDataSyncAll), @@ -48,6 +50,8 @@ func TestJobAutomatedGet(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Jobs.Automated.Get(context.TODO(), "job_id") if err != nil { @@ -70,6 +74,8 @@ func TestJobAutomatedListWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Jobs.Automated.List(context.TODO(), finchgo.JobAutomatedListParams{ Limit: finchgo.F(int64(0)), diff --git a/jobmanual.go b/jobmanual.go index 8a6d1038..254b365e 100644 --- a/jobmanual.go +++ b/jobmanual.go @@ -33,24 +33,25 @@ func NewJobManualService(opts ...option.RequestOption) (r *JobManualService) { return } -// Get a manual job by `job_id`. Manual jobs are completed by a human and include -// Assisted Benefits jobs. +// Check the status and outcome of a job by `job_id`. This includes all deductions +// jobs including those for both automated and assisted integrations. func (r *JobManualService) Get(ctx context.Context, jobID string, opts ...option.RequestOption) (res *ManualAsyncJob, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) if jobID == "" { err = errors.New("missing required job_id parameter") - return + return nil, err } path := fmt.Sprintf("jobs/manual/%s", jobID) err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + return res, err } type ManualAsyncJob struct { // Specific information about the job, such as individual statuses for batch jobs. - Body []interface{} `json:"body,required,nullable"` - JobID string `json:"job_id,required" format:"uuid"` - Status ManualAsyncJobStatus `json:"status,required"` + Body []interface{} `json:"body" api:"required,nullable"` + JobID string `json:"job_id" api:"required" format:"uuid"` + Status ManualAsyncJobStatus `json:"status" api:"required"` JSON manualAsyncJobJSON `json:"-"` } diff --git a/jobmanual_test.go b/jobmanual_test.go index 8fe52ffb..c707e08d 100644 --- a/jobmanual_test.go +++ b/jobmanual_test.go @@ -24,6 +24,8 @@ func TestJobManualGet(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Jobs.Manual.Get(context.TODO(), "job_id") if err != nil { diff --git a/option/middleware.go b/option/middleware.go index 8ec9dd60..4be09875 100644 --- a/option/middleware.go +++ b/option/middleware.go @@ -8,6 +8,10 @@ import ( "net/http/httputil" ) +// sensitiveLogHeaders are redacted before request and response content is +// written to the debug logger. +var sensitiveLogHeaders = []string{"authorization", "api-key", "x-api-key", "cookie", "set-cookie"} + // WithDebugLog logs the HTTP request and response content. // If the logger parameter is nil, it uses the default logger. // @@ -20,7 +24,7 @@ func WithDebugLog(logger *log.Logger) RequestOption { logger = log.Default() } - if reqBytes, err := httputil.DumpRequest(req, true); err == nil { + if reqBytes, err := dumpRedactedRequest(req); err == nil { logger.Printf("Request Content:\n%s\n", reqBytes) } @@ -29,10 +33,48 @@ func WithDebugLog(logger *log.Logger) RequestOption { return resp, err } - if respBytes, err := httputil.DumpResponse(resp, true); err == nil { + if respBytes, err := dumpRedactedResponse(resp); err == nil { logger.Printf("Response Content:\n%s\n", respBytes) } return resp, err }) } + +// dumpRedactedRequest dumps req with sensitive headers replaced. The +// original headers are restored via defer so a panic in DumpRequest cannot +// leak the placeholder map into the live request sent downstream. +func dumpRedactedRequest(req *http.Request) ([]byte, error) { + origHeaders := req.Header + req.Header = redactDebugHeaders(origHeaders) + defer func() { req.Header = origHeaders }() + return httputil.DumpRequest(req, true) +} + +func dumpRedactedResponse(resp *http.Response) ([]byte, error) { + origHeaders := resp.Header + resp.Header = redactDebugHeaders(origHeaders) + defer func() { resp.Header = origHeaders }() + return httputil.DumpResponse(resp, true) +} + +func redactDebugHeaders(headers http.Header) http.Header { + var redacted http.Header + for _, name := range sensitiveLogHeaders { + values := headers.Values(name) + if len(values) == 0 { + continue + } + if redacted == nil { + redacted = headers.Clone() + } + redacted.Del(name) + for range values { + redacted.Add(name, "***") + } + } + if redacted == nil { + return headers + } + return redacted +} diff --git a/option/requestoption.go b/option/requestoption.go index 4f29ccaf..94f59fac 100644 --- a/option/requestoption.go +++ b/option/requestoption.go @@ -4,7 +4,6 @@ package option import ( "bytes" - "encoding/base64" "fmt" "io" "net/http" @@ -271,7 +270,7 @@ func WithEnvironmentProduction() RequestOption { func WithAccessToken(value string) RequestOption { return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { r.AccessToken = value - return r.Apply(WithHeader("authorization", fmt.Sprintf("Bearer %s", r.AccessToken))) + return nil }) } @@ -279,7 +278,7 @@ func WithAccessToken(value string) RequestOption { func WithClientID(value string) RequestOption { return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { r.ClientID = value - return r.Apply(WithHeader("authorization", fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(r.ClientID+":"+r.ClientSecret))))) + return nil }) } @@ -287,7 +286,7 @@ func WithClientID(value string) RequestOption { func WithClientSecret(value string) RequestOption { return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { r.ClientSecret = value - return r.Apply(WithHeader("authorization", fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(r.ClientID+":"+r.ClientSecret))))) + return nil }) } diff --git a/packages/pagination/pagination.go b/packages/pagination/pagination.go index 40487f75..5c5ba04d 100644 --- a/packages/pagination/pagination.go +++ b/packages/pagination/pagination.go @@ -210,7 +210,7 @@ func (r *ResponsesPageAutoPager[T]) Index() int { type Page[T any] struct { Data []T `json:"data"` - Paging shared.Paging `json:"paging,required"` + Paging shared.Paging `json:"paging" api:"required"` JSON pageJSON `json:"-"` cfg *requestconfig.RequestConfig res *http.Response @@ -241,7 +241,9 @@ func (r *Page[T]) GetNextPage() (res *Page[T], err error) { } cfg := r.cfg.Clone(r.cfg.Context) - next := r.Paging.Offset + offset := r.Paging.Offset + length := int64(len(r.Data)) + next := offset + length if next < r.Paging.Count && next != 0 { err = cfg.Apply(option.WithQuery("offset", strconv.FormatInt(next, 10))) diff --git a/paginationauto_test.go b/paginationauto_test.go index 603f7ef6..6471d4ce 100644 --- a/paginationauto_test.go +++ b/paginationauto_test.go @@ -23,9 +23,11 @@ func TestAutoPagination(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) iter := client.HRIS.Directory.ListAutoPaging(context.TODO(), finchgo.HRISDirectoryListParams{}) - // Prism mock isn't going to give us real pagination + // The mock server isn't going to give us real pagination for i := 0; i < 3 && iter.Next(); i++ { directory := iter.Current() t.Logf("%+v\n", directory.ID) diff --git a/paginationmanual_test.go b/paginationmanual_test.go index fb4ca331..713a65c1 100644 --- a/paginationmanual_test.go +++ b/paginationmanual_test.go @@ -23,6 +23,8 @@ func TestManualPagination(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) page, err := client.HRIS.Directory.List(context.TODO(), finchgo.HRISDirectoryListParams{}) if err != nil { @@ -31,7 +33,7 @@ func TestManualPagination(t *testing.T) { for _, directory := range page.Individuals { t.Logf("%+v\n", directory.ID) } - // Prism mock isn't going to give us real pagination + // The mock server isn't going to give us real pagination page, err = page.GetNextPage() if err != nil { t.Fatalf("err should be nil: %s", err.Error()) diff --git a/payrollpaygroup.go b/payrollpaygroup.go index 3bd41963..bf22fdef 100644 --- a/payrollpaygroup.go +++ b/payrollpaygroup.go @@ -38,21 +38,23 @@ func NewPayrollPayGroupService(opts ...option.RequestOption) (r *PayrollPayGroup } // Read information from a single pay group -func (r *PayrollPayGroupService) Get(ctx context.Context, payGroupID string, opts ...option.RequestOption) (res *PayrollPayGroupGetResponse, err error) { - opts = slices.Concat(r.Options, opts) +func (r *PayrollPayGroupService) Get(ctx context.Context, payGroupID string, query PayrollPayGroupGetParams, opts ...option.RequestOption) (res *PayrollPayGroupGetResponse, err error) { + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) if payGroupID == "" { err = errors.New("missing required pay_group_id parameter") - return + return nil, err } path := fmt.Sprintf("employer/pay-groups/%s", payGroupID) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) + return res, err } // Read company pay groups and frequencies func (r *PayrollPayGroupService) List(ctx context.Context, query PayrollPayGroupListParams, opts ...option.RequestOption) (res *pagination.SinglePage[PayrollPayGroupListResponse], err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) path := "employer/pay-groups" cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, query, &res, opts...) @@ -74,12 +76,12 @@ func (r *PayrollPayGroupService) ListAutoPaging(ctx context.Context, query Payro type PayrollPayGroupGetResponse struct { // Finch id (uuidv4) for the pay group - ID string `json:"id,required" format:"uuid"` - IndividualIDs []string `json:"individual_ids,required" format:"uuid"` + ID string `json:"id" api:"required" format:"uuid"` + IndividualIDs []string `json:"individual_ids" api:"required" format:"uuid"` // Name of the pay group - Name string `json:"name,required"` + Name string `json:"name" api:"required"` // List of pay frequencies associated with this pay group - PayFrequencies []PayrollPayGroupGetResponsePayFrequency `json:"pay_frequencies,required"` + PayFrequencies []PayrollPayGroupGetResponsePayFrequency `json:"pay_frequencies" api:"required"` JSON payrollPayGroupGetResponseJSON `json:"-"` } @@ -126,11 +128,11 @@ func (r PayrollPayGroupGetResponsePayFrequency) IsKnown() bool { type PayrollPayGroupListResponse struct { // Finch id (uuidv4) for the pay group - ID string `json:"id,required" format:"uuid"` + ID string `json:"id" api:"required" format:"uuid"` // Name of the pay group - Name string `json:"name,required"` + Name string `json:"name" api:"required"` // List of pay frequencies associated with this pay group - PayFrequencies []PayrollPayGroupListResponsePayFrequency `json:"pay_frequencies,required"` + PayFrequencies []PayrollPayGroupListResponsePayFrequency `json:"pay_frequencies" api:"required"` JSON payrollPayGroupListResponseJSON `json:"-"` } @@ -174,7 +176,23 @@ func (r PayrollPayGroupListResponsePayFrequency) IsKnown() bool { return false } +type PayrollPayGroupGetParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` +} + +// URLQuery serializes [PayrollPayGroupGetParams]'s query parameters as +// `url.Values`. +func (r PayrollPayGroupGetParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatBrackets, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + type PayrollPayGroupListParams struct { + // The entity IDs to specify which entities' data to access. + EntityIDs param.Field[[]string] `query:"entity_ids" format:"uuid"` IndividualID param.Field[string] `query:"individual_id" format:"uuid"` PayFrequencies param.Field[[]string] `query:"pay_frequencies"` } diff --git a/payrollpaygroup_test.go b/payrollpaygroup_test.go index c1983a31..306c2324 100644 --- a/payrollpaygroup_test.go +++ b/payrollpaygroup_test.go @@ -13,7 +13,7 @@ import ( "github.com/Finch-API/finch-api-go/option" ) -func TestPayrollPayGroupGet(t *testing.T) { +func TestPayrollPayGroupGetWithOptionalParams(t *testing.T) { baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { baseURL = envURL @@ -24,8 +24,16 @@ func TestPayrollPayGroupGet(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), + ) + _, err := client.Payroll.PayGroups.Get( + context.TODO(), + "pay_group_id", + finchgo.PayrollPayGroupGetParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), + }, ) - _, err := client.Payroll.PayGroups.Get(context.TODO(), "pay_group_id") if err != nil { var apierr *finchgo.Error if errors.As(err, &apierr) { @@ -46,8 +54,11 @@ func TestPayrollPayGroupListWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Payroll.PayGroups.List(context.TODO(), finchgo.PayrollPayGroupListParams{ + EntityIDs: finchgo.F([]string{"550e8400-e29b-41d4-a716-446655440000"}), IndividualID: finchgo.F("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"), PayFrequencies: finchgo.F([]string{"string"}), }) diff --git a/provider.go b/provider.go index e3bdf4f6..54119f1f 100644 --- a/provider.go +++ b/provider.go @@ -35,7 +35,8 @@ func NewProviderService(opts ...option.RequestOption) (r *ProviderService) { // Return details on all available payroll and HR systems. func (r *ProviderService) List(ctx context.Context, opts ...option.RequestOption) (res *pagination.SinglePage[ProviderListResponse], err error) { var raw *http.Response - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) opts = append([]option.RequestOption{option.WithResponseInto(&raw)}, opts...) path := "providers" cfg, err := requestconfig.NewRequestConfig(ctx, http.MethodGet, path, nil, &res, opts...) @@ -57,11 +58,11 @@ func (r *ProviderService) ListAutoPaging(ctx context.Context, opts ...option.Req type ProviderListResponse struct { // The id of the payroll provider used in Connect. - ID string `json:"id,required"` + ID string `json:"id" api:"required"` // The display name of the payroll provider. - DisplayName string `json:"display_name,required"` + DisplayName string `json:"display_name" api:"required"` // The list of Finch products supported on this payroll provider. - Products []string `json:"products,required"` + Products []string `json:"products" api:"required"` // The authentication methods supported by the provider. AuthenticationMethods []ProviderListResponseAuthenticationMethod `json:"authentication_methods"` // `true` if the integration is in a beta state, `false` otherwise @@ -110,7 +111,7 @@ func (r providerListResponseJSON) RawJSON() string { type ProviderListResponseAuthenticationMethod struct { // The type of authentication method - Type ProviderListResponseAuthenticationMethodsType `json:"type,required"` + Type ProviderListResponseAuthenticationMethodsType `json:"type" api:"required"` // The supported benefit types and their configurations BenefitsSupport map[string]interface{} `json:"benefits_support"` // The supported fields for each Finch product diff --git a/provider_test.go b/provider_test.go index 708dfc6c..cd7374e7 100644 --- a/provider_test.go +++ b/provider_test.go @@ -24,6 +24,8 @@ func TestProviderList(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Providers.List(context.TODO()) if err != nil { diff --git a/requestforwarding.go b/requestforwarding.go index cecc8438..493be20a 100644 --- a/requestforwarding.go +++ b/requestforwarding.go @@ -40,26 +40,27 @@ func NewRequestForwardingService(opts ...option.RequestOption) (r *RequestForwar // Forward allows you to push or pull data models directly against an integration's // API. func (r *RequestForwardingService) Forward(ctx context.Context, body RequestForwardingForwardParams, opts ...option.RequestOption) (res *RequestForwardingForwardResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "forward" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } type RequestForwardingForwardResponse struct { // An object containing details of your original forwarded request, for your ease // of reference. - Request RequestForwardingForwardResponseRequest `json:"request,required"` + Request RequestForwardingForwardResponseRequest `json:"request" api:"required"` // The HTTP status code of the forwarded request's response, exactly received from // the underlying integration's API. This value will be returned as an integer. - StatusCode int64 `json:"statusCode,required"` + StatusCode int64 `json:"statusCode" api:"required"` // A string representation of the HTTP response body of the forwarded request's // response received from the underlying integration's API. This field may be null // in the case where the upstream system's response is empty. - Data string `json:"data,nullable"` + Data string `json:"data" api:"nullable"` // The HTTP headers of the forwarded request's response, exactly as received from // the underlying integration's API. - Headers map[string]interface{} `json:"headers,nullable"` + Headers map[string]interface{} `json:"headers" api:"nullable"` JSON requestForwardingForwardResponseJSON `json:"-"` } @@ -87,15 +88,15 @@ func (r requestForwardingForwardResponseJSON) RawJSON() string { type RequestForwardingForwardResponseRequest struct { // The HTTP method that was specified for the forwarded request. Valid values // include: `GET` , `POST` , `PUT` , `DELETE` , and `PATCH`. - Method string `json:"method,required"` + Method string `json:"method" api:"required"` // The URL route path that was specified for the forwarded request. - Route string `json:"route,required"` + Route string `json:"route" api:"required"` // The body that was specified for the forwarded request. - Data RequestForwardingForwardResponseRequestDataUnion `json:"data,nullable"` + Data RequestForwardingForwardResponseRequestDataUnion `json:"data" api:"nullable"` // The HTTP headers that were specified for the forwarded request. - Headers map[string]interface{} `json:"headers,nullable"` + Headers map[string]string `json:"headers" api:"nullable"` // The query parameters that were specified for the forwarded request. - Params map[string]interface{} `json:"params,nullable"` + Params map[string]interface{} `json:"params" api:"nullable"` JSON requestForwardingForwardResponseRequestJSON `json:"-"` } @@ -150,21 +151,21 @@ func (r RequestForwardingForwardResponseRequestDataMap) ImplementsRequestForward type RequestForwardingForwardParams struct { // The HTTP method for the forwarded request. Valid values include: `GET` , `POST` // , `PUT` , `DELETE` , and `PATCH`. - Method param.Field[string] `json:"method,required"` + Method param.Field[string] `json:"method" api:"required"` // The URL route path for the forwarded request. This value must begin with a // forward-slash ( / ) and may only contain alphanumeric characters, hyphens, and // underscores. - Route param.Field[string] `json:"route,required"` + Route param.Field[string] `json:"route" api:"required"` // The body for the forwarded request. This value must be specified as either a // string or a valid JSON object. Data param.Field[string] `json:"data"` - // The HTTP headers to include on the forwarded request. This value must be - // specified as an object of key-value pairs. Example: - // `{"Content-Type": "application/xml", "X-API-Version": "v1" }` - Headers param.Field[map[string]interface{}] `json:"headers"` // The query parameters for the forwarded request. This value must be specified as // a valid JSON object rather than a query string. Params param.Field[map[string]interface{}] `json:"params"` + // The HTTP headers to include on the forwarded request. This value must be + // specified as an object of key-value pairs. Example: + // `{"Content-Type": "application/xml", "X-API-Version": "v1" }` + RequestHeaders param.Field[map[string]interface{}] `json:"request_headers"` } func (r RequestForwardingForwardParams) MarshalJSON() (data []byte, err error) { diff --git a/requestforwarding_test.go b/requestforwarding_test.go index ec9aacf8..6af3ffcb 100644 --- a/requestforwarding_test.go +++ b/requestforwarding_test.go @@ -24,15 +24,17 @@ func TestRequestForwardingForwardWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.RequestForwarding.Forward(context.TODO(), finchgo.RequestForwardingForwardParams{ Method: finchgo.F("method"), Route: finchgo.F("route"), Data: finchgo.F("data"), - Headers: finchgo.F(map[string]interface{}{ + Params: finchgo.F(map[string]interface{}{ "foo": "bar", }), - Params: finchgo.F(map[string]interface{}{ + RequestHeaders: finchgo.F(map[string]interface{}{ "foo": "bar", }), }) diff --git a/sandboxcompany.go b/sandboxcompany.go index 6026cbe7..798e6b46 100644 --- a/sandboxcompany.go +++ b/sandboxcompany.go @@ -34,29 +34,30 @@ func NewSandboxCompanyService(opts ...option.RequestOption) (r *SandboxCompanySe // Update a sandbox company's data func (r *SandboxCompanyService) Update(ctx context.Context, body SandboxCompanyUpdateParams, opts ...option.RequestOption) (res *SandboxCompanyUpdateResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "sandbox/company" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, &res, opts...) - return + return res, err } type SandboxCompanyUpdateResponse struct { // An array of bank account objects associated with the payroll/HRIS system. - Accounts []SandboxCompanyUpdateResponseAccount `json:"accounts,required,nullable"` + Accounts []SandboxCompanyUpdateResponseAccount `json:"accounts" api:"required,nullable"` // The array of company departments. - Departments []SandboxCompanyUpdateResponseDepartment `json:"departments,required,nullable"` + Departments []SandboxCompanyUpdateResponseDepartment `json:"departments" api:"required,nullable"` // The employer identification number. - Ein string `json:"ein,required,nullable"` + Ein string `json:"ein" api:"required,nullable"` // The entity type object. - Entity SandboxCompanyUpdateResponseEntity `json:"entity,required,nullable"` + Entity SandboxCompanyUpdateResponseEntity `json:"entity" api:"required,nullable"` // The legal name of the company. - LegalName string `json:"legal_name,required,nullable"` - Locations []Location `json:"locations,required,nullable"` + LegalName string `json:"legal_name" api:"required,nullable"` + Locations []Location `json:"locations" api:"required,nullable"` // The email of the main administrator on the account. - PrimaryEmail string `json:"primary_email,required,nullable" format:"email"` + PrimaryEmail string `json:"primary_email" api:"required,nullable" format:"email"` // The phone number of the main administrator on the account. Format: E.164, with // extension where applicable, e.g. `+NNNNNNNNNNN xExtension` - PrimaryPhoneNumber string `json:"primary_phone_number,required,nullable"` + PrimaryPhoneNumber string `json:"primary_phone_number" api:"required,nullable"` JSON sandboxCompanyUpdateResponseJSON `json:"-"` } @@ -85,16 +86,16 @@ func (r sandboxCompanyUpdateResponseJSON) RawJSON() string { type SandboxCompanyUpdateResponseAccount struct { // The name of the bank associated in the payroll/HRIS system. - AccountName string `json:"account_name,nullable"` + AccountName string `json:"account_name" api:"nullable"` // 10-12 digit number to specify the bank account - AccountNumber string `json:"account_number,nullable"` + AccountNumber string `json:"account_number" api:"nullable"` // The type of bank account. - AccountType SandboxCompanyUpdateResponseAccountsAccountType `json:"account_type,nullable"` + AccountType SandboxCompanyUpdateResponseAccountsAccountType `json:"account_type" api:"nullable"` // Name of the banking institution. - InstitutionName string `json:"institution_name,nullable"` + InstitutionName string `json:"institution_name" api:"nullable"` // A nine-digit code that's based on the U.S. Bank location where your account was // opened. - RoutingNumber string `json:"routing_number,nullable"` + RoutingNumber string `json:"routing_number" api:"nullable"` JSON sandboxCompanyUpdateResponseAccountJSON `json:"-"` } @@ -136,9 +137,9 @@ func (r SandboxCompanyUpdateResponseAccountsAccountType) IsKnown() bool { type SandboxCompanyUpdateResponseDepartment struct { // The department name. - Name string `json:"name,nullable"` + Name string `json:"name" api:"nullable"` // The parent department, if present. - Parent SandboxCompanyUpdateResponseDepartmentsParent `json:"parent,nullable"` + Parent SandboxCompanyUpdateResponseDepartmentsParent `json:"parent" api:"nullable"` JSON sandboxCompanyUpdateResponseDepartmentJSON `json:"-"` } @@ -162,7 +163,7 @@ func (r sandboxCompanyUpdateResponseDepartmentJSON) RawJSON() string { // The parent department, if present. type SandboxCompanyUpdateResponseDepartmentsParent struct { // The parent department's name. - Name string `json:"name,nullable"` + Name string `json:"name" api:"nullable"` JSON sandboxCompanyUpdateResponseDepartmentsParentJSON `json:"-"` } @@ -185,9 +186,9 @@ func (r sandboxCompanyUpdateResponseDepartmentsParentJSON) RawJSON() string { // The entity type object. type SandboxCompanyUpdateResponseEntity struct { // The tax payer subtype of the company. - Subtype SandboxCompanyUpdateResponseEntitySubtype `json:"subtype,nullable"` + Subtype SandboxCompanyUpdateResponseEntitySubtype `json:"subtype" api:"nullable"` // The tax payer type of the company. - Type SandboxCompanyUpdateResponseEntityType `json:"type,nullable"` + Type SandboxCompanyUpdateResponseEntityType `json:"type" api:"nullable"` JSON sandboxCompanyUpdateResponseEntityJSON `json:"-"` } @@ -248,21 +249,21 @@ func (r SandboxCompanyUpdateResponseEntityType) IsKnown() bool { type SandboxCompanyUpdateParams struct { // An array of bank account objects associated with the payroll/HRIS system. - Accounts param.Field[[]SandboxCompanyUpdateParamsAccount] `json:"accounts,required"` + Accounts param.Field[[]SandboxCompanyUpdateParamsAccount] `json:"accounts" api:"required"` // The array of company departments. - Departments param.Field[[]SandboxCompanyUpdateParamsDepartment] `json:"departments,required"` + Departments param.Field[[]SandboxCompanyUpdateParamsDepartment] `json:"departments" api:"required"` // The employer identification number. - Ein param.Field[string] `json:"ein,required"` + Ein param.Field[string] `json:"ein" api:"required"` // The entity type object. - Entity param.Field[SandboxCompanyUpdateParamsEntity] `json:"entity,required"` + Entity param.Field[SandboxCompanyUpdateParamsEntity] `json:"entity" api:"required"` // The legal name of the company. - LegalName param.Field[string] `json:"legal_name,required"` - Locations param.Field[[]LocationParam] `json:"locations,required"` + LegalName param.Field[string] `json:"legal_name" api:"required"` + Locations param.Field[[]LocationParam] `json:"locations" api:"required"` // The email of the main administrator on the account. - PrimaryEmail param.Field[string] `json:"primary_email,required" format:"email"` + PrimaryEmail param.Field[string] `json:"primary_email" api:"required" format:"email"` // The phone number of the main administrator on the account. Format: E.164, with // extension where applicable, e.g. `+NNNNNNNNNNN xExtension` - PrimaryPhoneNumber param.Field[string] `json:"primary_phone_number,required"` + PrimaryPhoneNumber param.Field[string] `json:"primary_phone_number" api:"required"` } func (r SandboxCompanyUpdateParams) MarshalJSON() (data []byte, err error) { diff --git a/sandboxcompany_test.go b/sandboxcompany_test.go index 1a4e45f3..f3516f58 100644 --- a/sandboxcompany_test.go +++ b/sandboxcompany_test.go @@ -24,6 +24,8 @@ func TestSandboxCompanyUpdateWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Sandbox.Company.Update(context.TODO(), finchgo.SandboxCompanyUpdateParams{ Accounts: finchgo.F([]finchgo.SandboxCompanyUpdateParamsAccount{{ diff --git a/sandboxconnection.go b/sandboxconnection.go index 9bcfda9b..e70c20ca 100644 --- a/sandboxconnection.go +++ b/sandboxconnection.go @@ -36,28 +36,31 @@ func NewSandboxConnectionService(opts ...option.RequestOption) (r *SandboxConnec // Create a new connection (new company/provider pair) with a new account func (r *SandboxConnectionService) New(ctx context.Context, body SandboxConnectionNewParams, opts ...option.RequestOption) (res *SandboxConnectionNewResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBasicAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "sandbox/connections" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } type SandboxConnectionNewResponse struct { - AccessToken string `json:"access_token,required" format:"uuid"` + AccessToken string `json:"access_token" api:"required" format:"uuid"` // [DEPRECATED] Use `connection_id` to associate a connection with an access token // // Deprecated: deprecated - AccountID string `json:"account_id,required" format:"uuid"` - AuthenticationType SandboxConnectionNewResponseAuthenticationType `json:"authentication_type,required"` + AccountID string `json:"account_id" api:"required" format:"uuid"` + AuthenticationType SandboxConnectionNewResponseAuthenticationType `json:"authentication_type" api:"required"` // [DEPRECATED] Use `connection_id` to associate a connection with an access token // // Deprecated: deprecated - CompanyID string `json:"company_id,required" format:"uuid"` + CompanyID string `json:"company_id" api:"required" format:"uuid"` // The ID of the new connection - ConnectionID string `json:"connection_id,required" format:"uuid"` - Products []string `json:"products,required"` + ConnectionID string `json:"connection_id" api:"required" format:"uuid"` + // The ID of the entity for this connection + EntityID string `json:"entity_id" api:"required" format:"uuid"` + Products []string `json:"products" api:"required"` // The ID of the provider associated with the `access_token`. - ProviderID string `json:"provider_id,required" format:"uuid"` + ProviderID string `json:"provider_id" api:"required" format:"uuid"` TokenType string `json:"token_type"` JSON sandboxConnectionNewResponseJSON `json:"-"` } @@ -70,6 +73,7 @@ type sandboxConnectionNewResponseJSON struct { AuthenticationType apijson.Field CompanyID apijson.Field ConnectionID apijson.Field + EntityID apijson.Field Products apijson.Field ProviderID apijson.Field TokenType apijson.Field @@ -104,7 +108,7 @@ func (r SandboxConnectionNewResponseAuthenticationType) IsKnown() bool { type SandboxConnectionNewParams struct { // The provider associated with the connection - ProviderID param.Field[string] `json:"provider_id,required"` + ProviderID param.Field[string] `json:"provider_id" api:"required"` AuthenticationType param.Field[SandboxConnectionNewParamsAuthenticationType] `json:"authentication_type"` // Optional: the size of the employer to be created with this connection. Defaults // to 20. Note that if this is higher than 100, historical payroll data will not be diff --git a/sandboxconnection_test.go b/sandboxconnection_test.go index 0f572061..f262883d 100644 --- a/sandboxconnection_test.go +++ b/sandboxconnection_test.go @@ -25,6 +25,8 @@ func TestSandboxConnectionNewWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Sandbox.Connections.New(context.TODO(), finchgo.SandboxConnectionNewParams{ ProviderID: finchgo.F("provider_id"), diff --git a/sandboxconnectionaccount.go b/sandboxconnectionaccount.go index 644e0327..63e2eea7 100644 --- a/sandboxconnectionaccount.go +++ b/sandboxconnectionaccount.go @@ -35,37 +35,41 @@ func NewSandboxConnectionAccountService(opts ...option.RequestOption) (r *Sandbo // Create a new account for an existing connection (company/provider pair) func (r *SandboxConnectionAccountService) New(ctx context.Context, body SandboxConnectionAccountNewParams, opts ...option.RequestOption) (res *SandboxConnectionAccountNewResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBasicAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "sandbox/connections/accounts" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } // Update an existing sandbox account. Change the connection status to understand // how the Finch API responds. func (r *SandboxConnectionAccountService) Update(ctx context.Context, body SandboxConnectionAccountUpdateParams, opts ...option.RequestOption) (res *SandboxConnectionAccountUpdateResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "sandbox/connections/accounts" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, &res, opts...) - return + return res, err } type SandboxConnectionAccountNewResponse struct { - AccessToken string `json:"access_token,required" format:"uuid"` + AccessToken string `json:"access_token" api:"required" format:"uuid"` // [DEPRECATED] Use `connection_id` to associate a connection with an access token // // Deprecated: deprecated - AccountID string `json:"account_id,required" format:"uuid"` - AuthenticationType SandboxConnectionAccountNewResponseAuthenticationType `json:"authentication_type,required"` + AccountID string `json:"account_id" api:"required" format:"uuid"` + AuthenticationType SandboxConnectionAccountNewResponseAuthenticationType `json:"authentication_type" api:"required"` // [DEPRECATED] Use `connection_id` to associate a connection with an access token // // Deprecated: deprecated - CompanyID string `json:"company_id,required" format:"uuid"` + CompanyID string `json:"company_id" api:"required" format:"uuid"` // The ID of the new connection - ConnectionID string `json:"connection_id,required" format:"uuid"` - Products []string `json:"products,required"` + ConnectionID string `json:"connection_id" api:"required" format:"uuid"` + // The ID of the entity for this connection + EntityID string `json:"entity_id" api:"required" format:"uuid"` + Products []string `json:"products" api:"required"` // The ID of the provider associated with the `access_token` - ProviderID string `json:"provider_id,required"` + ProviderID string `json:"provider_id" api:"required"` JSON sandboxConnectionAccountNewResponseJSON `json:"-"` } @@ -77,6 +81,7 @@ type sandboxConnectionAccountNewResponseJSON struct { AuthenticationType apijson.Field CompanyID apijson.Field ConnectionID apijson.Field + EntityID apijson.Field Products apijson.Field ProviderID apijson.Field raw string @@ -112,18 +117,20 @@ type SandboxConnectionAccountUpdateResponse struct { // [DEPRECATED] Use `connection_id` to associate a connection with an access token // // Deprecated: deprecated - AccountID string `json:"account_id,required" format:"uuid"` - AuthenticationType SandboxConnectionAccountUpdateResponseAuthenticationType `json:"authentication_type,required"` + AccountID string `json:"account_id" api:"required" format:"uuid"` + AuthenticationType SandboxConnectionAccountUpdateResponseAuthenticationType `json:"authentication_type" api:"required"` // [DEPRECATED] Use `connection_id` to associate a connection with an access token // // Deprecated: deprecated - CompanyID string `json:"company_id,required" format:"uuid"` - Products []string `json:"products,required"` - // The ID of the provider associated with the `access_token` - ProviderID string `json:"provider_id,required"` + CompanyID string `json:"company_id" api:"required" format:"uuid"` // The ID of the new connection - ConnectionID string `json:"connection_id" format:"uuid"` - JSON sandboxConnectionAccountUpdateResponseJSON `json:"-"` + ConnectionID string `json:"connection_id" api:"required" format:"uuid"` + // The ID of the entity whose status was updated + EntityID string `json:"entity_id" api:"required" format:"uuid"` + Products []string `json:"products" api:"required"` + // The ID of the provider associated with the `access_token` + ProviderID string `json:"provider_id" api:"required"` + JSON sandboxConnectionAccountUpdateResponseJSON `json:"-"` } // sandboxConnectionAccountUpdateResponseJSON contains the JSON metadata for the @@ -132,9 +139,10 @@ type sandboxConnectionAccountUpdateResponseJSON struct { AccountID apijson.Field AuthenticationType apijson.Field CompanyID apijson.Field + ConnectionID apijson.Field + EntityID apijson.Field Products apijson.Field ProviderID apijson.Field - ConnectionID apijson.Field raw string ExtraFields map[string]apijson.Field } @@ -165,9 +173,9 @@ func (r SandboxConnectionAccountUpdateResponseAuthenticationType) IsKnown() bool } type SandboxConnectionAccountNewParams struct { - CompanyID param.Field[string] `json:"company_id,required" format:"uuid"` + CompanyID param.Field[string] `json:"company_id" api:"required" format:"uuid"` // The provider associated with the `access_token` - ProviderID param.Field[string] `json:"provider_id,required"` + ProviderID param.Field[string] `json:"provider_id" api:"required"` AuthenticationType param.Field[SandboxConnectionAccountNewParamsAuthenticationType] `json:"authentication_type"` // Optional, defaults to Organization products (`company`, `directory`, // `employment`, `individual`) diff --git a/sandboxconnectionaccount_test.go b/sandboxconnectionaccount_test.go index a79e6ee5..98ac8324 100644 --- a/sandboxconnectionaccount_test.go +++ b/sandboxconnectionaccount_test.go @@ -26,6 +26,8 @@ func TestSandboxConnectionAccountNewWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Sandbox.Connections.Accounts.New(context.TODO(), finchgo.SandboxConnectionAccountNewParams{ CompanyID: finchgo.F("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"), @@ -53,6 +55,8 @@ func TestSandboxConnectionAccountUpdateWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Sandbox.Connections.Accounts.Update(context.TODO(), finchgo.SandboxConnectionAccountUpdateParams{ ConnectionStatus: finchgo.F(shared.ConnectionStatusTypeReauth), diff --git a/sandboxdirectory.go b/sandboxdirectory.go index 49a278e6..6049762f 100644 --- a/sandboxdirectory.go +++ b/sandboxdirectory.go @@ -34,10 +34,11 @@ func NewSandboxDirectoryService(opts ...option.RequestOption) (r *SandboxDirecto // Add new individuals to a sandbox company func (r *SandboxDirectoryService) New(ctx context.Context, body SandboxDirectoryNewParams, opts ...option.RequestOption) (res *[]SandboxDirectoryNewResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "sandbox/directory" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } type SandboxDirectoryNewResponse = interface{} @@ -65,8 +66,7 @@ type SandboxDirectoryNewParamsBody struct { Emails param.Field[[]SandboxDirectoryNewParamsBodyEmail] `json:"emails"` // The employment object. Employment param.Field[SandboxDirectoryNewParamsBodyEmployment] `json:"employment"` - // The detailed employment status of the individual. Available options: `active`, - // `deceased`, `leave`, `onboarding`, `prehire`, `retired`, `terminated`. + // The detailed employment status of the individual. EmploymentStatus param.Field[SandboxDirectoryNewParamsBodyEmploymentStatus] `json:"employment_status"` // Social Security Number of the individual in **encrypted** format. This field is // only available with the `ssn` scope enabled and the @@ -77,6 +77,9 @@ type SandboxDirectoryNewParamsBody struct { Ethnicity param.Field[SandboxDirectoryNewParamsBodyEthnicity] `json:"ethnicity"` // The legal first name of the individual. FirstName param.Field[string] `json:"first_name"` + // The FLSA status of the individual. Available options: `exempt`, `non_exempt`, + // `unknown`. + FlsaStatus param.Field[SandboxDirectoryNewParamsBodyFlsaStatus] `json:"flsa_status"` // The gender of the individual. Gender param.Field[SandboxDirectoryNewParamsBodyGender] `json:"gender"` // The employee's income as reported by the provider. This may not always be @@ -116,14 +119,28 @@ func (r SandboxDirectoryNewParamsBody) MarshalJSON() (data []byte, err error) { } type SandboxDirectoryNewParamsBodyCustomField struct { - Name param.Field[string] `json:"name"` - Value param.Field[interface{}] `json:"value"` + Name param.Field[string] `json:"name"` + Value param.Field[SandboxDirectoryNewParamsBodyCustomFieldsValueUnion] `json:"value"` } func (r SandboxDirectoryNewParamsBodyCustomField) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } +// Satisfied by [shared.UnionString], +// [SandboxDirectoryNewParamsBodyCustomFieldsValueArray], [shared.UnionFloat], +// [shared.UnionBool]. +// +// Use [Raw()] to specify an arbitrary value for this param +type SandboxDirectoryNewParamsBodyCustomFieldsValueUnion interface { + ImplementsSandboxDirectoryNewParamsBodyCustomFieldsValueUnion() +} + +type SandboxDirectoryNewParamsBodyCustomFieldsValueArray []interface{} + +func (r SandboxDirectoryNewParamsBodyCustomFieldsValueArray) ImplementsSandboxDirectoryNewParamsBodyCustomFieldsValueUnion() { +} + // The department object. type SandboxDirectoryNewParamsBodyDepartment struct { // The name of the department associated with the individual. @@ -208,8 +225,7 @@ func (r SandboxDirectoryNewParamsBodyEmploymentType) IsKnown() bool { return false } -// The detailed employment status of the individual. Available options: `active`, -// `deceased`, `leave`, `onboarding`, `prehire`, `retired`, `terminated`. +// The detailed employment status of the individual. type SandboxDirectoryNewParamsBodyEmploymentStatus string const ( @@ -252,6 +268,24 @@ func (r SandboxDirectoryNewParamsBodyEthnicity) IsKnown() bool { return false } +// The FLSA status of the individual. Available options: `exempt`, `non_exempt`, +// `unknown`. +type SandboxDirectoryNewParamsBodyFlsaStatus string + +const ( + SandboxDirectoryNewParamsBodyFlsaStatusExempt SandboxDirectoryNewParamsBodyFlsaStatus = "exempt" + SandboxDirectoryNewParamsBodyFlsaStatusNonExempt SandboxDirectoryNewParamsBodyFlsaStatus = "non_exempt" + SandboxDirectoryNewParamsBodyFlsaStatusUnknown SandboxDirectoryNewParamsBodyFlsaStatus = "unknown" +) + +func (r SandboxDirectoryNewParamsBodyFlsaStatus) IsKnown() bool { + switch r { + case SandboxDirectoryNewParamsBodyFlsaStatusExempt, SandboxDirectoryNewParamsBodyFlsaStatusNonExempt, SandboxDirectoryNewParamsBodyFlsaStatusUnknown: + return true + } + return false +} + // The gender of the individual. type SandboxDirectoryNewParamsBodyGender string diff --git a/sandboxdirectory_test.go b/sandboxdirectory_test.go index b18682a2..b000a272 100644 --- a/sandboxdirectory_test.go +++ b/sandboxdirectory_test.go @@ -12,6 +12,7 @@ import ( "github.com/Finch-API/finch-api-go" "github.com/Finch-API/finch-api-go/internal/testutil" "github.com/Finch-API/finch-api-go/option" + "github.com/Finch-API/finch-api-go/shared" ) func TestSandboxDirectoryNewWithOptionalParams(t *testing.T) { @@ -25,13 +26,15 @@ func TestSandboxDirectoryNewWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Sandbox.Directory.New(context.TODO(), finchgo.SandboxDirectoryNewParams{ Body: []finchgo.SandboxDirectoryNewParamsBody{{ ClassCode: finchgo.F("class_code"), CustomFields: finchgo.F([]finchgo.SandboxDirectoryNewParamsBodyCustomField{{ Name: finchgo.F("name"), - Value: finchgo.F[any](map[string]interface{}{}), + Value: finchgo.F[finchgo.SandboxDirectoryNewParamsBodyCustomFieldsValueUnion](shared.UnionString("string")), }}), Department: finchgo.F(finchgo.SandboxDirectoryNewParamsBodyDepartment{ Name: finchgo.F("name"), @@ -50,6 +53,7 @@ func TestSandboxDirectoryNewWithOptionalParams(t *testing.T) { EndDate: finchgo.F("end_date"), Ethnicity: finchgo.F(finchgo.SandboxDirectoryNewParamsBodyEthnicityAsian), FirstName: finchgo.F("first_name"), + FlsaStatus: finchgo.F(finchgo.SandboxDirectoryNewParamsBodyFlsaStatusExempt), Gender: finchgo.F(finchgo.SandboxDirectoryNewParamsBodyGenderFemale), Income: finchgo.F(finchgo.IncomeParam{ Amount: finchgo.F(int64(0)), diff --git a/sandboxemployment.go b/sandboxemployment.go index bc946edd..0b58faaf 100644 --- a/sandboxemployment.go +++ b/sandboxemployment.go @@ -7,12 +7,15 @@ import ( "errors" "fmt" "net/http" + "reflect" "slices" "github.com/Finch-API/finch-api-go/internal/apijson" "github.com/Finch-API/finch-api-go/internal/param" "github.com/Finch-API/finch-api-go/internal/requestconfig" "github.com/Finch-API/finch-api-go/option" + "github.com/Finch-API/finch-api-go/shared" + "github.com/tidwall/gjson" ) // SandboxEmploymentService contains methods and other services that help with @@ -36,56 +39,59 @@ func NewSandboxEmploymentService(opts ...option.RequestOption) (r *SandboxEmploy // Update sandbox employment func (r *SandboxEmploymentService) Update(ctx context.Context, individualID string, body SandboxEmploymentUpdateParams, opts ...option.RequestOption) (res *SandboxEmploymentUpdateResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) if individualID == "" { err = errors.New("missing required individual_id parameter") - return + return nil, err } path := fmt.Sprintf("sandbox/employment/%s", individualID) err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, &res, opts...) - return + return res, err } type SandboxEmploymentUpdateResponse struct { // A stable Finch `id` (UUID v4) for an individual in the company. ID string `json:"id" format:"uuid"` // Worker's compensation classification code for this employee - ClassCode string `json:"class_code,nullable"` + ClassCode string `json:"class_code" api:"nullable"` // Custom fields for the individual. These are fields which are defined by the // employer in the system. Custom fields are not currently supported for assisted // connections. - CustomFields []SandboxEmploymentUpdateResponseCustomField `json:"custom_fields,nullable"` + CustomFields []SandboxEmploymentUpdateResponseCustomField `json:"custom_fields" api:"nullable"` // The department object. - Department SandboxEmploymentUpdateResponseDepartment `json:"department,nullable"` + Department SandboxEmploymentUpdateResponseDepartment `json:"department" api:"nullable"` // The employment object. - Employment SandboxEmploymentUpdateResponseEmployment `json:"employment,nullable"` - // The detailed employment status of the individual. Available options: `active`, - // `deceased`, `leave`, `onboarding`, `prehire`, `retired`, `terminated`. - EmploymentStatus SandboxEmploymentUpdateResponseEmploymentStatus `json:"employment_status,nullable"` - EndDate string `json:"end_date,nullable"` + Employment SandboxEmploymentUpdateResponseEmployment `json:"employment" api:"nullable"` + // The detailed employment status of the individual. + EmploymentStatus SandboxEmploymentUpdateResponseEmploymentStatus `json:"employment_status" api:"nullable"` + EndDate string `json:"end_date" api:"nullable"` // The legal first name of the individual. - FirstName string `json:"first_name,nullable"` + FirstName string `json:"first_name" api:"nullable"` + // The FLSA status of the individual. Available options: `exempt`, `non_exempt`, + // `unknown`. + FlsaStatus SandboxEmploymentUpdateResponseFlsaStatus `json:"flsa_status" api:"nullable"` // The employee's income as reported by the provider. This may not always be // annualized income, but may be in units of bi-weekly, semi-monthly, daily, etc, // depending on what information the provider returns. - Income Income `json:"income,nullable"` + Income Income `json:"income" api:"nullable"` // The array of income history. - IncomeHistory []Income `json:"income_history,nullable"` + IncomeHistory []Income `json:"income_history" api:"nullable"` // `true` if the individual an an active employee or contractor at the company. - IsActive bool `json:"is_active,nullable"` + IsActive bool `json:"is_active" api:"nullable"` // The legal last name of the individual. - LastName string `json:"last_name,nullable"` - LatestRehireDate string `json:"latest_rehire_date,nullable"` - Location Location `json:"location,nullable"` + LastName string `json:"last_name" api:"nullable"` + LatestRehireDate string `json:"latest_rehire_date" api:"nullable"` + Location Location `json:"location" api:"nullable"` // The manager object representing the manager of the individual within the org. - Manager SandboxEmploymentUpdateResponseManager `json:"manager,nullable"` + Manager SandboxEmploymentUpdateResponseManager `json:"manager" api:"nullable"` // The legal middle name of the individual. - MiddleName string `json:"middle_name,nullable"` + MiddleName string `json:"middle_name" api:"nullable"` // The source system's unique employment identifier for this individual - SourceID string `json:"source_id,nullable"` - StartDate string `json:"start_date,nullable"` + SourceID string `json:"source_id" api:"nullable"` + StartDate string `json:"start_date" api:"nullable"` // The current title of the individual. - Title string `json:"title,nullable"` + Title string `json:"title" api:"nullable"` JSON sandboxEmploymentUpdateResponseJSON `json:"-"` } @@ -100,6 +106,7 @@ type sandboxEmploymentUpdateResponseJSON struct { EmploymentStatus apijson.Field EndDate apijson.Field FirstName apijson.Field + FlsaStatus apijson.Field Income apijson.Field IncomeHistory apijson.Field IsActive apijson.Field @@ -124,9 +131,9 @@ func (r sandboxEmploymentUpdateResponseJSON) RawJSON() string { } type SandboxEmploymentUpdateResponseCustomField struct { - Name string `json:"name,nullable"` - Value interface{} `json:"value"` - JSON sandboxEmploymentUpdateResponseCustomFieldJSON `json:"-"` + Name string `json:"name" api:"nullable"` + Value SandboxEmploymentUpdateResponseCustomFieldsValueUnion `json:"value" api:"nullable"` + JSON sandboxEmploymentUpdateResponseCustomFieldJSON `json:"-"` } // sandboxEmploymentUpdateResponseCustomFieldJSON contains the JSON metadata for @@ -146,10 +153,49 @@ func (r sandboxEmploymentUpdateResponseCustomFieldJSON) RawJSON() string { return r.raw } +// Union satisfied by [shared.UnionString], +// [SandboxEmploymentUpdateResponseCustomFieldsValueArray], [shared.UnionFloat] or +// [shared.UnionBool]. +type SandboxEmploymentUpdateResponseCustomFieldsValueUnion interface { + ImplementsSandboxEmploymentUpdateResponseCustomFieldsValueUnion() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*SandboxEmploymentUpdateResponseCustomFieldsValueUnion)(nil)).Elem(), + "", + apijson.UnionVariant{ + TypeFilter: gjson.String, + Type: reflect.TypeOf(shared.UnionString("")), + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(SandboxEmploymentUpdateResponseCustomFieldsValueArray{}), + }, + apijson.UnionVariant{ + TypeFilter: gjson.Number, + Type: reflect.TypeOf(shared.UnionFloat(0)), + }, + apijson.UnionVariant{ + TypeFilter: gjson.True, + Type: reflect.TypeOf(shared.UnionBool(false)), + }, + apijson.UnionVariant{ + TypeFilter: gjson.False, + Type: reflect.TypeOf(shared.UnionBool(false)), + }, + ) +} + +type SandboxEmploymentUpdateResponseCustomFieldsValueArray []interface{} + +func (r SandboxEmploymentUpdateResponseCustomFieldsValueArray) ImplementsSandboxEmploymentUpdateResponseCustomFieldsValueUnion() { +} + // The department object. type SandboxEmploymentUpdateResponseDepartment struct { // The name of the department associated with the individual. - Name string `json:"name,nullable"` + Name string `json:"name" api:"nullable"` JSON sandboxEmploymentUpdateResponseDepartmentJSON `json:"-"` } @@ -173,9 +219,9 @@ func (r sandboxEmploymentUpdateResponseDepartmentJSON) RawJSON() string { type SandboxEmploymentUpdateResponseEmployment struct { // The secondary employment type of the individual. Options: `full_time`, // `part_time`, `intern`, `temp`, `seasonal` and `individual_contractor`. - Subtype SandboxEmploymentUpdateResponseEmploymentSubtype `json:"subtype,nullable"` + Subtype SandboxEmploymentUpdateResponseEmploymentSubtype `json:"subtype" api:"nullable"` // The main employment type of the individual. - Type SandboxEmploymentUpdateResponseEmploymentType `json:"type,nullable"` + Type SandboxEmploymentUpdateResponseEmploymentType `json:"type" api:"nullable"` JSON sandboxEmploymentUpdateResponseEmploymentJSON `json:"-"` } @@ -233,8 +279,7 @@ func (r SandboxEmploymentUpdateResponseEmploymentType) IsKnown() bool { return false } -// The detailed employment status of the individual. Available options: `active`, -// `deceased`, `leave`, `onboarding`, `prehire`, `retired`, `terminated`. +// The detailed employment status of the individual. type SandboxEmploymentUpdateResponseEmploymentStatus string const ( @@ -255,6 +300,24 @@ func (r SandboxEmploymentUpdateResponseEmploymentStatus) IsKnown() bool { return false } +// The FLSA status of the individual. Available options: `exempt`, `non_exempt`, +// `unknown`. +type SandboxEmploymentUpdateResponseFlsaStatus string + +const ( + SandboxEmploymentUpdateResponseFlsaStatusExempt SandboxEmploymentUpdateResponseFlsaStatus = "exempt" + SandboxEmploymentUpdateResponseFlsaStatusNonExempt SandboxEmploymentUpdateResponseFlsaStatus = "non_exempt" + SandboxEmploymentUpdateResponseFlsaStatusUnknown SandboxEmploymentUpdateResponseFlsaStatus = "unknown" +) + +func (r SandboxEmploymentUpdateResponseFlsaStatus) IsKnown() bool { + switch r { + case SandboxEmploymentUpdateResponseFlsaStatusExempt, SandboxEmploymentUpdateResponseFlsaStatusNonExempt, SandboxEmploymentUpdateResponseFlsaStatusUnknown: + return true + } + return false +} + // The manager object representing the manager of the individual within the org. type SandboxEmploymentUpdateResponseManager struct { // A stable Finch `id` (UUID v4) for an individual in the company. @@ -289,12 +352,14 @@ type SandboxEmploymentUpdateParams struct { Department param.Field[SandboxEmploymentUpdateParamsDepartment] `json:"department"` // The employment object. Employment param.Field[SandboxEmploymentUpdateParamsEmployment] `json:"employment"` - // The detailed employment status of the individual. Available options: `active`, - // `deceased`, `leave`, `onboarding`, `prehire`, `retired`, `terminated`. + // The detailed employment status of the individual. EmploymentStatus param.Field[SandboxEmploymentUpdateParamsEmploymentStatus] `json:"employment_status"` EndDate param.Field[string] `json:"end_date"` // The legal first name of the individual. FirstName param.Field[string] `json:"first_name"` + // The FLSA status of the individual. Available options: `exempt`, `non_exempt`, + // `unknown`. + FlsaStatus param.Field[SandboxEmploymentUpdateParamsFlsaStatus] `json:"flsa_status"` // The employee's income as reported by the provider. This may not always be // annualized income, but may be in units of bi-weekly, semi-monthly, daily, etc, // depending on what information the provider returns. @@ -323,14 +388,28 @@ func (r SandboxEmploymentUpdateParams) MarshalJSON() (data []byte, err error) { } type SandboxEmploymentUpdateParamsCustomField struct { - Name param.Field[string] `json:"name"` - Value param.Field[interface{}] `json:"value"` + Name param.Field[string] `json:"name"` + Value param.Field[SandboxEmploymentUpdateParamsCustomFieldsValueUnion] `json:"value"` } func (r SandboxEmploymentUpdateParamsCustomField) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } +// Satisfied by [shared.UnionString], +// [SandboxEmploymentUpdateParamsCustomFieldsValueArray], [shared.UnionFloat], +// [shared.UnionBool]. +// +// Use [Raw()] to specify an arbitrary value for this param +type SandboxEmploymentUpdateParamsCustomFieldsValueUnion interface { + ImplementsSandboxEmploymentUpdateParamsCustomFieldsValueUnion() +} + +type SandboxEmploymentUpdateParamsCustomFieldsValueArray []interface{} + +func (r SandboxEmploymentUpdateParamsCustomFieldsValueArray) ImplementsSandboxEmploymentUpdateParamsCustomFieldsValueUnion() { +} + // The department object. type SandboxEmploymentUpdateParamsDepartment struct { // The name of the department associated with the individual. @@ -391,8 +470,7 @@ func (r SandboxEmploymentUpdateParamsEmploymentType) IsKnown() bool { return false } -// The detailed employment status of the individual. Available options: `active`, -// `deceased`, `leave`, `onboarding`, `prehire`, `retired`, `terminated`. +// The detailed employment status of the individual. type SandboxEmploymentUpdateParamsEmploymentStatus string const ( @@ -413,6 +491,24 @@ func (r SandboxEmploymentUpdateParamsEmploymentStatus) IsKnown() bool { return false } +// The FLSA status of the individual. Available options: `exempt`, `non_exempt`, +// `unknown`. +type SandboxEmploymentUpdateParamsFlsaStatus string + +const ( + SandboxEmploymentUpdateParamsFlsaStatusExempt SandboxEmploymentUpdateParamsFlsaStatus = "exempt" + SandboxEmploymentUpdateParamsFlsaStatusNonExempt SandboxEmploymentUpdateParamsFlsaStatus = "non_exempt" + SandboxEmploymentUpdateParamsFlsaStatusUnknown SandboxEmploymentUpdateParamsFlsaStatus = "unknown" +) + +func (r SandboxEmploymentUpdateParamsFlsaStatus) IsKnown() bool { + switch r { + case SandboxEmploymentUpdateParamsFlsaStatusExempt, SandboxEmploymentUpdateParamsFlsaStatusNonExempt, SandboxEmploymentUpdateParamsFlsaStatusUnknown: + return true + } + return false +} + // The manager object representing the manager of the individual within the org. type SandboxEmploymentUpdateParamsManager struct { // A stable Finch `id` (UUID v4) for an individual in the company. diff --git a/sandboxemployment_test.go b/sandboxemployment_test.go index 2634723e..2881a3df 100644 --- a/sandboxemployment_test.go +++ b/sandboxemployment_test.go @@ -12,6 +12,7 @@ import ( "github.com/Finch-API/finch-api-go" "github.com/Finch-API/finch-api-go/internal/testutil" "github.com/Finch-API/finch-api-go/option" + "github.com/Finch-API/finch-api-go/shared" ) func TestSandboxEmploymentUpdateWithOptionalParams(t *testing.T) { @@ -25,6 +26,8 @@ func TestSandboxEmploymentUpdateWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Sandbox.Employment.Update( context.TODO(), @@ -33,7 +36,7 @@ func TestSandboxEmploymentUpdateWithOptionalParams(t *testing.T) { ClassCode: finchgo.F("class_code"), CustomFields: finchgo.F([]finchgo.SandboxEmploymentUpdateParamsCustomField{{ Name: finchgo.F("name"), - Value: finchgo.F[any](map[string]interface{}{}), + Value: finchgo.F[finchgo.SandboxEmploymentUpdateParamsCustomFieldsValueUnion](shared.UnionString("string")), }}), Department: finchgo.F(finchgo.SandboxEmploymentUpdateParamsDepartment{ Name: finchgo.F("name"), @@ -45,6 +48,7 @@ func TestSandboxEmploymentUpdateWithOptionalParams(t *testing.T) { EmploymentStatus: finchgo.F(finchgo.SandboxEmploymentUpdateParamsEmploymentStatusActive), EndDate: finchgo.F("end_date"), FirstName: finchgo.F("first_name"), + FlsaStatus: finchgo.F(finchgo.SandboxEmploymentUpdateParamsFlsaStatusExempt), Income: finchgo.F(finchgo.IncomeParam{ Amount: finchgo.F(int64(0)), Currency: finchgo.F("currency"), @@ -75,7 +79,7 @@ func TestSandboxEmploymentUpdateWithOptionalParams(t *testing.T) { }), MiddleName: finchgo.F("middle_name"), SourceID: finchgo.F("source_id"), - StartDate: finchgo.F("start_date"), + StartDate: finchgo.F("3/4/2020"), Title: finchgo.F("title"), }, ) diff --git a/sandboxindividual.go b/sandboxindividual.go index c15afc23..341bb691 100644 --- a/sandboxindividual.go +++ b/sandboxindividual.go @@ -36,44 +36,45 @@ func NewSandboxIndividualService(opts ...option.RequestOption) (r *SandboxIndivi // Update sandbox individual func (r *SandboxIndividualService) Update(ctx context.Context, individualID string, body SandboxIndividualUpdateParams, opts ...option.RequestOption) (res *SandboxIndividualUpdateResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) if individualID == "" { err = errors.New("missing required individual_id parameter") - return + return nil, err } path := fmt.Sprintf("sandbox/individual/%s", individualID) err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, &res, opts...) - return + return res, err } type SandboxIndividualUpdateResponse struct { // A stable Finch `id` (UUID v4) for an individual in the company. ID string `json:"id" format:"uuid"` - Dob string `json:"dob,nullable"` - Emails []SandboxIndividualUpdateResponseEmail `json:"emails,nullable"` + Dob string `json:"dob" api:"nullable"` + Emails []SandboxIndividualUpdateResponseEmail `json:"emails" api:"nullable"` // Social Security Number of the individual in **encrypted** format. This field is // only available with the `ssn` scope enabled and the // `options: { include: ['ssn'] }` param set in the body. - EncryptedSsn string `json:"encrypted_ssn,nullable"` + EncryptedSsn string `json:"encrypted_ssn" api:"nullable"` // The EEOC-defined ethnicity of the individual. - Ethnicity SandboxIndividualUpdateResponseEthnicity `json:"ethnicity,nullable"` + Ethnicity SandboxIndividualUpdateResponseEthnicity `json:"ethnicity" api:"nullable"` // The legal first name of the individual. - FirstName string `json:"first_name,nullable"` + FirstName string `json:"first_name" api:"nullable"` // The gender of the individual. - Gender SandboxIndividualUpdateResponseGender `json:"gender,nullable"` + Gender SandboxIndividualUpdateResponseGender `json:"gender" api:"nullable"` // The legal last name of the individual. - LastName string `json:"last_name,nullable"` + LastName string `json:"last_name" api:"nullable"` // The legal middle name of the individual. - MiddleName string `json:"middle_name,nullable"` - PhoneNumbers []SandboxIndividualUpdateResponsePhoneNumber `json:"phone_numbers,nullable"` + MiddleName string `json:"middle_name" api:"nullable"` + PhoneNumbers []SandboxIndividualUpdateResponsePhoneNumber `json:"phone_numbers" api:"nullable"` // The preferred name of the individual. - PreferredName string `json:"preferred_name,nullable"` - Residence Location `json:"residence,nullable"` + PreferredName string `json:"preferred_name" api:"nullable"` + Residence Location `json:"residence" api:"nullable"` // Social Security Number of the individual. This field is only available with the // `ssn` scope enabled and the `options: { include: ['ssn'] }` param set in the // body. // [Click here to learn more about enabling the SSN field](/developer-resources/Enable-SSN-Field). - Ssn string `json:"ssn,nullable"` + Ssn string `json:"ssn" api:"nullable"` JSON sandboxIndividualUpdateResponseJSON `json:"-"` } @@ -107,7 +108,7 @@ func (r sandboxIndividualUpdateResponseJSON) RawJSON() string { type SandboxIndividualUpdateResponseEmail struct { Data string `json:"data"` - Type SandboxIndividualUpdateResponseEmailsType `json:"type,nullable"` + Type SandboxIndividualUpdateResponseEmailsType `json:"type" api:"nullable"` JSON sandboxIndividualUpdateResponseEmailJSON `json:"-"` } @@ -184,8 +185,8 @@ func (r SandboxIndividualUpdateResponseGender) IsKnown() bool { } type SandboxIndividualUpdateResponsePhoneNumber struct { - Data string `json:"data,nullable"` - Type SandboxIndividualUpdateResponsePhoneNumbersType `json:"type,nullable"` + Data string `json:"data" api:"nullable"` + Type SandboxIndividualUpdateResponsePhoneNumbersType `json:"type" api:"nullable"` JSON sandboxIndividualUpdateResponsePhoneNumberJSON `json:"-"` } diff --git a/sandboxindividual_test.go b/sandboxindividual_test.go index a55e9aa5..d777e9de 100644 --- a/sandboxindividual_test.go +++ b/sandboxindividual_test.go @@ -24,6 +24,8 @@ func TestSandboxIndividualUpdateWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Sandbox.Individual.Update( context.TODO(), diff --git a/sandboxjob.go b/sandboxjob.go index 4cf958b4..d095a82e 100644 --- a/sandboxjob.go +++ b/sandboxjob.go @@ -36,21 +36,22 @@ func NewSandboxJobService(opts ...option.RequestOption) (r *SandboxJobService) { // Enqueue a new sandbox job func (r *SandboxJobService) New(ctx context.Context, body SandboxJobNewParams, opts ...option.RequestOption) (res *SandboxJobNewResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "sandbox/jobs" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } type SandboxJobNewResponse struct { // The number of allowed refreshes per hour (per hour, fixed window) - AllowedRefreshes int64 `json:"allowed_refreshes,required"` + AllowedRefreshes int64 `json:"allowed_refreshes" api:"required"` // The id of the job that has been created. - JobID string `json:"job_id,required" format:"uuid"` + JobID string `json:"job_id" api:"required" format:"uuid"` // The url that can be used to retrieve the job status - JobURL string `json:"job_url,required"` + JobURL string `json:"job_url" api:"required"` // The number of remaining refreshes available (per hour, fixed window) - RemainingRefreshes int64 `json:"remaining_refreshes,required"` + RemainingRefreshes int64 `json:"remaining_refreshes" api:"required"` JSON sandboxJobNewResponseJSON `json:"-"` } @@ -75,7 +76,7 @@ func (r sandboxJobNewResponseJSON) RawJSON() string { type SandboxJobNewParams struct { // The type of job to start. Currently the only supported type is `data_sync_all` - Type param.Field[SandboxJobNewParamsType] `json:"type,required"` + Type param.Field[SandboxJobNewParamsType] `json:"type" api:"required"` } func (r SandboxJobNewParams) MarshalJSON() (data []byte, err error) { diff --git a/sandboxjob_test.go b/sandboxjob_test.go index 48e1214f..0944d7ea 100644 --- a/sandboxjob_test.go +++ b/sandboxjob_test.go @@ -24,6 +24,8 @@ func TestSandboxJobNew(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Sandbox.Jobs.New(context.TODO(), finchgo.SandboxJobNewParams{ Type: finchgo.F(finchgo.SandboxJobNewParamsTypeDataSyncAll), diff --git a/sandboxjobconfiguration.go b/sandboxjobconfiguration.go index b0272e9e..5692135f 100644 --- a/sandboxjobconfiguration.go +++ b/sandboxjobconfiguration.go @@ -34,23 +34,25 @@ func NewSandboxJobConfigurationService(opts ...option.RequestOption) (r *Sandbox // Get configurations for sandbox jobs func (r *SandboxJobConfigurationService) Get(ctx context.Context, opts ...option.RequestOption) (res *[]SandboxJobConfiguration, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "sandbox/jobs/configuration" err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return + return res, err } // Update configurations for sandbox jobs func (r *SandboxJobConfigurationService) Update(ctx context.Context, body SandboxJobConfigurationUpdateParams, opts ...option.RequestOption) (res *SandboxJobConfiguration, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "sandbox/jobs/configuration" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, &res, opts...) - return + return res, err } type SandboxJobConfiguration struct { - CompletionStatus SandboxJobConfigurationCompletionStatus `json:"completion_status,required"` - Type SandboxJobConfigurationType `json:"type,required"` + CompletionStatus SandboxJobConfigurationCompletionStatus `json:"completion_status" api:"required"` + Type SandboxJobConfigurationType `json:"type" api:"required"` JSON sandboxJobConfigurationJSON `json:"-"` } @@ -103,8 +105,8 @@ func (r SandboxJobConfigurationType) IsKnown() bool { } type SandboxJobConfigurationUpdateParams struct { - CompletionStatus param.Field[SandboxJobConfigurationUpdateParamsCompletionStatus] `json:"completion_status,required"` - Type param.Field[SandboxJobConfigurationUpdateParamsType] `json:"type,required"` + CompletionStatus param.Field[SandboxJobConfigurationUpdateParamsCompletionStatus] `json:"completion_status" api:"required"` + Type param.Field[SandboxJobConfigurationUpdateParamsType] `json:"type" api:"required"` } func (r SandboxJobConfigurationUpdateParams) MarshalJSON() (data []byte, err error) { diff --git a/sandboxjobconfiguration_test.go b/sandboxjobconfiguration_test.go index 0311d5aa..09ccbab5 100644 --- a/sandboxjobconfiguration_test.go +++ b/sandboxjobconfiguration_test.go @@ -24,6 +24,8 @@ func TestSandboxJobConfigurationGet(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Sandbox.Jobs.Configuration.Get(context.TODO()) if err != nil { @@ -46,6 +48,8 @@ func TestSandboxJobConfigurationUpdate(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Sandbox.Jobs.Configuration.Update(context.TODO(), finchgo.SandboxJobConfigurationUpdateParams{ CompletionStatus: finchgo.F(finchgo.SandboxJobConfigurationUpdateParamsCompletionStatusComplete), diff --git a/sandboxpayment.go b/sandboxpayment.go index f65992da..a037ebab 100644 --- a/sandboxpayment.go +++ b/sandboxpayment.go @@ -35,17 +35,18 @@ func NewSandboxPaymentService(opts ...option.RequestOption) (r *SandboxPaymentSe // Add a new sandbox payment func (r *SandboxPaymentService) New(ctx context.Context, body SandboxPaymentNewParams, opts ...option.RequestOption) (res *SandboxPaymentNewResponse, err error) { - opts = slices.Concat(r.Options, opts) + var preClientOpts = []option.RequestOption{requestconfig.WithBearerAuthSecurity()} + opts = slices.Concat(preClientOpts, r.Options, opts) path := "sandbox/payment" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return + return res, err } type SandboxPaymentNewResponse struct { // The date of the payment. - PayDate string `json:"pay_date,required"` + PayDate string `json:"pay_date" api:"required"` // The ID of the payment. - PaymentID string `json:"payment_id,required"` + PaymentID string `json:"payment_id" api:"required"` JSON sandboxPaymentNewResponseJSON `json:"-"` } @@ -78,7 +79,7 @@ func (r SandboxPaymentNewParams) MarshalJSON() (data []byte, err error) { } type SandboxPaymentNewParamsPayStatement struct { - IndividualID param.Field[string] `json:"individual_id,required" format:"uuid"` + IndividualID param.Field[string] `json:"individual_id" api:"required" format:"uuid"` Earnings param.Field[[]SandboxPaymentNewParamsPayStatementsEarning] `json:"earnings"` EmployeeDeductions param.Field[[]SandboxPaymentNewParamsPayStatementsEmployeeDeduction] `json:"employee_deductions"` EmployerContributions param.Field[[]SandboxPaymentNewParamsPayStatementsEmployerContribution] `json:"employer_contributions"` @@ -132,7 +133,8 @@ func (r SandboxPaymentNewParamsPayStatementsEarningsType) IsKnown() bool { } type SandboxPaymentNewParamsPayStatementsEmployeeDeduction struct { - Amount param.Field[int64] `json:"amount"` + Amount param.Field[int64] `json:"amount"` + // The deduction name. Required when type is specified. Name param.Field[string] `json:"name"` PreTax param.Field[bool] `json:"pre_tax"` Type param.Field[SandboxPaymentNewParamsPayStatementsEmployeeDeductionsType] `json:"type"` @@ -175,9 +177,10 @@ func (r SandboxPaymentNewParamsPayStatementsEmployeeDeductionsType) IsKnown() bo } type SandboxPaymentNewParamsPayStatementsEmployerContribution struct { - Amount param.Field[int64] `json:"amount"` - Name param.Field[string] `json:"name"` - Type param.Field[SandboxPaymentNewParamsPayStatementsEmployerContributionsType] `json:"type"` + Amount param.Field[int64] `json:"amount"` + // The contribution name. Required when type is specified. + Name param.Field[string] `json:"name"` + Type param.Field[SandboxPaymentNewParamsPayStatementsEmployerContributionsType] `json:"type"` } func (r SandboxPaymentNewParamsPayStatementsEmployerContribution) MarshalJSON() (data []byte, err error) { diff --git a/sandboxpayment_test.go b/sandboxpayment_test.go index a733f40d..ca67b00e 100644 --- a/sandboxpayment_test.go +++ b/sandboxpayment_test.go @@ -25,6 +25,8 @@ func TestSandboxPaymentNewWithOptionalParams(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) _, err := client.Sandbox.Payment.New(context.TODO(), finchgo.SandboxPaymentNewParams{ EndDate: finchgo.F(time.Now()), diff --git a/scripts/bootstrap b/scripts/bootstrap index 5ab30665..46547f18 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { echo -n "==> Install Homebrew dependencies? (y/N): " read -r response diff --git a/scripts/mock b/scripts/mock index 0b28f6ea..04d29019 100755 --- a/scripts/mock +++ b/scripts/mock @@ -19,23 +19,34 @@ fi echo "==> Starting mock server with URL ${URL}" -# Run prism mock on the given spec +# Run steady mock on the given spec if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & + # Pre-install the package so the download doesn't eat into the startup timeout + npm exec --package=@stdy/cli@0.22.1 -- steady --version - # Wait for server to come online + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=brackets --validator-form-array-format=brackets --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" &> .stdy.log & + + # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + attempts=0 + while ! curl --silent --fail "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1; do + if ! kill -0 $! 2>/dev/null; then + echo + cat .stdy.log + exit 1 + fi + attempts=$((attempts + 1)) + if [ "$attempts" -ge 300 ]; then + echo + echo "Timed out waiting for Steady server to start" + cat .stdy.log + exit 1 + fi echo -n "." sleep 0.1 done - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - echo else - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=brackets --validator-form-array-format=brackets --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index c26b1222..50c89d31 100755 --- a/scripts/test +++ b/scripts/test @@ -9,8 +9,8 @@ GREEN='\033[0;32m' YELLOW='\033[0;33m' NC='\033[0m' # No Color -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 +function steady_is_running() { + curl --silent "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1 } kill_server_on_port() { @@ -25,7 +25,7 @@ function is_overriding_api_base_url() { [ -n "$TEST_API_BASE_URL" ] } -if ! is_overriding_api_base_url && ! prism_is_running ; then +if ! is_overriding_api_base_url && ! steady_is_running ; then # When we exit this script, make sure to kill the background mock server process trap 'kill_server_on_port 4010' EXIT @@ -36,19 +36,19 @@ fi if is_overriding_api_base_url ; then echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" +elif ! steady_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Steady server" echo -e "running against your OpenAPI spec." echo echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" + echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.22.1 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=brackets --validator-form-array-format=brackets --validator-query-object-format=brackets --validator-form-object-format=brackets${NC}" echo exit 1 else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo -e "${GREEN}✔ Mock steady server is running with your OpenAPI spec${NC}" echo fi diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 00000000..5658829e --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -exuo pipefail + +DIST_DIR="dist" +FILENAME="source.zip" + +mapfile -d '' files < <( + find . -type f \ + \( -name '*.go' -o -name 'go.mod' -o -name 'go.sum' \) \ + ! -path "./${DIST_DIR}/*" \ + -print0 +) + +if [[ ${#files[@]} -eq 0 ]]; then + echo -e "\033[31mNo Go source files found for packaging.\033[0m" + exit 1 +fi + +mkdir -p "$DIST_DIR" +rm -f "${DIST_DIR}/${FILENAME}" + +relative_files=() +for file in "${files[@]}"; do + relative_files+=("${file#./}") +done + +zip "${DIST_DIR}/${FILENAME}" "${relative_files[@]}" + +RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +UPLOAD_RESPONSE=$(curl -v -X PUT \ + -H "Content-Type: application/zip" \ + --data-binary "@${DIST_DIR}/${FILENAME}" "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: Download and unzip: 'https://pkg.stainless.com/s/finch-go/$SHA'. Run 'go mod edit -replace github.com/Finch-API/finch-api-go=/path/to/unzipped_directory'.\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi diff --git a/shared/shared.go b/shared/shared.go index 307facbe..133383c8 100644 --- a/shared/shared.go +++ b/shared/shared.go @@ -106,7 +106,7 @@ func (r operationSupportMatrixJSON) RawJSON() string { type Paging struct { // The current start index of the returned list of elements - Offset int64 `json:"offset,required"` + Offset int64 `json:"offset" api:"required"` // The total number of elements for the entire query (not just the given page) Count int64 `json:"count"` JSON pagingJSON `json:"-"` diff --git a/shared/union.go b/shared/union.go index f4187e22..f8f863e1 100644 --- a/shared/union.go +++ b/shared/union.go @@ -14,16 +14,25 @@ func (UnionTime) ImplementsIntrospectionAuthenticationMethodsConnectionStatusLas type UnionString string -func (UnionString) ImplementsEmploymentDataObjectCustomFieldsValueUnion() {} -func (UnionString) ImplementsIntrospectionConnectionStatusLastSuccessfulSyncUnion() {} +func (UnionString) ImplementsEmploymentDataEmploymentDataResponseBodyCustomFieldsValueUnion() {} +func (UnionString) ImplementsIntrospectionConnectionStatusLastSuccessfulSyncUnion() {} func (UnionString) ImplementsIntrospectionAuthenticationMethodsConnectionStatusLastSuccessfulSyncUnion() { } -func (UnionString) ImplementsRequestForwardingForwardResponseRequestDataUnion() {} +func (UnionString) ImplementsRequestForwardingForwardResponseRequestDataUnion() {} +func (UnionString) ImplementsSandboxDirectoryNewParamsBodyCustomFieldsValueUnion() {} +func (UnionString) ImplementsSandboxEmploymentUpdateResponseCustomFieldsValueUnion() {} +func (UnionString) ImplementsSandboxEmploymentUpdateParamsCustomFieldsValueUnion() {} type UnionBool bool -func (UnionBool) ImplementsEmploymentDataObjectCustomFieldsValueUnion() {} +func (UnionBool) ImplementsEmploymentDataEmploymentDataResponseBodyCustomFieldsValueUnion() {} +func (UnionBool) ImplementsSandboxDirectoryNewParamsBodyCustomFieldsValueUnion() {} +func (UnionBool) ImplementsSandboxEmploymentUpdateResponseCustomFieldsValueUnion() {} +func (UnionBool) ImplementsSandboxEmploymentUpdateParamsCustomFieldsValueUnion() {} type UnionFloat float64 -func (UnionFloat) ImplementsEmploymentDataObjectCustomFieldsValueUnion() {} +func (UnionFloat) ImplementsEmploymentDataEmploymentDataResponseBodyCustomFieldsValueUnion() {} +func (UnionFloat) ImplementsSandboxDirectoryNewParamsBodyCustomFieldsValueUnion() {} +func (UnionFloat) ImplementsSandboxEmploymentUpdateResponseCustomFieldsValueUnion() {} +func (UnionFloat) ImplementsSandboxEmploymentUpdateParamsCustomFieldsValueUnion() {} diff --git a/usage_test.go b/usage_test.go index 40a33099..3cebfb2e 100644 --- a/usage_test.go +++ b/usage_test.go @@ -23,6 +23,8 @@ func TestUsage(t *testing.T) { client := finchgo.NewClient( option.WithBaseURL(baseURL), option.WithAccessToken("My Access Token"), + option.WithClientID("4ab15e51-11ad-49f4-acae-f343b7794375"), + option.WithClientSecret("My Client Secret"), ) page, err := client.HRIS.Directory.List(context.TODO(), finchgo.HRISDirectoryListParams{}) if err != nil { diff --git a/webhook.go b/webhook.go index 565db13d..c4b08a9e 100644 --- a/webhook.go +++ b/webhook.go @@ -143,8 +143,8 @@ func (r accountUpdateEventJSON) RawJSON() string { func (r AccountUpdateEvent) implementsWebhookEventUnion() {} type AccountUpdateEventData struct { - AuthenticationMethod AccountUpdateEventDataAuthenticationMethod `json:"authentication_method,required"` - Status shared.ConnectionStatusType `json:"status,required"` + AuthenticationMethod AccountUpdateEventDataAuthenticationMethod `json:"authentication_method" api:"required"` + Status shared.ConnectionStatusType `json:"status" api:"required"` JSON accountUpdateEventDataJSON `json:"-"` } @@ -168,9 +168,9 @@ func (r accountUpdateEventDataJSON) RawJSON() string { type AccountUpdateEventDataAuthenticationMethod struct { // Each benefit type and their supported features. If the benefit type is not // supported, the property will be null - BenefitsSupport BenefitsSupport `json:"benefits_support,nullable"` + BenefitsSupport BenefitsSupport `json:"benefits_support" api:"nullable"` // The supported data fields returned by our HR and payroll endpoints - SupportedFields AccountUpdateEventDataAuthenticationMethodSupportedFields `json:"supported_fields,nullable"` + SupportedFields AccountUpdateEventDataAuthenticationMethodSupportedFields `json:"supported_fields" api:"nullable"` // The type of authentication method. Type AccountUpdateEventDataAuthenticationMethodType `json:"type"` JSON accountUpdateEventDataAuthenticationMethodJSON `json:"-"` @@ -862,8 +862,8 @@ func (r accountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementJSO } type AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPaging struct { - Count bool `json:"count,required"` - Offset bool `json:"offset,required"` + Count bool `json:"count" api:"required"` + Offset bool `json:"offset" api:"required"` JSON accountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPagingJSON `json:"-"` } @@ -1146,15 +1146,17 @@ type BaseWebhookEvent struct { // with this event. // // Deprecated: deprecated - AccountID string `json:"account_id,required"` + AccountID string `json:"account_id" api:"required"` // [DEPRECATED] Unique Finch ID of the company for which data has been updated. Use // `connection_id` instead to identify the connection associated with this event. // // Deprecated: deprecated - CompanyID string `json:"company_id,required"` + CompanyID string `json:"company_id" api:"required"` // Unique Finch ID of the connection associated with the webhook event. - ConnectionID string `json:"connection_id"` - JSON baseWebhookEventJSON `json:"-"` + ConnectionID string `json:"connection_id"` + // Unique Finch id of the entity for which data has been updated. + EntityID string `json:"entity_id"` + JSON baseWebhookEventJSON `json:"-"` } // baseWebhookEventJSON contains the JSON metadata for the struct @@ -1163,6 +1165,7 @@ type baseWebhookEventJSON struct { AccountID apijson.Field CompanyID apijson.Field ConnectionID apijson.Field + EntityID apijson.Field raw string ExtraFields map[string]apijson.Field } @@ -1176,7 +1179,7 @@ func (r baseWebhookEventJSON) RawJSON() string { } type CompanyEvent struct { - Data map[string]interface{} `json:"data,nullable"` + Data map[string]interface{} `json:"data" api:"nullable"` EventType CompanyEventEventType `json:"event_type"` JSON companyEventJSON `json:"-"` BaseWebhookEvent @@ -1431,9 +1434,9 @@ func (r JobCompletionEvent) implementsWebhookEventUnion() {} type JobCompletionEventData struct { // The id of the job which has completed. - JobID string `json:"job_id,required"` + JobID string `json:"job_id" api:"required"` // The url to query the result of the job. - JobURL string `json:"job_url,required"` + JobURL string `json:"job_url" api:"required"` JSON jobCompletionEventDataJSON `json:"-"` } @@ -1457,17 +1460,20 @@ func (r jobCompletionEventDataJSON) RawJSON() string { type JobCompletionEventEventType string const ( - JobCompletionEventEventTypeJobBenefitCreateCompleted JobCompletionEventEventType = "job.benefit_create.completed" - JobCompletionEventEventTypeJobBenefitEnrollCompleted JobCompletionEventEventType = "job.benefit_enroll.completed" - JobCompletionEventEventTypeJobBenefitRegisterCompleted JobCompletionEventEventType = "job.benefit_register.completed" - JobCompletionEventEventTypeJobBenefitUnenrollCompleted JobCompletionEventEventType = "job.benefit_unenroll.completed" - JobCompletionEventEventTypeJobBenefitUpdateCompleted JobCompletionEventEventType = "job.benefit_update.completed" - JobCompletionEventEventTypeJobDataSyncAllCompleted JobCompletionEventEventType = "job.data_sync_all.completed" + JobCompletionEventEventTypeJobBenefitCreateCompleted JobCompletionEventEventType = "job.benefit_create.completed" + JobCompletionEventEventTypeJobBenefitEnrollCompleted JobCompletionEventEventType = "job.benefit_enroll.completed" + JobCompletionEventEventTypeJobBenefitRegisterCompleted JobCompletionEventEventType = "job.benefit_register.completed" + JobCompletionEventEventTypeJobBenefitUnenrollCompleted JobCompletionEventEventType = "job.benefit_unenroll.completed" + JobCompletionEventEventTypeJobBenefitUpdateCompleted JobCompletionEventEventType = "job.benefit_update.completed" + JobCompletionEventEventTypeJobDataSyncAllCompleted JobCompletionEventEventType = "job.data_sync_all.completed" + JobCompletionEventEventTypeJobW4FormEmployeeSyncCompleted JobCompletionEventEventType = "job.w4_form_employee_sync.completed" + JobCompletionEventEventTypeJobInitialDataSyncOrgSucceeded JobCompletionEventEventType = "job.initial_data_sync_org.succeeded" + JobCompletionEventEventTypeJobInitialDataSyncPayrollSucceeded JobCompletionEventEventType = "job.initial_data_sync_payroll.succeeded" ) func (r JobCompletionEventEventType) IsKnown() bool { switch r { - case JobCompletionEventEventTypeJobBenefitCreateCompleted, JobCompletionEventEventTypeJobBenefitEnrollCompleted, JobCompletionEventEventTypeJobBenefitRegisterCompleted, JobCompletionEventEventTypeJobBenefitUnenrollCompleted, JobCompletionEventEventTypeJobBenefitUpdateCompleted, JobCompletionEventEventTypeJobDataSyncAllCompleted: + case JobCompletionEventEventTypeJobBenefitCreateCompleted, JobCompletionEventEventTypeJobBenefitEnrollCompleted, JobCompletionEventEventTypeJobBenefitRegisterCompleted, JobCompletionEventEventTypeJobBenefitUnenrollCompleted, JobCompletionEventEventTypeJobBenefitUpdateCompleted, JobCompletionEventEventTypeJobDataSyncAllCompleted, JobCompletionEventEventTypeJobW4FormEmployeeSyncCompleted, JobCompletionEventEventTypeJobInitialDataSyncOrgSucceeded, JobCompletionEventEventTypeJobInitialDataSyncPayrollSucceeded: return true } return false @@ -1567,9 +1573,9 @@ func (r PaymentEvent) implementsWebhookEventUnion() {} type PaymentEventData struct { // The date of the payment. - PayDate string `json:"pay_date,required"` + PayDate string `json:"pay_date" api:"required"` // The ID of the payment. - PaymentID string `json:"payment_id,required"` + PaymentID string `json:"payment_id" api:"required"` JSON paymentEventDataJSON `json:"-"` } @@ -1652,6 +1658,21 @@ func init() { Type: reflect.TypeOf(JobCompletionEvent{}), DiscriminatorValue: "job.data_sync_all.completed", }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(JobCompletionEvent{}), + DiscriminatorValue: "job.w4_form_employee_sync.completed", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(JobCompletionEvent{}), + DiscriminatorValue: "job.initial_data_sync_org.succeeded", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(JobCompletionEvent{}), + DiscriminatorValue: "job.initial_data_sync_payroll.succeeded", + }, apijson.UnionVariant{ TypeFilter: gjson.JSON, Type: reflect.TypeOf(CompanyEvent{}),