diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml new file mode 100644 index 0000000..8dec9f0 --- /dev/null +++ b/.github/workflows/release-doctor.yml @@ -0,0 +1,19 @@ +name: Release Doctor +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + release_doctor: + name: release doctor + runs-on: ubuntu-latest + if: github.repository == 'CASParser/cas-parser-php' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + + steps: + - uses: actions/checkout@v4 + + - name: Check release environment + run: | + bash ./bin/check-release-environment diff --git a/.gitignore b/.gitignore index 70d76f1..6739884 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ -*.swo -*.swp +composer.lock +docs/ .idea/ .php-cs-fixer.cache .php-cs-fixer.php -.phpdoc/ .phpunit.cache -composer.lock phpunit.xml playground/ +*.swo +*.swp vendor/ diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 1e5b181..a2b062b 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -7,5 +7,11 @@ return (new Config()) ->setParallelConfig(ParallelConfigFactory::detect()) ->setFinder(Finder::create()->in([__DIR__.'/src', __DIR__.'/tests'])) - ->setRules(['@PhpCsFixer' => true, 'phpdoc_align' => false, 'new_with_parentheses' => ['named_class' => false]]) + ->setRules([ + '@PhpCsFixer' => true, + 'phpdoc_align' => false, + 'new_with_parentheses' => ['named_class' => false], + 'ordered_types' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + ]) ; diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c7159c1..3d2ac0b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.2" + ".": "0.1.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 334efb5..3a8e525 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,56 @@ # Changelog +## 0.1.0 (2025-09-03) + +Full Changelog: [v0.0.2...v0.1.0](https://github.com/CASParser/cas-parser-php/compare/v0.0.2...v0.1.0) + +### ⚠ BREAKING CHANGES + +* use builders for RequestOptions +* rename errors to exceptions +* pagination field rename, and basic streaming docs +* **refactor:** namespacing cleanup +* **refactor:** clean up pagination, errors, as well as request methods + +### Features + +* **client:** add streaming ([8a3649a](https://github.com/CASParser/cas-parser-php/commit/8a3649ac38d283238cb78f183d88388d2220350f)) +* **client:** improve error handling ([b8969bb](https://github.com/CASParser/cas-parser-php/commit/b8969bb06c1b5c4575ad2f0a25af16f2bb0f5c5f)) +* **client:** use named parameters in methods ([f977b9a](https://github.com/CASParser/cas-parser-php/commit/f977b9a00b4f72c4d0add8e637baf699339b3707)) +* ensure `->toArray()` benefits from structural typing ([16e7c95](https://github.com/CASParser/cas-parser-php/commit/16e7c9593a3216a1af73bd62b8c3d9561d2a05ad)) +* pagination field rename, and basic streaming docs ([961e540](https://github.com/CASParser/cas-parser-php/commit/961e54000c4f7e50b2c2bdbed82ac08b4862450f)) +* **php:** differentiate null and omit ([d5736cf](https://github.com/CASParser/cas-parser-php/commit/d5736cf656dd266165e262c6fe85a6bc0a12e5d9)) +* **php:** rename internal types ([d8b47a2](https://github.com/CASParser/cas-parser-php/commit/d8b47a27256c7429058623b48624698804778320)) +* **refactor:** clean up pagination, errors, as well as request methods ([26437b5](https://github.com/CASParser/cas-parser-php/commit/26437b521827b8cf5393feee1e8af173e18d7a22)) +* **refactor:** namespacing cleanup ([666f374](https://github.com/CASParser/cas-parser-php/commit/666f37473888855fcc884cb9d851d43ce62aa75f)) +* rename errors to exceptions ([92d9817](https://github.com/CASParser/cas-parser-php/commit/92d9817bf28718bfe5ecc6e61f6ed42abe4b0d4b)) +* use builders for RequestOptions ([7f3730e](https://github.com/CASParser/cas-parser-php/commit/7f3730ee1d2081c219209b4cf06a9568af6b2559)) + + +### Bug Fixes + +* add create release workflow ([11358ea](https://github.com/CASParser/cas-parser-php/commit/11358ea756b190e7c2f459311f409f7ec6ddd255)) +* basic pagination should work ([041e76c](https://github.com/CASParser/cas-parser-php/commit/041e76c9f5db339bd9834e609058b3b4edc865d3)) +* **client:** elide null named parameters ([47e2824](https://github.com/CASParser/cas-parser-php/commit/47e28244783dd47d03f095cf0015aa947c0db5b8)) +* minor bugs ([2e13643](https://github.com/CASParser/cas-parser-php/commit/2e13643e201a80bfb8d1ffbeea643133cb79701e)) +* remove inaccurate `license` field in composer.json ([86ba48b](https://github.com/CASParser/cas-parser-php/commit/86ba48b8fc66042665d86362545915504f04011e)) +* streaming internals ([39bcd29](https://github.com/CASParser/cas-parser-php/commit/39bcd29aa689ba5505428c732b63d07fc049c010)) + + +### Chores + +* add additional php doc tags ([4403319](https://github.com/CASParser/cas-parser-php/commit/44033196dc6699b911b64f473f95badc0f548c71)) +* improve model annotations ([b45934c](https://github.com/CASParser/cas-parser-php/commit/b45934c91c079bc8eff6a3cfb53e8adab8927034)) +* **internal:** refactor base client internals ([36dc0b6](https://github.com/CASParser/cas-parser-php/commit/36dc0b68f8706fe3bd5fcbb7f57c5487cbea2496)) +* **internal:** refactored internal codepaths ([b306fa4](https://github.com/CASParser/cas-parser-php/commit/b306fa456c045ddd061225257fd4490e04099f12)) +* intuitively order union types ([150660a](https://github.com/CASParser/cas-parser-php/commit/150660ae58f257d5faaa6f8cf7c4f9093e0bae2c)) +* readme improvements ([ac66540](https://github.com/CASParser/cas-parser-php/commit/ac665402fa3edc84c13de25248d0b901e54aaab5)) +* refactor request options ([f1b303a](https://github.com/CASParser/cas-parser-php/commit/f1b303add63f19005284bec65d3b5907d8f34372)) +* **refactor:** simplify base page interface ([95b27e8](https://github.com/CASParser/cas-parser-php/commit/95b27e82468e3026ab7c6adad0af449ef2dd9355)) +* remove `php-http/multipart-stream-builder` as a required dependency ([607f49a](https://github.com/CASParser/cas-parser-php/commit/607f49a7bfb66fc667c15ced3baaa556e1b689ab)) +* remove type aliases ([ff49892](https://github.com/CASParser/cas-parser-php/commit/ff4989246f4c361a99aa1240db0b7c956fa5161d)) +* simplify model initialization ([f38ec08](https://github.com/CASParser/cas-parser-php/commit/f38ec08395db6f5fcf1c8a8e2ccae49cd0e4537a)) + ## 0.0.2 (2025-08-18) Full Changelog: [v0.0.1...v0.0.2](https://github.com/CASParser/cas-parser-php/compare/v0.0.1...v0.0.2) diff --git a/README.md b/README.md index eb8a46a..75f8882 100644 --- a/README.md +++ b/README.md @@ -39,63 +39,65 @@ To use this package, install via Composer by adding the following to your applic ## Usage +This library uses named parameters to specify optional arguments. +Parameters with a default value must be set by name. + ```php casParser->smartParse($params); +$unifiedResponse = $client->casParser->smartParse(); var_dump($unifiedResponse->demat_accounts); ``` +### Value Objects + +It is recommended to use the static `with` constructor `Dog::with(name: "Joey")` +and named parameters to initialize value objects. + +However, builders are also provided `(new Dog)->withName("Joey")`. + ### Handling errors -When the library is unable to connect to the API, or if the API returns a non-success status code (i.e., 4xx or 5xx response), a subclass of `CasParser\Errors\APIError` will be thrown: +When the library is unable to connect to the API, or if the API returns a non-success status code (i.e., 4xx or 5xx response), a subclass of `CasParser\Core\Exceptions\APIException` will be thrown: ```php casParser->smartParse($params); -} catch (APIConnectionError $e) { - echo "The server could not be reached", PHP_EOL; - var_dump($e->getPrevious()); + $unifiedResponse = $client->casParser->smartParse(); +} catch (APIConnectionException $e) { + echo "The server could not be reached", PHP_EOL; + var_dump($e->getPrevious()); } catch (RateLimitError $_) { - echo "A 429 status code was received; we should back off a bit.", PHP_EOL; + echo "A 429 status code was received; we should back off a bit.", PHP_EOL; } catch (APIStatusError $e) { - echo "Another non-200-range status code was received", PHP_EOL; - var_dump($e->status); + echo "Another non-200-range status code was received", PHP_EOL; + echo $e->getMessage(); } ``` Error codes are as follows: -| Cause | Error Type | -| ---------------- | -------------------------- | -| HTTP 400 | `BadRequestError` | -| HTTP 401 | `AuthenticationError` | -| HTTP 403 | `PermissionDeniedError` | -| HTTP 404 | `NotFoundError` | -| HTTP 409 | `ConflictError` | -| HTTP 422 | `UnprocessableEntityError` | -| HTTP 429 | `RateLimitError` | -| HTTP >= 500 | `InternalServerError` | -| Other HTTP error | `APIStatusError` | -| Timeout | `APITimeoutError` | -| Network error | `APIConnectionError` | +| Cause | Error Type | +| ---------------- | ------------------------------ | +| HTTP 400 | `BadRequestException` | +| HTTP 401 | `AuthenticationException` | +| HTTP 403 | `PermissionDeniedException` | +| HTTP 404 | `NotFoundException` | +| HTTP 409 | `ConflictException` | +| HTTP 422 | `UnprocessableEntityException` | +| HTTP 429 | `RateLimitException` | +| HTTP >= 500 | `InternalServerException` | +| Other HTTP error | `APIStatusException` | +| Timeout | `APITimeoutException` | +| Network error | `APIConnectionException` | ### Retries @@ -103,24 +105,22 @@ Certain errors will be automatically retried 2 times by default, with a short ex Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit, >=500 Internal errors, and timeouts will all be retried by default. -You can use the `max_retries` option to configure or disable this: +You can use the `maxRetries` option to configure or disable this: ```php casParser - ->smartParse($params, new RequestOptions(maxRetries: 5)); +// Or, configure per-request: + +$result = $client->casParser->smartParse( + requestOptions: RequestOptions::with(maxRetries: 5) +); ``` ## Advanced concepts @@ -131,22 +131,15 @@ $params = CasParserSmartParseParams::with( You can send undocumented parameters to any endpoint, and read undocumented response properties, like so: -Note: the `extra_` parameters of the same name overrides the documented parameters. +Note: the `extra*` parameters of the same name overrides the documented parameters. ```php casParser - ->smartParse( - $params, - new RequestOptions( +$unifiedResponse = $client->casParser->smartParse( + requestOptions: RequestOptions::with( extraQueryParams: ["my_query_parameter" => "value"], extraBodyParams: ["my_body_parameter" => "value"], extraHeaders: ["my-header" => "value"], diff --git a/bin/check-release-environment b/bin/check-release-environment new file mode 100644 index 0000000..cf571b6 --- /dev/null +++ b/bin/check-release-environment @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +errors=() + +if [ -z "${PACKAGIST_USERNAME}" ]; then + errors+=("The PACKAGIST_USERNAME secret has not been set. Please set it in either this repository's secrets or your organization secrets") +fi + +if [ -z "${PACKAGIST_SAFE_KEY}" ]; then + errors+=("The PACKAGIST_SAFE_KEY secret has not been set. Please set it in either this repository's secrets or your organization secrets") +fi + +lenErrors=${#errors[@]} + +if [[ lenErrors -gt 0 ]]; then + echo -e "Found the following errors in the release environment:\n" + + for error in "${errors[@]}"; do + echo -e "- $error\n" + done + + exit 1 +fi + +echo "The environment is ready to push releases!" diff --git a/composer.json b/composer.json index 77eee58..7163498 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,7 @@ { "$schema": "https://getcomposer.org/schema.json", "autoload": { - "files": [ - "src/Client.php" - ], + "files": ["src/Core.php", "src/Client.php"], "psr-4": { "CasParser\\": "src/" } @@ -25,13 +23,11 @@ "preferred-install": "dist", "sort-packages": true }, - "license": "APACHE-2.0", "description": "Cas Parser PHP SDK", "name": "org-placeholder/cas-parser", "require": { "php": "^8.1", "php-http/discovery": "^1", - "php-http/multipart-stream-builder": "^1", "psr/http-client": "^1", "psr/http-client-implementation": "^1", "psr/http-factory-implementation": "^1", @@ -48,6 +44,7 @@ "symfony/http-client": "^7" }, "scripts": { + "build:docs": "curl --etag-save ./vendor/ag.etags --etag-compare ./vendor/ag.etags --create-dirs --remote-name --output-dir ./vendor/bin --no-progress-meter -- https://github.com/ApiGen/ApiGen/releases/latest/download/apigen.phar && php ./vendor/bin/apigen.phar --output docs -- src", "lint": "./scripts/lint", "test": "./scripts/test" } diff --git a/src/CasGenerator/CasGeneratorGenerateCasParams.php b/src/CasGenerator/CasGeneratorGenerateCasParams.php index 5de37b0..f8f5c20 100644 --- a/src/CasGenerator/CasGeneratorGenerateCasParams.php +++ b/src/CasGenerator/CasGeneratorGenerateCasParams.php @@ -6,15 +6,17 @@ use CasParser\CasGenerator\CasGeneratorGenerateCasParams\CasAuthority; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; -use CasParser\Core\Concerns\Params; +use CasParser\Core\Concerns\SdkModel; +use CasParser\Core\Concerns\SdkParams; use CasParser\Core\Contracts\BaseModel; /** * This endpoint generates CAS (Consolidated Account Statement) documents by submitting a mailback request to the specified CAS authority. * Currently only supports KFintech, with plans to support CAMS, CDSL, and NSDL in the future. * - * @phpstan-type generate_cas_params = array{ + * @see CasParser\CasGenerator->generateCas + * + * @phpstan-type cas_generator_generate_cas_params = array{ * email: string, * fromDate: string, * password: string, @@ -25,8 +27,9 @@ */ final class CasGeneratorGenerateCasParams implements BaseModel { - use Model; - use Params; + /** @use SdkModel */ + use SdkModel; + use SdkParams; /** * Email address to receive the CAS document. @@ -55,7 +58,7 @@ final class CasGeneratorGenerateCasParams implements BaseModel /** * CAS authority to generate the document from (currently only kfintech is supported). * - * @var null|CasAuthority::* $casAuthority + * @var CasAuthority::*|null $casAuthority */ #[Api('cas_authority', enum: CasAuthority::class, optional: true)] public ?string $casAuthority; @@ -88,8 +91,7 @@ final class CasGeneratorGenerateCasParams implements BaseModel */ public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -97,7 +99,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|CasAuthority::* $casAuthority + * @param CasAuthority::* $casAuthority */ public static function with( string $email, diff --git a/src/CasGenerator/CasGeneratorGenerateCasParams/CasAuthority.php b/src/CasGenerator/CasGeneratorGenerateCasParams/CasAuthority.php index 160ff36..8c5f709 100644 --- a/src/CasGenerator/CasGeneratorGenerateCasParams/CasAuthority.php +++ b/src/CasGenerator/CasGeneratorGenerateCasParams/CasAuthority.php @@ -4,17 +4,15 @@ namespace CasParser\CasGenerator\CasGeneratorGenerateCasParams; -use CasParser\Core\Concerns\Enum; +use CasParser\Core\Concerns\SdkEnum; use CasParser\Core\Conversion\Contracts\ConverterSource; /** * CAS authority to generate the document from (currently only kfintech is supported). - * - * @phpstan-type cas_authority_alias = CasAuthority::* */ final class CasAuthority implements ConverterSource { - use Enum; + use SdkEnum; public const KFINTECH = 'kfintech'; diff --git a/src/Responses/CasGenerator/CasGeneratorGenerateCasResponse.php b/src/CasGenerator/CasGeneratorGenerateCasResponse.php similarity index 71% rename from src/Responses/CasGenerator/CasGeneratorGenerateCasResponse.php rename to src/CasGenerator/CasGeneratorGenerateCasResponse.php index d960ad6..97e841e 100644 --- a/src/Responses/CasGenerator/CasGeneratorGenerateCasResponse.php +++ b/src/CasGenerator/CasGeneratorGenerateCasResponse.php @@ -2,20 +2,21 @@ declare(strict_types=1); -namespace CasParser\Responses\CasGenerator; +namespace CasParser\CasGenerator; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** - * @phpstan-type cas_generator_generate_cas_response_alias = array{ - * msg?: string, status?: string + * @phpstan-type cas_generator_generate_cas_response = array{ + * msg?: string|null, status?: string|null * } */ final class CasGeneratorGenerateCasResponse implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; #[Api(optional: true)] public ?string $msg; @@ -25,8 +26,7 @@ final class CasGeneratorGenerateCasResponse implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -34,10 +34,8 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. */ - public static function with( - ?string $msg = null, - ?string $status = null - ): self { + public static function with(?string $msg = null, ?string $status = null): self + { $obj = new self; null !== $msg && $obj->msg = $msg; diff --git a/src/CasGenerator/CasGeneratorService.php b/src/CasGenerator/CasGeneratorService.php deleted file mode 100644 index e5edde5..0000000 --- a/src/CasGenerator/CasGeneratorService.php +++ /dev/null @@ -1,52 +0,0 @@ -client->request( - method: 'post', - path: 'v4/generate', - body: (object) $parsed, - options: $options, - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce( - CasGeneratorGenerateCasResponse::class, - value: $resp - ); - } -} diff --git a/src/CasParser/CasParserCamsKfintechParams.php b/src/CasParser/CasParserCamsKfintechParams.php index dc45ccd..0dda13d 100644 --- a/src/CasParser/CasParserCamsKfintechParams.php +++ b/src/CasParser/CasParserCamsKfintechParams.php @@ -5,22 +5,25 @@ namespace CasParser\CasParser; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; -use CasParser\Core\Concerns\Params; +use CasParser\Core\Concerns\SdkModel; +use CasParser\Core\Concerns\SdkParams; use CasParser\Core\Contracts\BaseModel; /** * This endpoint specifically parses CAMS/KFintech CAS (Consolidated Account Statement) PDF files and returns data in a unified format. * Use this endpoint when you know the PDF is from CAMS or KFintech. * - * @phpstan-type cams_kfintech_params = array{ + * @see CasParser\CasParser->camsKfintech + * + * @phpstan-type cas_parser_cams_kfintech_params = array{ * password?: string, pdfFile?: string, pdfURL?: string * } */ final class CasParserCamsKfintechParams implements BaseModel { - use Model; - use Params; + /** @use SdkModel */ + use SdkModel; + use SdkParams; /** * Password for the PDF file (if required). @@ -42,8 +45,7 @@ final class CasParserCamsKfintechParams implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/CasParserCdslParams.php b/src/CasParser/CasParserCdslParams.php index 7496a29..c8401d7 100644 --- a/src/CasParser/CasParserCdslParams.php +++ b/src/CasParser/CasParserCdslParams.php @@ -5,22 +5,25 @@ namespace CasParser\CasParser; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; -use CasParser\Core\Concerns\Params; +use CasParser\Core\Concerns\SdkModel; +use CasParser\Core\Concerns\SdkParams; use CasParser\Core\Contracts\BaseModel; /** * This endpoint specifically parses CDSL CAS (Consolidated Account Statement) PDF files and returns data in a unified format. * Use this endpoint when you know the PDF is from CDSL. * - * @phpstan-type cdsl_params = array{ + * @see CasParser\CasParser->cdsl + * + * @phpstan-type cas_parser_cdsl_params = array{ * password?: string, pdfFile?: string, pdfURL?: string * } */ final class CasParserCdslParams implements BaseModel { - use Model; - use Params; + /** @use SdkModel */ + use SdkModel; + use SdkParams; /** * Password for the PDF file (if required). @@ -42,8 +45,7 @@ final class CasParserCdslParams implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/CasParserNsdlParams.php b/src/CasParser/CasParserNsdlParams.php index 9c782fe..df6c11a 100644 --- a/src/CasParser/CasParserNsdlParams.php +++ b/src/CasParser/CasParserNsdlParams.php @@ -5,22 +5,25 @@ namespace CasParser\CasParser; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; -use CasParser\Core\Concerns\Params; +use CasParser\Core\Concerns\SdkModel; +use CasParser\Core\Concerns\SdkParams; use CasParser\Core\Contracts\BaseModel; /** * This endpoint specifically parses NSDL CAS (Consolidated Account Statement) PDF files and returns data in a unified format. * Use this endpoint when you know the PDF is from NSDL. * - * @phpstan-type nsdl_params = array{ + * @see CasParser\CasParser->nsdl + * + * @phpstan-type cas_parser_nsdl_params = array{ * password?: string, pdfFile?: string, pdfURL?: string * } */ final class CasParserNsdlParams implements BaseModel { - use Model; - use Params; + /** @use SdkModel */ + use SdkModel; + use SdkParams; /** * Password for the PDF file (if required). @@ -42,8 +45,7 @@ final class CasParserNsdlParams implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/CasParserSmartParseParams.php b/src/CasParser/CasParserSmartParseParams.php index 6626658..5596cf1 100644 --- a/src/CasParser/CasParserSmartParseParams.php +++ b/src/CasParser/CasParserSmartParseParams.php @@ -5,22 +5,25 @@ namespace CasParser\CasParser; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; -use CasParser\Core\Concerns\Params; +use CasParser\Core\Concerns\SdkModel; +use CasParser\Core\Concerns\SdkParams; use CasParser\Core\Contracts\BaseModel; /** * This endpoint parses CAS (Consolidated Account Statement) PDF files from NSDL, CDSL, or CAMS/KFintech and returns data in a unified format. * It auto-detects the CAS type and transforms the data into a consistent structure regardless of the source. * - * @phpstan-type smart_parse_params = array{ + * @see CasParser\CasParser->smartParse + * + * @phpstan-type cas_parser_smart_parse_params = array{ * password?: string, pdfFile?: string, pdfURL?: string * } */ final class CasParserSmartParseParams implements BaseModel { - use Model; - use Params; + /** @use SdkModel */ + use SdkModel; + use SdkParams; /** * Password for the PDF file (if required). @@ -42,8 +45,7 @@ final class CasParserSmartParseParams implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse.php b/src/CasParser/UnifiedResponse.php index b5736a5..2e0c131 100644 --- a/src/CasParser/UnifiedResponse.php +++ b/src/CasParser/UnifiedResponse.php @@ -11,30 +11,26 @@ use CasParser\CasParser\UnifiedResponse\MutualFund; use CasParser\CasParser\UnifiedResponse\Summary; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; -use CasParser\Core\Conversion\ListOf; /** - * @phpstan-type unified_response_alias = array{ - * dematAccounts?: list, - * insurance?: Insurance, - * investor?: Investor, - * meta?: Meta, - * mutualFunds?: list, - * summary?: Summary, + * @phpstan-type unified_response = array{ + * dematAccounts?: list|null, + * insurance?: Insurance|null, + * investor?: Investor|null, + * meta?: Meta|null, + * mutualFunds?: list|null, + * summary?: Summary|null, * } */ final class UnifiedResponse implements BaseModel { - use Model; - - /** @var null|list $dematAccounts */ - #[Api( - 'demat_accounts', - type: new ListOf(DematAccount::class), - optional: true - )] + /** @use SdkModel */ + use SdkModel; + + /** @var list|null $dematAccounts */ + #[Api('demat_accounts', list: DematAccount::class, optional: true)] public ?array $dematAccounts; #[Api(optional: true)] @@ -46,8 +42,8 @@ final class UnifiedResponse implements BaseModel #[Api(optional: true)] public ?Meta $meta; - /** @var null|list $mutualFunds */ - #[Api('mutual_funds', type: new ListOf(MutualFund::class), optional: true)] + /** @var list|null $mutualFunds */ + #[Api('mutual_funds', list: MutualFund::class, optional: true)] public ?array $mutualFunds; #[Api(optional: true)] @@ -55,8 +51,7 @@ final class UnifiedResponse implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -64,8 +59,8 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|list $dematAccounts - * @param null|list $mutualFunds + * @param list $dematAccounts + * @param list $mutualFunds */ public static function with( ?array $dematAccounts = null, diff --git a/src/CasParser/UnifiedResponse/DematAccount.php b/src/CasParser/UnifiedResponse/DematAccount.php index 055c941..1833505 100644 --- a/src/CasParser/UnifiedResponse/DematAccount.php +++ b/src/CasParser/UnifiedResponse/DematAccount.php @@ -8,24 +8,25 @@ use CasParser\CasParser\UnifiedResponse\DematAccount\DematType; use CasParser\CasParser\UnifiedResponse\DematAccount\Holdings; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** - * @phpstan-type demat_account_alias = array{ - * additionalInfo?: AdditionalInfo, - * boID?: string, - * clientID?: string, - * dematType?: DematType::*, - * dpID?: string, - * dpName?: string, - * holdings?: Holdings, - * value?: float, + * @phpstan-type demat_account = array{ + * additionalInfo?: AdditionalInfo|null, + * boID?: string|null, + * clientID?: string|null, + * dematType?: DematType::*|null, + * dpID?: string|null, + * dpName?: string|null, + * holdings?: Holdings|null, + * value?: float|null, * } */ final class DematAccount implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Additional information specific to the demat account type. @@ -48,7 +49,7 @@ final class DematAccount implements BaseModel /** * Type of demat account. * - * @var null|DematType::* $dematType + * @var DematType::*|null $dematType */ #[Api('demat_type', enum: DematType::class, optional: true)] public ?string $dematType; @@ -76,8 +77,7 @@ final class DematAccount implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -85,7 +85,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|DematType::* $dematType + * @param DematType::* $dematType */ public static function with( ?AdditionalInfo $additionalInfo = null, diff --git a/src/CasParser/UnifiedResponse/DematAccount/AdditionalInfo.php b/src/CasParser/UnifiedResponse/DematAccount/AdditionalInfo.php index 41c11da..90b1ca7 100644 --- a/src/CasParser/UnifiedResponse/DematAccount/AdditionalInfo.php +++ b/src/CasParser/UnifiedResponse/DematAccount/AdditionalInfo.php @@ -5,27 +5,27 @@ namespace CasParser\CasParser\UnifiedResponse\DematAccount; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; -use CasParser\Core\Conversion\ListOf; /** * Additional information specific to the demat account type. * - * @phpstan-type additional_info_alias = array{ - * boStatus?: string, - * boSubStatus?: string, - * boType?: string, - * bsda?: string, - * email?: string, - * linkedPans?: list, - * nominee?: string, - * status?: string, + * @phpstan-type additional_info = array{ + * boStatus?: string|null, + * boSubStatus?: string|null, + * boType?: string|null, + * bsda?: string|null, + * email?: string|null, + * linkedPans?: list|null, + * nominee?: string|null, + * status?: string|null, * } */ final class AdditionalInfo implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Beneficiary Owner status (CDSL). @@ -60,9 +60,9 @@ final class AdditionalInfo implements BaseModel /** * List of linked PAN numbers (NSDL). * - * @var null|list $linkedPans + * @var list|null $linkedPans */ - #[Api('linked_pans', type: new ListOf('string'), optional: true)] + #[Api('linked_pans', list: 'string', optional: true)] public ?array $linkedPans; /** @@ -79,8 +79,7 @@ final class AdditionalInfo implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -88,7 +87,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|list $linkedPans + * @param list $linkedPans */ public static function with( ?string $boStatus = null, diff --git a/src/CasParser/UnifiedResponse/DematAccount/DematType.php b/src/CasParser/UnifiedResponse/DematAccount/DematType.php index 441209e..0cc1284 100644 --- a/src/CasParser/UnifiedResponse/DematAccount/DematType.php +++ b/src/CasParser/UnifiedResponse/DematAccount/DematType.php @@ -4,17 +4,15 @@ namespace CasParser\CasParser\UnifiedResponse\DematAccount; -use CasParser\Core\Concerns\Enum; +use CasParser\Core\Concerns\SdkEnum; use CasParser\Core\Conversion\Contracts\ConverterSource; /** * Type of demat account. - * - * @phpstan-type demat_type_alias = DematType::* */ final class DematType implements ConverterSource { - use Enum; + use SdkEnum; public const NSDL = 'NSDL'; diff --git a/src/CasParser/UnifiedResponse/DematAccount/Holdings.php b/src/CasParser/UnifiedResponse/DematAccount/Holdings.php index 61a4d5c..f942fef 100644 --- a/src/CasParser/UnifiedResponse/DematAccount/Holdings.php +++ b/src/CasParser/UnifiedResponse/DematAccount/Holdings.php @@ -10,59 +10,50 @@ use CasParser\CasParser\UnifiedResponse\DematAccount\Holdings\Equity; use CasParser\CasParser\UnifiedResponse\DematAccount\Holdings\GovernmentSecurity; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; -use CasParser\Core\Conversion\ListOf; /** * @phpstan-type holdings_alias = array{ - * aifs?: list, - * corporateBonds?: list, - * dematMutualFunds?: list, - * equities?: list, - * governmentSecurities?: list, + * aifs?: list|null, + * corporateBonds?: list|null, + * dematMutualFunds?: list|null, + * equities?: list|null, + * governmentSecurities?: list|null, * } */ final class Holdings implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; - /** @var null|list $aifs */ - #[Api(type: new ListOf(Aif::class), optional: true)] + /** @var list|null $aifs */ + #[Api(list: Aif::class, optional: true)] public ?array $aifs; - /** @var null|list $corporateBonds */ - #[Api( - 'corporate_bonds', - type: new ListOf(CorporateBond::class), - optional: true - )] + /** @var list|null $corporateBonds */ + #[Api('corporate_bonds', list: CorporateBond::class, optional: true)] public ?array $corporateBonds; - /** @var null|list $dematMutualFunds */ - #[Api( - 'demat_mutual_funds', - type: new ListOf(DematMutualFund::class), - optional: true, - )] + /** @var list|null $dematMutualFunds */ + #[Api('demat_mutual_funds', list: DematMutualFund::class, optional: true)] public ?array $dematMutualFunds; - /** @var null|list $equities */ - #[Api(type: new ListOf(Equity::class), optional: true)] + /** @var list|null $equities */ + #[Api(list: Equity::class, optional: true)] public ?array $equities; - /** @var null|list $governmentSecurities */ + /** @var list|null $governmentSecurities */ #[Api( 'government_securities', - type: new ListOf(GovernmentSecurity::class), - optional: true, + list: GovernmentSecurity::class, + optional: true )] public ?array $governmentSecurities; public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -70,11 +61,11 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|list $aifs - * @param null|list $corporateBonds - * @param null|list $dematMutualFunds - * @param null|list $equities - * @param null|list $governmentSecurities + * @param list $aifs + * @param list $corporateBonds + * @param list $dematMutualFunds + * @param list $equities + * @param list $governmentSecurities */ public static function with( ?array $aifs = null, diff --git a/src/CasParser/UnifiedResponse/DematAccount/Holdings/Aif.php b/src/CasParser/UnifiedResponse/DematAccount/Holdings/Aif.php index cda73f1..d893d44 100644 --- a/src/CasParser/UnifiedResponse/DematAccount/Holdings/Aif.php +++ b/src/CasParser/UnifiedResponse/DematAccount/Holdings/Aif.php @@ -5,21 +5,22 @@ namespace CasParser\CasParser\UnifiedResponse\DematAccount\Holdings; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** * @phpstan-type aif_alias = array{ * additionalInfo?: mixed, - * isin?: string, - * name?: string, - * units?: float, - * value?: float, + * isin?: string|null, + * name?: string|null, + * units?: float|null, + * value?: float|null, * } */ final class Aif implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Additional information specific to the AIF. @@ -53,8 +54,7 @@ final class Aif implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/DematAccount/Holdings/CorporateBond.php b/src/CasParser/UnifiedResponse/DematAccount/Holdings/CorporateBond.php index 2d3da3e..2f86609 100644 --- a/src/CasParser/UnifiedResponse/DematAccount/Holdings/CorporateBond.php +++ b/src/CasParser/UnifiedResponse/DematAccount/Holdings/CorporateBond.php @@ -5,21 +5,22 @@ namespace CasParser\CasParser\UnifiedResponse\DematAccount\Holdings; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** - * @phpstan-type corporate_bond_alias = array{ + * @phpstan-type corporate_bond = array{ * additionalInfo?: mixed, - * isin?: string, - * name?: string, - * units?: float, - * value?: float, + * isin?: string|null, + * name?: string|null, + * units?: float|null, + * value?: float|null, * } */ final class CorporateBond implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Additional information specific to the corporate bond. @@ -53,8 +54,7 @@ final class CorporateBond implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/DematAccount/Holdings/DematMutualFund.php b/src/CasParser/UnifiedResponse/DematAccount/Holdings/DematMutualFund.php index bc52212..3d14ad3 100644 --- a/src/CasParser/UnifiedResponse/DematAccount/Holdings/DematMutualFund.php +++ b/src/CasParser/UnifiedResponse/DematAccount/Holdings/DematMutualFund.php @@ -5,21 +5,22 @@ namespace CasParser\CasParser\UnifiedResponse\DematAccount\Holdings; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** - * @phpstan-type demat_mutual_fund_alias = array{ + * @phpstan-type demat_mutual_fund = array{ * additionalInfo?: mixed, - * isin?: string, - * name?: string, - * units?: float, - * value?: float, + * isin?: string|null, + * name?: string|null, + * units?: float|null, + * value?: float|null, * } */ final class DematMutualFund implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Additional information specific to the mutual fund. @@ -53,8 +54,7 @@ final class DematMutualFund implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/DematAccount/Holdings/Equity.php b/src/CasParser/UnifiedResponse/DematAccount/Holdings/Equity.php index b2bf602..771f7ec 100644 --- a/src/CasParser/UnifiedResponse/DematAccount/Holdings/Equity.php +++ b/src/CasParser/UnifiedResponse/DematAccount/Holdings/Equity.php @@ -5,21 +5,22 @@ namespace CasParser\CasParser\UnifiedResponse\DematAccount\Holdings; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** * @phpstan-type equity_alias = array{ * additionalInfo?: mixed, - * isin?: string, - * name?: string, - * units?: float, - * value?: float, + * isin?: string|null, + * name?: string|null, + * units?: float|null, + * value?: float|null, * } */ final class Equity implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Additional information specific to the equity. @@ -53,8 +54,7 @@ final class Equity implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/DematAccount/Holdings/GovernmentSecurity.php b/src/CasParser/UnifiedResponse/DematAccount/Holdings/GovernmentSecurity.php index f2b0e1a..0755acd 100644 --- a/src/CasParser/UnifiedResponse/DematAccount/Holdings/GovernmentSecurity.php +++ b/src/CasParser/UnifiedResponse/DematAccount/Holdings/GovernmentSecurity.php @@ -5,21 +5,22 @@ namespace CasParser\CasParser\UnifiedResponse\DematAccount\Holdings; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** - * @phpstan-type government_security_alias = array{ + * @phpstan-type government_security = array{ * additionalInfo?: mixed, - * isin?: string, - * name?: string, - * units?: float, - * value?: float, + * isin?: string|null, + * name?: string|null, + * units?: float|null, + * value?: float|null, * } */ final class GovernmentSecurity implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Additional information specific to the government security. @@ -53,8 +54,7 @@ final class GovernmentSecurity implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/Insurance.php b/src/CasParser/UnifiedResponse/Insurance.php index 9e113a5..49d0ef6 100644 --- a/src/CasParser/UnifiedResponse/Insurance.php +++ b/src/CasParser/UnifiedResponse/Insurance.php @@ -6,31 +6,30 @@ use CasParser\CasParser\UnifiedResponse\Insurance\LifeInsurancePolicy; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; -use CasParser\Core\Conversion\ListOf; /** * @phpstan-type insurance_alias = array{ - * lifeInsurancePolicies?: list + * lifeInsurancePolicies?: list|null * } */ final class Insurance implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; - /** @var null|list $lifeInsurancePolicies */ + /** @var list|null $lifeInsurancePolicies */ #[Api( 'life_insurance_policies', - type: new ListOf(LifeInsurancePolicy::class), - optional: true, + list: LifeInsurancePolicy::class, + optional: true )] public ?array $lifeInsurancePolicies; public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -38,7 +37,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|list $lifeInsurancePolicies + * @param list $lifeInsurancePolicies */ public static function with(?array $lifeInsurancePolicies = null): self { diff --git a/src/CasParser/UnifiedResponse/Insurance/LifeInsurancePolicy.php b/src/CasParser/UnifiedResponse/Insurance/LifeInsurancePolicy.php index 872b340..58eaa3d 100644 --- a/src/CasParser/UnifiedResponse/Insurance/LifeInsurancePolicy.php +++ b/src/CasParser/UnifiedResponse/Insurance/LifeInsurancePolicy.php @@ -5,25 +5,26 @@ namespace CasParser\CasParser\UnifiedResponse\Insurance; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** - * @phpstan-type life_insurance_policy_alias = array{ + * @phpstan-type life_insurance_policy = array{ * additionalInfo?: mixed, - * lifeAssured?: string, - * policyName?: string, - * policyNumber?: string, - * premiumAmount?: float, - * premiumFrequency?: string, - * provider?: string, - * status?: string, - * sumAssured?: float, + * lifeAssured?: string|null, + * policyName?: string|null, + * policyNumber?: string|null, + * premiumAmount?: float|null, + * premiumFrequency?: string|null, + * provider?: string|null, + * status?: string|null, + * sumAssured?: float|null, * } */ final class LifeInsurancePolicy implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Additional information specific to the policy. @@ -81,8 +82,7 @@ final class LifeInsurancePolicy implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/Investor.php b/src/CasParser/UnifiedResponse/Investor.php index e74c610..e2befea 100644 --- a/src/CasParser/UnifiedResponse/Investor.php +++ b/src/CasParser/UnifiedResponse/Investor.php @@ -5,23 +5,24 @@ namespace CasParser\CasParser\UnifiedResponse; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** * @phpstan-type investor_alias = array{ - * address?: string, - * casID?: string, - * email?: string, - * mobile?: string, - * name?: string, - * pan?: string, - * pincode?: string, + * address?: string|null, + * casID?: string|null, + * email?: string|null, + * mobile?: string|null, + * name?: string|null, + * pan?: string|null, + * pincode?: string|null, * } */ final class Investor implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Address of the investor. @@ -67,8 +68,7 @@ final class Investor implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/Meta.php b/src/CasParser/UnifiedResponse/Meta.php index ca1b4b2..1187ef3 100644 --- a/src/CasParser/UnifiedResponse/Meta.php +++ b/src/CasParser/UnifiedResponse/Meta.php @@ -7,24 +7,25 @@ use CasParser\CasParser\UnifiedResponse\Meta\CasType; use CasParser\CasParser\UnifiedResponse\Meta\StatementPeriod; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** * @phpstan-type meta_alias = array{ - * casType?: CasType::*, - * generatedAt?: \DateTimeInterface, - * statementPeriod?: StatementPeriod, + * casType?: CasType::*|null, + * generatedAt?: \DateTimeInterface|null, + * statementPeriod?: StatementPeriod|null, * } */ final class Meta implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Type of CAS detected and processed. * - * @var null|CasType::* $casType + * @var CasType::*|null $casType */ #[Api('cas_type', enum: CasType::class, optional: true)] public ?string $casType; @@ -40,8 +41,7 @@ final class Meta implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -49,7 +49,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|CasType::* $casType + * @param CasType::* $casType */ public static function with( ?string $casType = null, diff --git a/src/CasParser/UnifiedResponse/Meta/CasType.php b/src/CasParser/UnifiedResponse/Meta/CasType.php index d6b39d6..6fc6c8e 100644 --- a/src/CasParser/UnifiedResponse/Meta/CasType.php +++ b/src/CasParser/UnifiedResponse/Meta/CasType.php @@ -4,17 +4,15 @@ namespace CasParser\CasParser\UnifiedResponse\Meta; -use CasParser\Core\Concerns\Enum; +use CasParser\Core\Concerns\SdkEnum; use CasParser\Core\Conversion\Contracts\ConverterSource; /** * Type of CAS detected and processed. - * - * @phpstan-type cas_type_alias = CasType::* */ final class CasType implements ConverterSource { - use Enum; + use SdkEnum; public const NSDL = 'NSDL'; diff --git a/src/CasParser/UnifiedResponse/Meta/StatementPeriod.php b/src/CasParser/UnifiedResponse/Meta/StatementPeriod.php index 925da8e..eb49ce1 100644 --- a/src/CasParser/UnifiedResponse/Meta/StatementPeriod.php +++ b/src/CasParser/UnifiedResponse/Meta/StatementPeriod.php @@ -5,17 +5,18 @@ namespace CasParser\CasParser\UnifiedResponse\Meta; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** - * @phpstan-type statement_period_alias = array{ - * from?: \DateTimeInterface, to?: \DateTimeInterface + * @phpstan-type statement_period = array{ + * from?: \DateTimeInterface|null, to?: \DateTimeInterface|null * } */ final class StatementPeriod implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Start date of the statement period. @@ -31,8 +32,7 @@ final class StatementPeriod implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/MutualFund.php b/src/CasParser/UnifiedResponse/MutualFund.php index 56bc838..f95a9f8 100644 --- a/src/CasParser/UnifiedResponse/MutualFund.php +++ b/src/CasParser/UnifiedResponse/MutualFund.php @@ -7,23 +7,23 @@ use CasParser\CasParser\UnifiedResponse\MutualFund\AdditionalInfo; use CasParser\CasParser\UnifiedResponse\MutualFund\Scheme; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; -use CasParser\Core\Conversion\ListOf; /** - * @phpstan-type mutual_fund_alias = array{ - * additionalInfo?: AdditionalInfo, - * amc?: string, - * folioNumber?: string, - * registrar?: string, - * schemes?: list, - * value?: float, + * @phpstan-type mutual_fund = array{ + * additionalInfo?: AdditionalInfo|null, + * amc?: string|null, + * folioNumber?: string|null, + * registrar?: string|null, + * schemes?: list|null, + * value?: float|null, * } */ final class MutualFund implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Additional folio information. @@ -49,8 +49,8 @@ final class MutualFund implements BaseModel #[Api(optional: true)] public ?string $registrar; - /** @var null|list $schemes */ - #[Api(type: new ListOf(Scheme::class), optional: true)] + /** @var list|null $schemes */ + #[Api(list: Scheme::class, optional: true)] public ?array $schemes; /** @@ -61,8 +61,7 @@ final class MutualFund implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -70,7 +69,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|list $schemes + * @param list $schemes */ public static function with( ?AdditionalInfo $additionalInfo = null, diff --git a/src/CasParser/UnifiedResponse/MutualFund/AdditionalInfo.php b/src/CasParser/UnifiedResponse/MutualFund/AdditionalInfo.php index cb11684..bf28a8b 100644 --- a/src/CasParser/UnifiedResponse/MutualFund/AdditionalInfo.php +++ b/src/CasParser/UnifiedResponse/MutualFund/AdditionalInfo.php @@ -5,19 +5,20 @@ namespace CasParser\CasParser\UnifiedResponse\MutualFund; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** * Additional folio information. * - * @phpstan-type additional_info_alias = array{ - * kyc?: string, pan?: string, pankyc?: string + * @phpstan-type additional_info = array{ + * kyc?: string|null, pan?: string|null, pankyc?: string|null * } */ final class AdditionalInfo implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * KYC status of the folio. @@ -39,8 +40,7 @@ final class AdditionalInfo implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/MutualFund/Scheme.php b/src/CasParser/UnifiedResponse/MutualFund/Scheme.php index 451cb56..4923d8c 100644 --- a/src/CasParser/UnifiedResponse/MutualFund/Scheme.php +++ b/src/CasParser/UnifiedResponse/MutualFund/Scheme.php @@ -9,28 +9,28 @@ use CasParser\CasParser\UnifiedResponse\MutualFund\Scheme\Transaction; use CasParser\CasParser\UnifiedResponse\MutualFund\Scheme\Type; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; -use CasParser\Core\Conversion\ListOf; /** * @phpstan-type scheme_alias = array{ - * additionalInfo?: AdditionalInfo, - * cost?: float, - * gain?: Gain, - * isin?: string, - * name?: string, - * nav?: float, - * nominees?: list, - * transactions?: list, - * type?: Type::*, - * units?: float, - * value?: float, + * additionalInfo?: AdditionalInfo|null, + * cost?: float|null, + * gain?: Gain|null, + * isin?: string|null, + * name?: string|null, + * nav?: float|null, + * nominees?: list|null, + * transactions?: list|null, + * type?: Type::*|null, + * units?: float|null, + * value?: float|null, * } */ final class Scheme implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Additional information specific to the scheme. @@ -68,19 +68,19 @@ final class Scheme implements BaseModel /** * List of nominees. * - * @var null|list $nominees + * @var list|null $nominees */ - #[Api(type: new ListOf('string'), optional: true)] + #[Api(list: 'string', optional: true)] public ?array $nominees; - /** @var null|list $transactions */ - #[Api(type: new ListOf(Transaction::class), optional: true)] + /** @var list|null $transactions */ + #[Api(list: Transaction::class, optional: true)] public ?array $transactions; /** * Type of mutual fund scheme. * - * @var null|Type::* $type + * @var Type::*|null $type */ #[Api(enum: Type::class, optional: true)] public ?string $type; @@ -99,8 +99,7 @@ final class Scheme implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -108,9 +107,9 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|list $nominees - * @param null|list $transactions - * @param null|Type::* $type + * @param list $nominees + * @param list $transactions + * @param Type::* $type */ public static function with( ?AdditionalInfo $additionalInfo = null, diff --git a/src/CasParser/UnifiedResponse/MutualFund/Scheme/AdditionalInfo.php b/src/CasParser/UnifiedResponse/MutualFund/Scheme/AdditionalInfo.php index 6102c30..d12d703 100644 --- a/src/CasParser/UnifiedResponse/MutualFund/Scheme/AdditionalInfo.php +++ b/src/CasParser/UnifiedResponse/MutualFund/Scheme/AdditionalInfo.php @@ -5,23 +5,24 @@ namespace CasParser\CasParser\UnifiedResponse\MutualFund\Scheme; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** * Additional information specific to the scheme. * - * @phpstan-type additional_info_alias = array{ - * advisor?: string, - * amfi?: string, - * closeUnits?: float, - * openUnits?: float, - * rtaCode?: string, + * @phpstan-type additional_info = array{ + * advisor?: string|null, + * amfi?: string|null, + * closeUnits?: float|null, + * openUnits?: float|null, + * rtaCode?: string|null, * } */ final class AdditionalInfo implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Financial advisor name (CAMS/KFintech). @@ -55,8 +56,7 @@ final class AdditionalInfo implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/MutualFund/Scheme/Gain.php b/src/CasParser/UnifiedResponse/MutualFund/Scheme/Gain.php index 4b495bf..f951aba 100644 --- a/src/CasParser/UnifiedResponse/MutualFund/Scheme/Gain.php +++ b/src/CasParser/UnifiedResponse/MutualFund/Scheme/Gain.php @@ -5,15 +5,16 @@ namespace CasParser\CasParser\UnifiedResponse\MutualFund\Scheme; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** - * @phpstan-type gain_alias = array{absolute?: float, percentage?: float} + * @phpstan-type gain_alias = array{absolute?: float|null, percentage?: float|null} */ final class Gain implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Absolute gain or loss. @@ -29,8 +30,7 @@ final class Gain implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/MutualFund/Scheme/Transaction.php b/src/CasParser/UnifiedResponse/MutualFund/Scheme/Transaction.php index 9efe4da..90f6ad6 100644 --- a/src/CasParser/UnifiedResponse/MutualFund/Scheme/Transaction.php +++ b/src/CasParser/UnifiedResponse/MutualFund/Scheme/Transaction.php @@ -5,24 +5,25 @@ namespace CasParser\CasParser\UnifiedResponse\MutualFund\Scheme; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** * @phpstan-type transaction_alias = array{ - * amount?: float, - * balance?: float, - * date?: \DateTimeInterface, - * description?: string, - * dividendRate?: float, - * nav?: float, - * type?: string, - * units?: float, + * amount?: float|null, + * balance?: float|null, + * date?: \DateTimeInterface|null, + * description?: string|null, + * dividendRate?: float|null, + * nav?: float|null, + * type?: string|null, + * units?: float|null, * } */ final class Transaction implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Transaction amount. @@ -74,8 +75,7 @@ final class Transaction implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/MutualFund/Scheme/Type.php b/src/CasParser/UnifiedResponse/MutualFund/Scheme/Type.php index a19aa3b..ad08d86 100644 --- a/src/CasParser/UnifiedResponse/MutualFund/Scheme/Type.php +++ b/src/CasParser/UnifiedResponse/MutualFund/Scheme/Type.php @@ -4,17 +4,15 @@ namespace CasParser\CasParser\UnifiedResponse\MutualFund\Scheme; -use CasParser\Core\Concerns\Enum; +use CasParser\Core\Concerns\SdkEnum; use CasParser\Core\Conversion\Contracts\ConverterSource; /** * Type of mutual fund scheme. - * - * @phpstan-type type_alias = Type::* */ final class Type implements ConverterSource { - use Enum; + use SdkEnum; public const EQUITY = 'Equity'; diff --git a/src/CasParser/UnifiedResponse/Summary.php b/src/CasParser/UnifiedResponse/Summary.php index cae3f14..0b9d4fb 100644 --- a/src/CasParser/UnifiedResponse/Summary.php +++ b/src/CasParser/UnifiedResponse/Summary.php @@ -6,15 +6,18 @@ use CasParser\CasParser\UnifiedResponse\Summary\Accounts; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** - * @phpstan-type summary_alias = array{accounts?: Accounts, totalValue?: float} + * @phpstan-type summary_alias = array{ + * accounts?: Accounts|null, totalValue?: float|null + * } */ final class Summary implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; #[Api(optional: true)] public ?Accounts $accounts; @@ -27,8 +30,7 @@ final class Summary implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/Summary/Accounts.php b/src/CasParser/UnifiedResponse/Summary/Accounts.php index 2da5c85..d80af8b 100644 --- a/src/CasParser/UnifiedResponse/Summary/Accounts.php +++ b/src/CasParser/UnifiedResponse/Summary/Accounts.php @@ -8,17 +8,18 @@ use CasParser\CasParser\UnifiedResponse\Summary\Accounts\Insurance; use CasParser\CasParser\UnifiedResponse\Summary\Accounts\MutualFunds; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** * @phpstan-type accounts_alias = array{ - * demat?: Demat, insurance?: Insurance, mutualFunds?: MutualFunds + * demat?: Demat|null, insurance?: Insurance|null, mutualFunds?: MutualFunds|null * } */ final class Accounts implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; #[Api(optional: true)] public ?Demat $demat; @@ -31,8 +32,7 @@ final class Accounts implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/Summary/Accounts/Demat.php b/src/CasParser/UnifiedResponse/Summary/Accounts/Demat.php index 2bce5bc..8eb9bf5 100644 --- a/src/CasParser/UnifiedResponse/Summary/Accounts/Demat.php +++ b/src/CasParser/UnifiedResponse/Summary/Accounts/Demat.php @@ -5,15 +5,16 @@ namespace CasParser\CasParser\UnifiedResponse\Summary\Accounts; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** - * @phpstan-type demat_alias = array{count?: int, totalValue?: float} + * @phpstan-type demat_alias = array{count?: int|null, totalValue?: float|null} */ final class Demat implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Number of demat accounts. @@ -29,8 +30,7 @@ final class Demat implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/Summary/Accounts/Insurance.php b/src/CasParser/UnifiedResponse/Summary/Accounts/Insurance.php index c8751f1..fce66f1 100644 --- a/src/CasParser/UnifiedResponse/Summary/Accounts/Insurance.php +++ b/src/CasParser/UnifiedResponse/Summary/Accounts/Insurance.php @@ -5,15 +5,16 @@ namespace CasParser\CasParser\UnifiedResponse\Summary\Accounts; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** - * @phpstan-type insurance_alias = array{count?: int, totalValue?: float} + * @phpstan-type insurance_alias = array{count?: int|null, totalValue?: float|null} */ final class Insurance implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Number of insurance policies. @@ -29,8 +30,7 @@ final class Insurance implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/CasParser/UnifiedResponse/Summary/Accounts/MutualFunds.php b/src/CasParser/UnifiedResponse/Summary/Accounts/MutualFunds.php index 1abd59f..9ce16cb 100644 --- a/src/CasParser/UnifiedResponse/Summary/Accounts/MutualFunds.php +++ b/src/CasParser/UnifiedResponse/Summary/Accounts/MutualFunds.php @@ -5,15 +5,16 @@ namespace CasParser\CasParser\UnifiedResponse\Summary\Accounts; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; /** - * @phpstan-type mutual_funds_alias = array{count?: int, totalValue?: float} + * @phpstan-type mutual_funds = array{count?: int|null, totalValue?: float|null} */ final class MutualFunds implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Number of mutual fund folios. @@ -29,8 +30,7 @@ final class MutualFunds implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** diff --git a/src/Client.php b/src/Client.php index 90fb9a9..2f39108 100644 --- a/src/Client.php +++ b/src/Client.php @@ -4,16 +4,24 @@ namespace CasParser; -use CasParser\CasGenerator\CasGeneratorService; -use CasParser\CasParser\CasParserService; use CasParser\Core\BaseClient; +use CasParser\Core\Services\CasGeneratorService; +use CasParser\Core\Services\CasParserService; +use Http\Discovery\Psr17FactoryDiscovery; +use Http\Discovery\Psr18ClientDiscovery; class Client extends BaseClient { public string $apiKey; + /** + * @api + */ public CasParserService $casParser; + /** + * @api + */ public CasGeneratorService $casGenerator; public function __construct(?string $apiKey = null, ?string $baseUrl = null) @@ -24,12 +32,19 @@ public function __construct(?string $apiKey = null, ?string $baseUrl = null) 'CAS_PARSER_BASE_URL' ) ?: 'https://portfolio-parser.api.casparser.in'; + $options = RequestOptions::with( + uriFactory: Psr17FactoryDiscovery::findUriFactory(), + streamFactory: Psr17FactoryDiscovery::findStreamFactory(), + requestFactory: Psr17FactoryDiscovery::findRequestFactory(), + transporter: Psr18ClientDiscovery::find(), + ); + parent::__construct( headers: [ 'Content-Type' => 'application/json', 'Accept' => 'application/json', ], baseUrl: $base, - options: new RequestOptions, + options: $options, ); $this->casParser = new CasParserService($this); diff --git a/src/Contracts/CasGeneratorContract.php b/src/Contracts/CasGeneratorContract.php deleted file mode 100644 index 5b7a6ec..0000000 --- a/src/Contracts/CasGeneratorContract.php +++ /dev/null @@ -1,28 +0,0 @@ - $st - */ - public function __construct(private \Generator $st) {} - - public function __toString(): string - { - try { - return $this->getContents(); - } catch (\Throwable) { - return ''; - } - } - - public function getSize(): ?int - { - return null; - } - - public function eof(): bool - { - return !strlen($this->buf) && !$this->st->valid(); - } - - public function close(): void - { - $ex = new class() extends \Exception {}; - - try { - $this->st->throw(new $ex); - } catch (\Throwable) { - } - } - - public function detach(): null - { - $this->buf = ''; - $this->close(); - - return null; - } - - public function tell(): int - { - return $this->pos; - } - - public function rewind(): void - { - $this->buf = ''; - $this->st->rewind(); - } - - public function isSeekable(): bool - { - return false; - } - - public function seek(int $offset, int $whence = SEEK_SET): void {} - - public function isWritable(): bool - { - return false; - } - - public function write(string $string): int - { - return 0; - } - - public function isReadable(): bool - { - return !$this->eof(); - } - - public function read(int $length): string - { - return ''; - } - - public function getContents(): string - { - foreach ($this->st as $chunk) { - $this->buf .= $chunk; - } - - return $this->buf; - } - - public function getMetadata(?string $key = null): mixed - { - return null; - } -} diff --git a/src/Core/Attributes/Api.php b/src/Core/Attributes/Api.php index d6aa88a..fd2b006 100644 --- a/src/Core/Attributes/Api.php +++ b/src/Core/Attributes/Api.php @@ -6,6 +6,8 @@ use CasParser\Core\Conversion\Contracts\Converter; use CasParser\Core\Conversion\Contracts\ConverterSource; +use CasParser\Core\Conversion\ListOf; +use CasParser\Core\Conversion\MapOf; /** * @internal @@ -14,23 +16,27 @@ final class Api { /** - * @var null|class-string|Converter|string + * @var class-string|Converter|string|null */ - public readonly null|Converter|string $type; + public readonly Converter|string|null $type; /** - * @param null|class-string|Converter|string $type - * @param null|class-string|Converter $enum - * @param null|class-string|Converter|string $union + * @param class-string|Converter|string|null $type + * @param class-string|Converter|null $enum + * @param class-string|Converter|null $union + * @param class-string|Converter|string|null $list + * @param class-string|Converter|string|null $map */ public function __construct( public readonly ?string $apiName = null, - null|Converter|string $type = null, - null|Converter|string $enum = null, - null|Converter|string $union = null, + Converter|string|null $type = null, + Converter|string|null $enum = null, + Converter|string|null $union = null, + Converter|string|null $list = null, + Converter|string|null $map = null, public readonly bool $nullable = false, public readonly bool $optional = false, ) { - $this->type = $type ?? $enum ?? $union; + $this->type = $type ?? $enum ?? $union ?? ($list ? new ListOf($list) : ($map ? new MapOf($map) : null)); } } diff --git a/src/Core/BaseClient.php b/src/Core/BaseClient.php index a6b3e04..cae68a4 100644 --- a/src/Core/BaseClient.php +++ b/src/Core/BaseClient.php @@ -4,10 +4,14 @@ namespace CasParser\Core; -use CasParser\Errors\APIStatusError; +use CasParser\Core\Contracts\BasePage; +use CasParser\Core\Contracts\BaseStream; +use CasParser\Core\Conversion\Contracts\Converter; +use CasParser\Core\Conversion\Contracts\ConverterSource; +use CasParser\Core\Exceptions\APIConnectionException; +use CasParser\Core\Exceptions\APIStatusException; use CasParser\RequestOptions; -use Http\Discovery\Psr17FactoryDiscovery; -use Http\Discovery\Psr18ClientDiscovery; +use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\RequestInterface; @@ -16,69 +20,89 @@ use Psr\Http\Message\UriFactoryInterface; use Psr\Http\Message\UriInterface; +/** + * @phpstan-type normalized_request = array{ + * method: string, + * path: string, + * query: array, + * headers: array>, + * body: mixed, + * } + */ class BaseClient { protected UriInterface $baseUrl; - protected UriFactoryInterface $uriFactory; - - protected StreamFactoryInterface $streamFactory; - - protected RequestFactoryInterface $requestFactory; - - protected ClientInterface $requester; - /** - * @param array|string> $headers + * @internal + * + * @param array|null> $headers */ public function __construct( protected array $headers, string $baseUrl, protected RequestOptions $options = new RequestOptions, ) { - $this->uriFactory = Psr17FactoryDiscovery::findUriFactory(); - $this->streamFactory = Psr17FactoryDiscovery::findStreamFactory(); - $this->requestFactory = Psr17FactoryDiscovery::findRequestFactory(); - - $this->baseUrl = $this->uriFactory->createUri($baseUrl); - $this->requester = Psr18ClientDiscovery::find(); + assert(!is_null($this->options->uriFactory)); + $this->baseUrl = $this->options->uriFactory->createUri($baseUrl); } /** - * @param list|string $path + * @param string|list $path * @param array $query * @param array $headers + * @param class-string> $page + * @param class-string> $stream + * @param RequestOptions|array|null $options */ public function request( string $method, - array|string $path, + string|array $path, array $query = [], array $headers = [], mixed $body = null, - mixed $options = [], + string|Converter|ConverterSource|null $convert = null, + ?string $page = null, + ?string $stream = null, + RequestOptions|array|null $options = [], ): mixed { // @phpstan-ignore-next-line - [$req, $opts] = $this->buildRequest(method: $method, path: $path, query: $query, headers: $headers, opts: $options); + [$req, $opts] = $this->buildRequest(method: $method, path: $path, query: $query, headers: $headers, body: $body, opts: $options); + ['method' => $method, 'path' => $uri, 'headers' => $headers] = $req; + assert(!is_null($opts->requestFactory)); + + $request = $opts->requestFactory->createRequest($method, uri: $uri); + $request = Util::withSetHeaders($request, headers: $headers); // @phpstan-ignore-next-line - $rsp = $this->sendRequest($req, data: $body, opts: $opts, redirectCount: 0, retryCount: 0); - if (204 == $rsp->getStatusCode()) { - return null; // Handle 204 No Content + $rsp = $this->sendRequest($opts, req: $request, data: $body, redirectCount: 0, retryCount: 0); + + $decoded = Util::decodeContent($rsp); + + if (!is_null($stream)) { + return new $stream( + convert: $convert, + request: $request, + response: $rsp, + stream: $decoded + ); } - return Util::decodeContent($rsp); - } + if (!is_null($page)) { + return new $page( + convert: $convert, + client: $this, + request: $req, + options: $opts, + data: $decoded, + ); + } - /** - * @template Item - * @template T of Pagination\AbstractPage - * - * @param T $page - */ - public function requestApiList(object $page, RequestOptions $options): ResponseInterface - { - // @phpstan-ignore-next-line - return null; + if (!is_null($convert)) { + return Conversion::coerce($convert, value: $decoded); + } + + return $decoded; } /** @return array */ @@ -88,56 +112,67 @@ protected function authHeaders(): array } /** - * @param list|string $path + * @internal + * + * @param string|list $path * @param array $query - * @param array|string> $headers - * @param null|array{ - * timeout?: null|float, - * maxRetries?: null|int, - * initialRetryDelay?: null|float, - * maxRetryDelay?: null|float, - * extraHeaders?: null|list, - * extraQueryParams?: null|list, - * extraBodyParams?: null|list, - * }|RequestOptions $opts + * @param array|null> $headers + * @param array{ + * timeout?: float|null, + * maxRetries?: int|null, + * initialRetryDelay?: float|null, + * maxRetryDelay?: float|null, + * extraHeaders?: array|null>|null, + * extraQueryParams?: array|null, + * extraBodyParams?: mixed, + * transporter?: ClientInterface|null, + * uriFactory?: UriFactoryInterface|null, + * streamFactory?: StreamFactoryInterface|null, + * requestFactory?: RequestFactoryInterface|null, + * }|null $opts * - * @return array{RequestInterface, RequestOptions} + * @return array{normalized_request, RequestOptions} */ protected function buildRequest( string $method, - array|string $path, + string|array $path, array $query, array $headers, - null|array|RequestOptions $opts, + mixed $body, + RequestOptions|array|null $opts, ): array { - $opts = [...$this->options->__serialize(), ...RequestOptions::parse($opts)->__serialize()]; - $options = new RequestOptions(...$opts); + $options = RequestOptions::parse($this->options, $opts); $parsedPath = Util::parsePath($path); /** @var array $mergedQuery */ - $mergedQuery = array_merge_recursive($query, $options->extraQueryParams); - $uri = Util::joinUri($this->baseUrl, path: $parsedPath, query: $mergedQuery); + $mergedQuery = array_merge_recursive( + $query, + $options->extraQueryParams ?? [], + ); + $uri = Util::joinUri($this->baseUrl, path: $parsedPath, query: $mergedQuery)->__toString(); - /** @var array|string> $mergedHeaders */ + /** @var array|null> $mergedHeaders */ $mergedHeaders = [...$this->headers, ...$this->authHeaders(), ...$headers, - ...$options->extraHeaders, ]; + ...($options->extraHeaders ?? []), ]; - $req = $this->requestFactory->createRequest(strtoupper($method), uri: $uri); - $req = Util::withSetHeaders($req, headers: $mergedHeaders); + $req = ['method' => strtoupper($method), 'path' => $uri, 'query' => $mergedQuery, 'headers' => $mergedHeaders, 'body' => $body]; return [$req, $options]; } + /** + * @internal + */ protected function followRedirect( ResponseInterface $rsp, RequestInterface $req ): RequestInterface { $location = $rsp->getHeaderLine('Location'); if (!$location) { - throw new \RuntimeException('Redirection without Location header'); + throw new APIConnectionException($req, message: 'Redirection without Location header'); } $uri = Util::joinUri($req->getUri(), path: $location); @@ -146,39 +181,106 @@ protected function followRedirect( } /** - * @param null|array|bool|float|int|resource|string|\Traversable< - * mixed - * > $data + * @internal + */ + protected function shouldRetry( + RequestOptions $opts, + int $retryCount, + ?ResponseInterface $rsp + ): bool { + if ($retryCount >= $opts->maxRetries) { + return false; + } + + $code = $rsp?->getStatusCode(); + if (408 == $code || 409 == $code || 429 == $code || $code >= 500) { + return true; + } + + return false; + } + + /** + * @internal + */ + protected function retryDelay( + RequestOptions $opts, + int $retryCount, + ?ResponseInterface $rsp + ): float { + if (!empty($header = $rsp?->getHeaderLine('retry-after'))) { + if (is_numeric($header)) { + return floatval($header); + } + + try { + $date = new \DateTimeImmutable($header); + $span = time() - $date->getTimestamp(); + + return max(0.0, $span); + } catch (\DateMalformedStringException) { + } + } + + $scale = $retryCount ** 2; + $jitter = 1 - (0.25 * mt_rand() / mt_getrandmax()); + $naive = $opts->initialRetryDelay * $scale * $jitter; + + return max(0.0, min($naive, $opts->maxRetryDelay)); + } + + /** + * @internal + * + * @param bool|int|float|string|resource|\Traversable|array|null $data */ protected function sendRequest( + RequestOptions $opts, RequestInterface $req, mixed $data, - RequestOptions $opts, int $retryCount, int $redirectCount, ): ResponseInterface { - $req = Util::withSetBody($this->streamFactory, req: $req, body: $data); - $rsp = $this->requester->sendRequest($req); - $code = $rsp->getStatusCode(); + assert(null !== $opts->streamFactory && null !== $opts->transporter); + + $req = Util::withSetBody($opts->streamFactory, req: $req, body: $data); + + $rsp = null; + $err = null; + + try { + $rsp = $opts->transporter->sendRequest($req); + } catch (ClientExceptionInterface $e) { + $err = $e; + } + + $code = $rsp?->getStatusCode(); if ($code >= 300 && $code < 400) { + assert(!is_null($rsp)); + if ($redirectCount >= 20) { - throw new \RuntimeException('Maximum redirects exceeded'); + throw new APIConnectionException($req, message: 'Maximum redirects exceeded'); } $req = $this->followRedirect($rsp, req: $req); - return $this->sendRequest($req, data: $data, opts: $opts, retryCount: $retryCount, redirectCount: ++$redirectCount); + return $this->sendRequest($opts, req: $req, data: $data, retryCount: $retryCount, redirectCount: ++$redirectCount); } - if ($code >= 400 && $code < 500) { - throw APIStatusError::from(null, request: $req, response: $rsp); - } + if ($code >= 400 || is_null($rsp)) { + if ($this->shouldRetry($opts, retryCount: $retryCount, rsp: $rsp)) { + $exn = is_null($rsp) ? new APIConnectionException($req, previous: $err) : APIStatusException::from(request: $req, response: $rsp); + + throw $exn; + } - if ($code >= 500 && $retryCount < $opts->maxRetries) { - usleep((int) $opts->initialRetryDelay); + $seconds = $this->retryDelay($opts, retryCount: $redirectCount, rsp: $rsp); + $floor = floor($seconds); + time_nanosleep((int) $floor, nanoseconds: (int) ($seconds - $floor) * 10 ** 9); - return $this->sendRequest($req, data: $data, opts: $opts, retryCount: ++$retryCount, redirectCount: $redirectCount); + return $this->sendRequest($opts, req: $req, data: $data, retryCount: ++$retryCount, redirectCount: $redirectCount); } return $rsp; diff --git a/src/Core/Concerns/Page.php b/src/Core/Concerns/Page.php deleted file mode 100644 index dacd050..0000000 --- a/src/Core/Concerns/Page.php +++ /dev/null @@ -1,22 +0,0 @@ -|self $params - * @param null|array|RequestOptions $options - * - * @return array{array, array{ - * timeout: float, - * maxRetries: int, - * initialRetryDelay: float, - * maxRetryDelay: float, - * extraHeaders: list, - * extraQueryParams: list, - * extraBodyParams: list, - * }} - */ - public static function parseRequest(null|array|self $params, null|array|RequestOptions $options): array - { - $converter = self::converter(); - $state = new DumpState; - $dumped = (array) Conversion::dump($converter, value: $params, state: $state); - $opts = RequestOptions::parse($options); // @phpstan-ignore-line - - if (!$state->canRetry) { - $opts->maxRetries = 0; - } - - $opt = $opts->__serialize(); - if (empty($opt['extraHeaders'])) { - unset($opt['extraHeaders']); - } - if (empty($opt['extraQueryParams'])) { - unset($opt['extraQueryParams']); - } - if (empty($opt['extraBodyParams'])) { - unset($opt['extraBodyParams']); - } - - return [$dumped, $opt]; // @phpstan-ignore-line - } -} diff --git a/src/Core/Concerns/Enum.php b/src/Core/Concerns/SdkEnum.php similarity index 95% rename from src/Core/Concerns/Enum.php rename to src/Core/Concerns/SdkEnum.php index c967b5d..9941eaf 100644 --- a/src/Core/Concerns/Enum.php +++ b/src/Core/Concerns/SdkEnum.php @@ -7,7 +7,10 @@ use CasParser\Core\Conversion\Contracts\Converter; use CasParser\Core\Conversion\EnumOf; -trait Enum +/** + * @internal + */ +trait SdkEnum { private static Converter $converter; diff --git a/src/Core/Concerns/Model.php b/src/Core/Concerns/SdkModel.php similarity index 79% rename from src/Core/Concerns/Model.php rename to src/Core/Concerns/SdkModel.php index f6e3881..27f095b 100644 --- a/src/Core/Concerns/Model.php +++ b/src/Core/Concerns/SdkModel.php @@ -13,8 +13,10 @@ /** * @internal + * + * @template-covariant Shape of array */ -trait Model +trait SdkModel { private static ModelOf $converter; @@ -38,16 +40,18 @@ public function __serialize(): array /** * @internal * - * @param array $data + * @param array $data */ public function __unserialize(array $data): void { foreach ($data as $key => $value) { - $this->offsetSet($key, value: $value); + $this->offsetSet($key, value: $value); // @phpstan-ignore-line } } /** + * @internal + * * @return array */ public function __debugInfo(): array @@ -60,22 +64,24 @@ public function __debugInfo(): array */ public function __toString(): string { - return json_encode($this->__debugInfo(), flags: JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) ?: ''; + return Util::prettyEncodeJson($this->__debugInfo()); } /** + * @internal + * * Magic get is intended to occur when we have manually unset * a native class property, indicating an omitted value, - * or a property overridden with an incongruent type. + * or a property overridden with an incongruent type * - * @throws \Exception + * @return value-of * - * @internal + * @throws \Exception */ public function __get(string $key): mixed { if (!array_key_exists($key, array: self::$converter->properties)) { - throw new \Exception("Property '{$key}' does not exist in {$this}::class"); + throw new \RuntimeException("Property '{$key}' does not exist in {$this}::class"); } // The unset property was overridden by a value with an incongruent type. @@ -88,17 +94,21 @@ public function __get(string $key): mixed // An optional property which was unset to be omitted from serialized is being accessed. // Return null to match user's expectations. - return null; + return null; // @phpstan-ignore-line } - /** @return array */ + /** + * @return Shape + */ public function toArray(): array { - return $this->__serialize(); + return $this->__serialize(); // @phpstan-ignore-line } /** * @internal + * + * @param key-of $offset */ public function offsetExists(mixed $offset): bool { @@ -115,7 +125,7 @@ public function offsetExists(mixed $offset): bool return true; } - $property = self::$converter->properties[$offset]->property ?? new \ReflectionProperty($this, property: $offset); + $property = self::$converter->properties[$offset]->property; return $property->isInitialized($this); } @@ -125,6 +135,8 @@ public function offsetExists(mixed $offset): bool /** * @internal + * + * @param key-of $offset */ public function &offsetGet(mixed $offset): mixed { @@ -132,19 +144,21 @@ public function &offsetGet(mixed $offset): mixed throw new \InvalidArgumentException; } - if (!$this->offsetExists($offset)) { - return null; + if (!$this->offsetExists($offset)) { // @phpstan-ignore-line + return null; // @phpstan-ignore-line } if (array_key_exists($offset, array: $this->_data)) { - return $this->_data[$offset]; + return $this->_data[$offset]; // @phpstan-ignore-line } - return $this->{$offset}; + return $this->{$offset}; // @phpstan-ignore-line } /** * @internal + * + * @param key-of $offset */ public function offsetSet(mixed $offset, mixed $value): void { @@ -158,9 +172,9 @@ public function offsetSet(mixed $offset, mixed $value): void $coerced = Conversion::coerce($type, value: $value, state: new CoerceState(translateNames: false)); - if (property_exists($this, property: $offset)) { + if (property_exists($this, property: $offset)) { // @phpstan-ignore-line try { - $this->{$offset} = $coerced; + $this->{$offset} = $coerced; // @phpstan-ignore-line unset($this->_data[$offset]); return; @@ -174,6 +188,8 @@ public function offsetSet(mixed $offset, mixed $value): void /** * @internal + * + * @param key-of $offset */ public function offsetUnset(mixed $offset): void { @@ -181,7 +197,7 @@ public function offsetUnset(mixed $offset): void throw new \InvalidArgumentException; } - if (property_exists($this, property: $offset)) { + if (property_exists($this, property: $offset)) { // @phpstan-ignore-line unset($this->{$offset}); } @@ -189,6 +205,8 @@ public function offsetUnset(mixed $offset): void } /** + * @internal + * * @return array */ public function jsonSerialize(): array @@ -198,9 +216,9 @@ public function jsonSerialize(): array } /** - * @internal + * @param array $data */ - public static function fromArray(mixed $data): self + public static function fromArray(array $data): static { return self::converter()->from($data); // @phpstan-ignore-line } @@ -222,16 +240,10 @@ public static function converter(): Converter /** * @internal */ - public static function introspect(): void + private function initialize(): void { static::converter(); - } - /** - * @internal - */ - private function unsetOptionalProperties(): void - { foreach (self::$converter->properties as $name => $info) { if ($info->optional) { unset($this->{$name}); diff --git a/src/Core/Concerns/SdkPage.php b/src/Core/Concerns/SdkPage.php new file mode 100644 index 0000000..20e1c71 --- /dev/null +++ b/src/Core/Concerns/SdkPage.php @@ -0,0 +1,118 @@ + + */ + abstract public function getItems(): array; + + public function hasNextPage(): bool + { + $items = $this->getItems(); + if (empty($items)) { + return false; + } + + return null != $this->nextRequest(); + } + + /** + * Get the next page of results. + * Before calling this method, you must check if there is a next page + * using {@link hasNextPage()}. + * + * @return static of static + * + * @throws APIStatusException + */ + public function getNextPage(): static + { + $next = $this->nextRequest(); + if (!$next) { + throw new \RuntimeException( + 'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.' + ); + } + + [$req, $opts] = $next; + + // @phpstan-ignore-next-line + return $this->client->request(...$req, convert: $this->convert, page: $this::class, options: $opts); + } + + /** + * Iterator yielding each page (instance of static). + * + * @return \Generator + */ + public function getIterator(): \Generator + { + $page = $this; + + yield $page; + while ($page->hasNextPage()) { + $page = $page->getNextPage(); + + yield $page; + } + } + + /** + * Iterator yielding each item across all pages. + * + * @return \Generator + */ + public function pagingEachItem(): \Generator + { + foreach ($this as $page) { + foreach ($page->getItems() as $item) { + yield $item; + } + } + } + + /** + * @internal + * + * @param array $data + * + * @return static + */ + abstract public static function fromArray(array $data): static; + + /** + * @internal + * + * @return array{normalized_request, RequestOptions} + */ + abstract protected function nextRequest(): ?array; +} diff --git a/src/Core/Concerns/SdkParams.php b/src/Core/Concerns/SdkParams.php new file mode 100644 index 0000000..1010389 --- /dev/null +++ b/src/Core/Concerns/SdkParams.php @@ -0,0 +1,37 @@ +|self|null $params + * @param array|RequestOptions|null $options + * + * @return array{array, RequestOptions} + */ + public static function parseRequest(array|self|null $params, array|RequestOptions|null $options): array + { + $value = is_array($params) ? Util::array_filter_omit($params) : $params; + $converter = self::converter(); + $state = new DumpState; + $dumped = (array) Conversion::dump($converter, value: $value, state: $state); + $opts = RequestOptions::parse($options); // @phpstan-ignore-line + + if (!$state->canRetry) { + $opts->maxRetries = 0; + } + + return [$dumped, $opts]; // @phpstan-ignore-line + } +} diff --git a/src/Core/Concerns/Union.php b/src/Core/Concerns/SdkUnion.php similarity index 98% rename from src/Core/Concerns/Union.php rename to src/Core/Concerns/SdkUnion.php index 62a2aec..6a073f8 100644 --- a/src/Core/Concerns/Union.php +++ b/src/Core/Concerns/SdkUnion.php @@ -11,7 +11,7 @@ /** * @internal */ -trait Union +trait SdkUnion { private static Converter $converter; diff --git a/src/Core/Contracts/BasePage.php b/src/Core/Contracts/BasePage.php index d08f732..40eecd6 100644 --- a/src/Core/Contracts/BasePage.php +++ b/src/Core/Contracts/BasePage.php @@ -4,13 +4,49 @@ namespace CasParser\Core\Contracts; +use CasParser\Client; +use CasParser\Core\Conversion\Contracts\Converter; +use CasParser\Core\Conversion\Contracts\ConverterSource; +use CasParser\RequestOptions; + /** * @internal + * + * @phpstan-import-type normalized_request from \CasParser\Core\BaseClient + * + * @template Item + * + * @extends \IteratorAggregate */ -interface BasePage extends \Stringable +interface BasePage extends \IteratorAggregate { /** - * @return \Traversable + * @internal + * + * @param normalized_request $request + */ + public function __construct( + Converter|ConverterSource|string $convert, + Client $client, + array $request, + RequestOptions $options, + mixed $data, + ); + + public function hasNextPage(): bool; + + /** + * @return list + */ + public function getItems(): array; + + /** + * @return static + */ + public function getNextPage(): static; + + /** + * @return \Generator */ - public function pagingEachItem(): \Traversable; + public function pagingEachItem(): \Generator; } diff --git a/src/Core/Contracts/BaseStream.php b/src/Core/Contracts/BaseStream.php new file mode 100644 index 0000000..26a9dd7 --- /dev/null +++ b/src/Core/Contracts/BaseStream.php @@ -0,0 +1,36 @@ + + */ +interface BaseStream extends \IteratorAggregate +{ + /** + * @param \Generator $stream + */ + public function __construct( + Converter|ConverterSource|string $convert, + RequestInterface $request, + ResponseInterface $response, + \Generator $stream, + ); + + /** + * Manually force the stream to close early. + * Iterating through will automatically close as well. + */ + public function close(): void; +} diff --git a/src/Core/Conversion/Concerns/ArrayOf.php b/src/Core/Conversion/Concerns/ArrayOf.php index c283cd3..e711b11 100644 --- a/src/Core/Conversion/Concerns/ArrayOf.php +++ b/src/Core/Conversion/Concerns/ArrayOf.php @@ -15,12 +15,12 @@ */ trait ArrayOf { - private readonly null|Converter|ConverterSource|string $type; + private readonly Converter|ConverterSource|string|null $type; public function __construct( - null|Converter|ConverterSource|string $type = null, - null|Converter|ConverterSource|string $enum = null, - null|Converter|ConverterSource|string $union = null, + Converter|ConverterSource|string|null $type = null, + Converter|ConverterSource|string|null $enum = null, + Converter|ConverterSource|string|null $union = null, private readonly bool $nullable = false, ) { $this->type = $type ?? $enum ?? $union; diff --git a/src/Core/Conversion/EnumOf.php b/src/Core/Conversion/EnumOf.php index 05065ac..aaed8da 100644 --- a/src/Core/Conversion/EnumOf.php +++ b/src/Core/Conversion/EnumOf.php @@ -15,7 +15,7 @@ final class EnumOf implements Converter private readonly string $type; /** - * @param list $members + * @param list $members */ public function __construct(private readonly array $members) { diff --git a/src/Core/Conversion/ModelOf.php b/src/Core/Conversion/ModelOf.php index fbaeb42..0dbf406 100644 --- a/src/Core/Conversion/ModelOf.php +++ b/src/Core/Conversion/ModelOf.php @@ -88,11 +88,11 @@ public function coerce(mixed $value, CoerceState $state): mixed $acc[$name] = $item; } - return $this->from($acc); + return $this->from($acc); // @phpstan-ignore-line } /** - * @param array $data + * @param array $data */ public function from(array $data): BaseModel { diff --git a/src/Core/Conversion/PropertyInfo.php b/src/Core/Conversion/PropertyInfo.php index 5eec48e..385627b 100644 --- a/src/Core/Conversion/PropertyInfo.php +++ b/src/Core/Conversion/PropertyInfo.php @@ -46,9 +46,9 @@ public function __construct(public readonly \ReflectionProperty $property) } /** - * @param null|array|Converter|ConverterSource|\ReflectionType|string $type + * @param array|Converter|ConverterSource|\ReflectionType|string|null $type */ - private static function parse(null|array|Converter|ConverterSource|\ReflectionType|string $type): Converter|ConverterSource|string + private static function parse(array|Converter|ConverterSource|\ReflectionType|string|null $type): Converter|ConverterSource|string { if (is_string($type) || $type instanceof Converter) { return $type; diff --git a/src/Core/Conversion/UnionOf.php b/src/Core/Conversion/UnionOf.php index 525fdac..4a059ab 100644 --- a/src/Core/Conversion/UnionOf.php +++ b/src/Core/Conversion/UnionOf.php @@ -9,6 +9,9 @@ use CasParser\Core\Conversion\Contracts\Converter; use CasParser\Core\Conversion\Contracts\ConverterSource; +/** + * @internal + */ final class UnionOf implements Converter { /** @@ -77,7 +80,7 @@ public function dump(mixed $value, DumpState $state): mixed private function resolveVariant( mixed $value, - ): null|Converter|ConverterSource|string { + ): Converter|ConverterSource|string|null { if ($value instanceof BaseModel) { return $value::class; } diff --git a/src/Errors/APIConnectionError.php b/src/Core/Exceptions/APIConnectionException.php similarity index 52% rename from src/Errors/APIConnectionError.php rename to src/Core/Exceptions/APIConnectionException.php index 7e0af87..6757759 100644 --- a/src/Errors/APIConnectionError.php +++ b/src/Core/Exceptions/APIConnectionException.php @@ -1,8 +1,8 @@ getBody()->__toString(), previous: $previous); + parent::__construct(message: $message, previous: $previous); } } diff --git a/src/Core/Exceptions/APIStatusException.php b/src/Core/Exceptions/APIStatusException.php new file mode 100644 index 0000000..ff9d460 --- /dev/null +++ b/src/Core/Exceptions/APIStatusException.php @@ -0,0 +1,55 @@ +response = $response; + $this->status = $response->getStatusCode(); + + $summary = Util::prettyEncodeJson(['status' => $this->status, 'body' => Util::decodeJson($response->getBody())]); + + if ('' != $message) { + $summary .= $message.PHP_EOL.$summary; + } + + parent::__construct(request: $request, message: $summary, previous: $previous); + } + + public static function from( + RequestInterface $request, + ResponseInterface $response, + string $message = '' + ): self { + $status = $response->getStatusCode(); + + $cls = match (true) { + 400 === $status => BadRequestException::class, + 401 === $status => AuthenticationException::class, + 403 === $status => PermissionDeniedException::class, + 404 === $status => NotFoundException::class, + 409 === $status => ConflictException::class, + 422 === $status => UnprocessableEntityException::class, + 429 === $status => RateLimitException::class, + $status >= 500 => InternalServerException::class, + default => APIStatusException::class + }; + + return new $cls(request: $request, response: $response, message: $message); + } +} diff --git a/src/Errors/APITimeoutError.php b/src/Core/Exceptions/APITimeoutException.php similarity index 68% rename from src/Errors/APITimeoutError.php rename to src/Core/Exceptions/APITimeoutException.php index 3d73bfb..5e9e4d3 100644 --- a/src/Errors/APITimeoutError.php +++ b/src/Core/Exceptions/APITimeoutException.php @@ -1,13 +1,13 @@ - */ -abstract class AbstractPage implements \IteratorAggregate, Page -{ - public function __construct( - protected BaseClient $client, - protected PageRequestOptions $options, - protected ResponseInterface $response, - protected mixed $body, - ) {} - - abstract public function nextPageRequestOptions(): ?PageRequestOptions; - - /** - * @return list - */ - abstract public function getPaginatedItems(): array; - - public function hasNextPage(): bool - { - $items = $this->getPaginatedItems(); - if (empty($items)) { - return false; - } - - return null != $this->nextPageRequestOptions(); - } - - /** - * Get the next page of results. - * Before calling this method, you must check if there is a next page - * using {@link hasNextPage()}. - * - * @return static of AbstractPage - * - * @throws Error - */ - public function getNextPage(): static - { - $nextOptions = $this->nextPageRequestOptions(); - if (!$nextOptions) { - throw new Error( - 'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.' - ); - } - - $response = $this->client->requestApiList($this, $nextOptions); - - /** @var static of AbstractPage $nextPage */ - $nextPage = new static( - client: $this->client, - options: $nextOptions, - response: $response, - body: $response->getBody() - ); - - return $nextPage; - } - - /** - * Generator yielding each page (instance of static). - * - * @return \Generator - */ - public function iterPages(): \Generator - { - $page = $this; - - yield $page; - while ($page->hasNextPage()) { - $page = $page->getNextPage(); - - yield $page; - } - } - - /** - * Generator yielding each item across all pages. - * - * @return \Generator - */ - public function getIterator(): \Generator - { - foreach ($this->iterPages() as $page) { - foreach ($page->getPaginatedItems() as $item) { - yield $item; - } - } - } -} diff --git a/src/Core/Pagination/PageRequestOptions.php b/src/Core/Pagination/PageRequestOptions.php deleted file mode 100644 index 0c3b2b7..0000000 --- a/src/Core/Pagination/PageRequestOptions.php +++ /dev/null @@ -1,73 +0,0 @@ - $email, + 'fromDate' => $fromDate, + 'password' => $password, + 'toDate' => $toDate, + 'casAuthority' => $casAuthority, + 'panNo' => $panNo, + ], + $requestOptions, + ); + + // @phpstan-ignore-next-line; + return $this->client->request( + method: 'post', + path: 'v4/generate', + body: (object) $parsed, + options: $options, + convert: CasGeneratorGenerateCasResponse::class, + ); + } +} diff --git a/src/CasParser/CasParserService.php b/src/Core/Services/CasParserService.php similarity index 51% rename from src/CasParser/CasParserService.php rename to src/Core/Services/CasParserService.php index 318face..27fbe5a 100644 --- a/src/CasParser/CasParserService.php +++ b/src/Core/Services/CasParserService.php @@ -2,122 +2,147 @@ declare(strict_types=1); -namespace CasParser\CasParser; +namespace CasParser\Core\Services; +use CasParser\CasParser\CasParserCamsKfintechParams; +use CasParser\CasParser\CasParserCdslParams; +use CasParser\CasParser\CasParserNsdlParams; +use CasParser\CasParser\CasParserSmartParseParams; +use CasParser\CasParser\UnifiedResponse; use CasParser\Client; -use CasParser\Contracts\CasParserContract; -use CasParser\Core\Conversion; +use CasParser\Core\ServiceContracts\CasParserContract; use CasParser\RequestOptions; +use const CasParser\Core\OMIT as omit; + final class CasParserService implements CasParserContract { + /** + * @internal + */ public function __construct(private Client $client) {} /** + * @api + * * This endpoint specifically parses CAMS/KFintech CAS (Consolidated Account Statement) PDF files and returns data in a unified format. * Use this endpoint when you know the PDF is from CAMS or KFintech. * - * @param array{ - * password?: string, pdfFile?: string, pdfURL?: string - * }|CasParserCamsKfintechParams $params + * @param string $password Password for the PDF file (if required) + * @param string $pdfFile Base64 encoded CAS PDF file + * @param string $pdfURL URL to the CAS PDF file */ public function camsKfintech( - array|CasParserCamsKfintechParams $params, + $password = omit, + $pdfFile = omit, + $pdfURL = omit, ?RequestOptions $requestOptions = null, ): UnifiedResponse { [$parsed, $options] = CasParserCamsKfintechParams::parseRequest( - $params, - $requestOptions + ['password' => $password, 'pdfFile' => $pdfFile, 'pdfURL' => $pdfURL], + $requestOptions, ); - $resp = $this->client->request( + + // @phpstan-ignore-next-line; + return $this->client->request( method: 'post', path: 'v4/cams_kfintech/parse', body: (object) $parsed, options: $options, + convert: UnifiedResponse::class, ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(UnifiedResponse::class, value: $resp); } /** + * @api + * * This endpoint specifically parses CDSL CAS (Consolidated Account Statement) PDF files and returns data in a unified format. * Use this endpoint when you know the PDF is from CDSL. * - * @param array{ - * password?: string, pdfFile?: string, pdfURL?: string - * }|CasParserCdslParams $params + * @param string $password Password for the PDF file (if required) + * @param string $pdfFile Base64 encoded CAS PDF file + * @param string $pdfURL URL to the CAS PDF file */ public function cdsl( - array|CasParserCdslParams $params, - ?RequestOptions $requestOptions = null + $password = omit, + $pdfFile = omit, + $pdfURL = omit, + ?RequestOptions $requestOptions = null, ): UnifiedResponse { [$parsed, $options] = CasParserCdslParams::parseRequest( - $params, - $requestOptions + ['password' => $password, 'pdfFile' => $pdfFile, 'pdfURL' => $pdfURL], + $requestOptions, ); - $resp = $this->client->request( + + // @phpstan-ignore-next-line; + return $this->client->request( method: 'post', path: 'v4/cdsl/parse', body: (object) $parsed, options: $options, + convert: UnifiedResponse::class, ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(UnifiedResponse::class, value: $resp); } /** + * @api + * * This endpoint specifically parses NSDL CAS (Consolidated Account Statement) PDF files and returns data in a unified format. * Use this endpoint when you know the PDF is from NSDL. * - * @param array{ - * password?: string, pdfFile?: string, pdfURL?: string - * }|CasParserNsdlParams $params + * @param string $password Password for the PDF file (if required) + * @param string $pdfFile Base64 encoded CAS PDF file + * @param string $pdfURL URL to the CAS PDF file */ public function nsdl( - array|CasParserNsdlParams $params, - ?RequestOptions $requestOptions = null + $password = omit, + $pdfFile = omit, + $pdfURL = omit, + ?RequestOptions $requestOptions = null, ): UnifiedResponse { [$parsed, $options] = CasParserNsdlParams::parseRequest( - $params, - $requestOptions + ['password' => $password, 'pdfFile' => $pdfFile, 'pdfURL' => $pdfURL], + $requestOptions, ); - $resp = $this->client->request( + + // @phpstan-ignore-next-line; + return $this->client->request( method: 'post', path: 'v4/nsdl/parse', body: (object) $parsed, options: $options, + convert: UnifiedResponse::class, ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(UnifiedResponse::class, value: $resp); } /** + * @api + * * This endpoint parses CAS (Consolidated Account Statement) PDF files from NSDL, CDSL, or CAMS/KFintech and returns data in a unified format. * It auto-detects the CAS type and transforms the data into a consistent structure regardless of the source. * - * @param array{ - * password?: string, pdfFile?: string, pdfURL?: string - * }|CasParserSmartParseParams $params + * @param string $password Password for the PDF file (if required) + * @param string $pdfFile Base64 encoded CAS PDF file + * @param string $pdfURL URL to the CAS PDF file */ public function smartParse( - array|CasParserSmartParseParams $params, + $password = omit, + $pdfFile = omit, + $pdfURL = omit, ?RequestOptions $requestOptions = null, ): UnifiedResponse { [$parsed, $options] = CasParserSmartParseParams::parseRequest( - $params, - $requestOptions + ['password' => $password, 'pdfFile' => $pdfFile, 'pdfURL' => $pdfURL], + $requestOptions, ); - $resp = $this->client->request( + + // @phpstan-ignore-next-line; + return $this->client->request( method: 'post', path: 'v4/smart/parse', body: (object) $parsed, options: $options, + convert: UnifiedResponse::class, ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(UnifiedResponse::class, value: $resp); } } diff --git a/src/Core/Util.php b/src/Core/Util.php index 738ab26..68721b8 100644 --- a/src/Core/Util.php +++ b/src/Core/Util.php @@ -4,8 +4,8 @@ namespace CasParser\Core; -use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; @@ -16,7 +16,9 @@ final class Util public const JSON_ENCODE_FLAGS = JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; - public const JSON_CONTENT_TYPE = '/application\/json/'; + public const JSON_CONTENT_TYPE = '/^application\/(?:vnd(?:.[^.]+)*+)?json(?!l)/'; + + public const JSONL_CONTENT_TYPE = '/^application\/(:?x-(?:n|l)djson)|(:?(?:x-)?jsonl)/'; /** * @return array @@ -45,11 +47,21 @@ public static function array_transform_keys(array $array, array $map): array } /** - * @param callable|int|list|string $key + * @param array $arr + * + * @return array + */ + public static function array_filter_omit(array $arr): array + { + return array_filter($arr, fn ($v, $_) => OMIT !== $v, mode: ARRAY_FILTER_USE_BOTH); + } + + /** + * @param string|int|list|callable $key */ public static function dig( mixed $array, - array|callable|int|string $key + string|int|array|callable $key ): mixed { if (is_callable($key)) { return $key($array); @@ -71,9 +83,9 @@ public static function dig( } /** - * @param list|string $path + * @param string|list $path */ - public static function parsePath(array|string $path): string + public static function parsePath(string|array $path): string { if (is_string($path)) { return $path; @@ -85,7 +97,7 @@ public static function parsePath(array|string $path): string [$template] = $path; - return sprintf($template, ...array_map('rawurlencode', array_slice($path, 1))); + return sprintf($template, ...array_map('rawurlencode', array: array_slice($path, 1))); } /** @@ -124,7 +136,7 @@ public static function joinUri( } /** - * @param array|string> $headers + * @param array|null> $headers */ public static function withSetHeaders( RequestInterface $req, @@ -165,46 +177,8 @@ public static function streamIterator(StreamInterface $stream): \Iterator } /** - * @param null|array|bool|float|int|resource|string|\Traversable< - * mixed - * > $body - * - * @return array{string, \Generator} - */ - public static function encodeMultipartStreaming(mixed $body): array - { - $boundary = rtrim(strtr(base64_encode(random_bytes(60)), '+/', '-_'), '='); - $gen = (function () use ($boundary, $body) { - $closing = []; - - try { - if (is_array($body) || is_object($body)) { - foreach ((array) $body as $key => $val) { - foreach (static::writeMultipartChunk(boundary: $boundary, key: $key, val: $val, closing: $closing) as $chunk) { - yield $chunk; - } - } - } else { - foreach (static::writeMultipartChunk(boundary: $boundary, key: null, val: $body, closing: $closing) as $chunk) { - yield $chunk; - } - } - - yield "--{$boundary}--\r\n"; - } finally { - foreach ($closing as $c) { - $c(); - } - } - })(); - - return [$boundary, $gen]; - } - - /** - * @param null|array|bool|float|int|resource|string|\Traversable< - * mixed - * > $body + * @param bool|int|float|string|resource|\Traversable|array|null $body */ public static function withSetBody( StreamFactoryInterface $factory, @@ -265,13 +239,13 @@ public static function decodeLines(\Iterator $stream): \Iterator /** * @param \Iterator $lines * - * @return \Iterator< + * @return \Generator< * array{ - * event?: null|string, data?: null|string, id?: null|string, retry?: null|int + * event?: string|null, data?: string|null, id?: string|null, retry?: int|null * }, * > */ - public static function decodeSSE(\Iterator $lines): \Iterator + public static function decodeSSE(\Iterator $lines): \Generator { $blank = ['event' => null, 'data' => null, 'id' => null, 'retry' => null]; $acc = []; @@ -328,18 +302,38 @@ public static function decodeSSE(\Iterator $lines): \Iterator } } - public static function decodeContent(MessageInterface $rsp): mixed + public static function decodeJson(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } + + public static function decodeContent(ResponseInterface $rsp): mixed { + if (204 == $rsp->getStatusCode()) { + return null; + } + $content_type = $rsp->getHeaderLine('Content-Type'); $body = $rsp->getBody(); - if (preg_match(self::JSON_CONTENT_TYPE, $content_type)) { + if (preg_match(self::JSON_CONTENT_TYPE, subject: $content_type)) { $json = $body->getContents(); - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + return self::decodeJson($json); } - if (str_contains($content_type, 'text/event-stream')) { + if (preg_match(self::JSONL_CONTENT_TYPE, subject: $content_type)) { + $it = self::streamIterator($body); + $lines = self::decodeLines($it); + + return (function () use ($lines) { + foreach ($lines as $line) { + yield static::decodeJson($line); + } + })(); + } + + if (str_contains($content_type, needle: 'text/event-stream')) { $it = self::streamIterator($body); $lines = self::decodeLines($it); @@ -349,6 +343,11 @@ public static function decodeContent(MessageInterface $rsp): mixed return self::streamIterator($body); } + public static function prettyEncodeJson(mixed $obj): string + { + return json_encode($obj, flags: JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) ?: ''; + } + /** * @param list $closing * @@ -407,4 +406,40 @@ private static function writeMultipartChunk( yield $chunk; } } + + /** + * @param bool|int|float|string|resource|\Traversable|array|null $body + * + * @return array{string, \Generator} + */ + private static function encodeMultipartStreaming(mixed $body): array + { + $boundary = rtrim(strtr(base64_encode(random_bytes(60)), '+/', '-_'), '='); + $gen = (function () use ($boundary, $body) { + $closing = []; + + try { + if (is_array($body) || is_object($body)) { + foreach ((array) $body as $key => $val) { + foreach (static::writeMultipartChunk(boundary: $boundary, key: $key, val: $val, closing: $closing) as $chunk) { + yield $chunk; + } + } + } else { + foreach (static::writeMultipartChunk(boundary: $boundary, key: null, val: $body, closing: $closing) as $chunk) { + yield $chunk; + } + } + + yield "--{$boundary}--\r\n"; + } finally { + foreach ($closing as $c) { + $c(); + } + } + })(); + + return [$boundary, $gen]; + } } diff --git a/src/Errors/APIStatusError.php b/src/Errors/APIStatusError.php deleted file mode 100644 index cdb1ec7..0000000 --- a/src/Errors/APIStatusError.php +++ /dev/null @@ -1,53 +0,0 @@ -response = $response; - $this->status = $response->getStatusCode(); - $message |= json_encode( - ['status' => $this->status, 'body' => $body], - flags: Util::JSON_ENCODE_FLAGS, - ); - parent::__construct(request: $request, message: $message, previous: $previous); - } - - public static function from( - mixed $body, - RequestInterface $request, - ResponseInterface $response - ): self { - $status = $response->getStatusCode(); - - $cls = match (true) { - 400 === $status => BadRequestError::class, - 401 === $status => AuthenticationError::class, - 403 === $status => PermissionDeniedError::class, - 404 === $status => NotFoundError::class, - 409 === $status => ConflictError::class, - 422 === $status => UnprocessableEntityError::class, - 429 === $status => RateLimitError::class, - $status >= 500 => InternalServerError::class, - default => APIStatusError::class - }; - - return new $cls(body: $body, request: $request, response: $response); - } -} diff --git a/src/Errors/AuthenticationError.php b/src/Errors/AuthenticationError.php deleted file mode 100644 index 762b707..0000000 --- a/src/Errors/AuthenticationError.php +++ /dev/null @@ -1,9 +0,0 @@ ->|null, + * extraQueryParams?: array|null, + * extraBodyParams?: mixed, + * transporter?: ClientInterface|null, + * uriFactory?: UriFactoryInterface|null, + * streamFactory?: StreamFactoryInterface|null, + * requestFactory?: RequestFactoryInterface|null, + * } + * @phpstan-type request_opts = null|RequestOptions|request_options + */ +final class RequestOptions implements BaseModel { - public const DEFAULT_TIMEOUT = 60; + /** @use SdkModel */ + use SdkModel; + + #[Property] + public float $timeout = 60; + + #[Property] + public int $maxRetries = 2; + + #[Property] + public float $initialRetryDelay = 0.5; + + #[Property] + public float $maxRetryDelay = 8.0; + + /** @var array|null>|null $extraHeaders */ + #[Property(optional: true)] + public ?array $extraHeaders; - public const DEFAULT_MAX_RETRIES = 2; + /** @var array|null $extraQueryParams */ + #[Property(optional: true)] + public ?array $extraQueryParams; - public const DEFAULT_INITIAL_RETRYDELAY = 0.5; + #[Property(optional: true)] + public mixed $extraBodyParams; - public const DEFAULT_MAX_RETRY_DELAY = 8.0; + #[Property(optional: true)] + public ?ClientInterface $transporter; + + #[Property(optional: true)] + public ?UriFactoryInterface $uriFactory; + + #[Property(optional: true)] + public ?StreamFactoryInterface $streamFactory; + + #[Property(optional: true)] + public ?RequestFactoryInterface $requestFactory; + + public function __construct() + { + $this->initialize(); + } /** - * @param list $extraHeaders - * @param list $extraQueryParams - * @param list $extraBodyParams + * @param request_opts|null $options */ - public function __construct( - public float $timeout = self::DEFAULT_TIMEOUT, - public int $maxRetries = self::DEFAULT_MAX_RETRIES, - public float $initialRetryDelay = self::DEFAULT_INITIAL_RETRYDELAY, - public float $maxRetryDelay = self::DEFAULT_MAX_RETRY_DELAY, - public array $extraHeaders = [], - public array $extraQueryParams = [], - public array $extraBodyParams = [], - ) {} + public static function parse(RequestOptions|array|null ...$options): self + { + $parsed = array_map(static fn ($o) => $o instanceof self ? $o->toArray() : $o ?? [], array: $options); + + return self::with(...array_merge(...$parsed)); // @phpstan-ignore-line + } /** - * @return array{ - * timeout: float, - * maxRetries: int, - * initialRetryDelay: float, - * maxRetryDelay: float, - * extraHeaders: list, - * extraQueryParams: list, - * extraBodyParams: list, - * } + * @param array|null>|null $extraHeaders + * @param array|null $extraQueryParams + * @param mixed|Omit $extraBodyParams */ - public function __serialize(): array + public static function with( + ?float $timeout = null, + ?int $maxRetries = null, + ?float $initialRetryDelay = null, + ?float $maxRetryDelay = null, + ?array $extraHeaders = null, + ?array $extraQueryParams = null, + mixed $extraBodyParams = omit, + ?ClientInterface $transporter = null, + ?UriFactoryInterface $uriFactory = null, + ?StreamFactoryInterface $streamFactory = null, + ?RequestFactoryInterface $requestFactory = null, + ): self { + $obj = new self; + + null !== $timeout && $obj->timeout = $timeout; + null !== $maxRetries && $obj->maxRetries = $maxRetries; + null !== $initialRetryDelay && $obj->initialRetryDelay = $initialRetryDelay; + null !== $maxRetryDelay && $obj->maxRetryDelay = $maxRetryDelay; + null !== $extraHeaders && $obj->extraHeaders = $extraHeaders; + null !== $extraQueryParams && $obj->extraQueryParams = $extraQueryParams; + omit !== $extraBodyParams && $obj->extraBodyParams = $extraBodyParams; + null !== $transporter && $obj->transporter = $transporter; + null !== $uriFactory && $obj->uriFactory = $uriFactory; + null !== $streamFactory && $obj->streamFactory = $streamFactory; + null !== $requestFactory && $obj->requestFactory = $requestFactory; + + return $obj; + } + + public function withTimeout(float $timeout): self + { + $obj = clone $this; + $obj->timeout = $timeout; + + return $obj; + } + + public function withMaxRetries(int $maxRetries): self + { + $obj = clone $this; + $obj->maxRetries = $maxRetries; + + return $obj; + } + + public function withInitialRetryDelay(float $initialRetryDelay): self { - return [ - 'timeout' => $this->timeout, - 'maxRetries' => $this->maxRetries, - 'initialRetryDelay' => $this->initialRetryDelay, - 'maxRetryDelay' => $this->maxRetryDelay, - 'extraHeaders' => $this->extraHeaders, - 'extraQueryParams' => $this->extraQueryParams, - 'extraBodyParams' => $this->extraBodyParams, - ]; + $obj = clone $this; + $obj->initialRetryDelay = $initialRetryDelay; + + return $obj; + } + + public function withMaxRetryDelay(float $maxRetryDelay): self + { + $obj = clone $this; + $obj->maxRetryDelay = $maxRetryDelay; + + return $obj; } /** - * @param array{ - * timeout?: null|float, - * maxRetries?: null|int, - * initialRetryDelay?: null|float, - * maxRetryDelay?: null|float, - * extraHeaders?: null|list, - * extraQueryParams?: null|list, - * extraBodyParams?: null|list, - * } $data + * @param array|null> $extraHeaders */ - public function __unserialize(array $data): void + public function withExtraHeaders(array $extraHeaders): self { - $this->timeout = $data['timeout'] ?? self::DEFAULT_TIMEOUT; - $this - ->maxRetries = $data['maxRetries'] ?? self::DEFAULT_MAX_RETRIES - ; - $this - ->initialRetryDelay = $data[ - 'initialRetryDelay' - ] ?? self::DEFAULT_INITIAL_RETRYDELAY - ; - $this->maxRetryDelay = $data[ - 'maxRetryDelay' - ] ?? self::DEFAULT_MAX_RETRY_DELAY; - $this->extraHeaders = $data[ - 'extraHeaders' - ] ?? []; - $this->extraQueryParams = $data['extraQueryParams'] ?? []; - $this - ->extraBodyParams = $data['extraBodyParams'] ?? [] - ; + $obj = clone $this; + $obj->extraHeaders = $extraHeaders; + + return $obj; } /** - * @param null|array{ - * timeout?: null|float, - * maxRetries?: null|int, - * initialRetryDelay?: null|float, - * maxRetryDelay?: null|float, - * extraHeaders?: null|list, - * extraQueryParams?: null|list, - * extraBodyParams?: null|list, - * }|RequestOptions $options + * @param array $extraQueryParams */ - public static function parse(null|array|RequestOptions $options): self + public function withExtraQueryParams(array $extraQueryParams): self + { + $obj = clone $this; + $obj->extraQueryParams = $extraQueryParams; + + return $obj; + } + + public function withExtraBodyParams(mixed $extraBodyParams): self { - if (is_null($options)) { - return new self; - } + $obj = clone $this; + $obj->extraBodyParams = $extraBodyParams; - if ($options instanceof self) { - return $options; - } + return $obj; + } + + public function withTransporter(ClientInterface $transporter): self + { + $obj = clone $this; + $obj->transporter = $transporter; + + return $obj; + } + + public function withUriFactory(UriFactoryInterface $uriFactory): self + { + $obj = clone $this; + $obj->uriFactory = $uriFactory; + + return $obj; + } + + public function withStreamFactory( + StreamFactoryInterface $streamFactory + ): self { + $obj = clone $this; + $obj->streamFactory = $streamFactory; + + return $obj; + } - $opts = new self; - $opts->__unserialize($options); + public function withRequestFactory( + RequestFactoryInterface $requestFactory + ): self { + $obj = clone $this; + $obj->requestFactory = $requestFactory; - return $opts; + return $obj; } } diff --git a/tests/Core/TestModel.php b/tests/Core/TestModel.php index 8e03729..4d24c06 100644 --- a/tests/Core/TestModel.php +++ b/tests/Core/TestModel.php @@ -3,7 +3,7 @@ namespace Tests\Core; use CasParser\Core\Attributes\Api; -use CasParser\Core\Concerns\Model; +use CasParser\Core\Concerns\SdkModel; use CasParser\Core\Contracts\BaseModel; use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\Attributes\Test; @@ -11,7 +11,8 @@ class TestModel implements BaseModel { - use Model; + /** @use SdkModel> */ + use SdkModel; #[Api] public string $name; @@ -19,7 +20,7 @@ class TestModel implements BaseModel #[Api('age_years')] public int $ageYears; - /** @var null|list */ + /** @var list|null */ #[Api(optional: true)] public ?array $friends; @@ -27,7 +28,7 @@ class TestModel implements BaseModel public ?string $owner; /** - * @param null|list $friends + * @param list|null $friends */ public function __construct( string $name, @@ -35,13 +36,12 @@ public function __construct( ?string $owner, ?array $friends = null, ) { + $this->initialize(); + $this->name = $name; $this->ageYears = $ageYears; $this->owner = $owner; - self::introspect(); - $this->unsetOptionalProperties(); - null != $friends && $this->friends = $friends; } } diff --git a/tests/Resources/CasGeneratorTest.php b/tests/Services/CasGeneratorTest.php similarity index 77% rename from tests/Resources/CasGeneratorTest.php rename to tests/Services/CasGeneratorTest.php index 32e6826..12120b4 100644 --- a/tests/Resources/CasGeneratorTest.php +++ b/tests/Services/CasGeneratorTest.php @@ -1,8 +1,7 @@ markTestSkipped('Prism tests are disabled'); } - $params = CasGeneratorGenerateCasParams::with( + $result = $this->client->casGenerator->generateCas( email: 'user@example.com', fromDate: '2023-01-01', password: 'Abcdefghi12$', toDate: '2023-12-31', ); - $result = $this->client->casGenerator->generateCas($params); $this->assertTrue(true); // @phpstan-ignore-line } @@ -52,15 +50,12 @@ public function testGenerateCasWithOptionalParams(): void $this->markTestSkipped('Prism tests are disabled'); } - $params = CasGeneratorGenerateCasParams::with( + $result = $this->client->casGenerator->generateCas( email: 'user@example.com', fromDate: '2023-01-01', password: 'Abcdefghi12$', toDate: '2023-12-31', - casAuthority: 'kfintech', - panNo: 'ABCDE1234F', ); - $result = $this->client->casGenerator->generateCas($params); $this->assertTrue(true); // @phpstan-ignore-line } diff --git a/tests/Resources/CasParserTest.php b/tests/Services/CasParserTest.php similarity index 69% rename from tests/Resources/CasParserTest.php rename to tests/Services/CasParserTest.php index 61d9206..a6cd2a3 100644 --- a/tests/Resources/CasParserTest.php +++ b/tests/Services/CasParserTest.php @@ -1,11 +1,7 @@ markTestSkipped('Prism tests are disabled'); } - $params = (new CasParserCamsKfintechParams); - $result = $this->client->casParser->camsKfintech($params); + $result = $this->client->casParser->camsKfintech(); $this->assertTrue(true); // @phpstan-ignore-line } @@ -50,8 +45,7 @@ public function testCdsl(): void $this->markTestSkipped('Prism tests are disabled'); } - $params = (new CasParserCdslParams); - $result = $this->client->casParser->cdsl($params); + $result = $this->client->casParser->cdsl(); $this->assertTrue(true); // @phpstan-ignore-line } @@ -63,8 +57,7 @@ public function testNsdl(): void $this->markTestSkipped('Prism tests are disabled'); } - $params = (new CasParserNsdlParams); - $result = $this->client->casParser->nsdl($params); + $result = $this->client->casParser->nsdl(); $this->assertTrue(true); // @phpstan-ignore-line } @@ -76,8 +69,7 @@ public function testSmartParse(): void $this->markTestSkipped('Prism tests are disabled'); } - $params = (new CasParserSmartParseParams); - $result = $this->client->casParser->smartParse($params); + $result = $this->client->casParser->smartParse(); $this->assertTrue(true); // @phpstan-ignore-line }