diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml new file mode 100644 index 0000000..aeb54bd --- /dev/null +++ b/.github/workflows/release-doctor.yml @@ -0,0 +1,22 @@ +name: Release Doctor +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + release_doctor: + name: release doctor + runs-on: ubuntu-latest + if: github.repository == 'ScrapeGraphAI/scrapegraphai-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 + env: + PACKAGIST_USERNAME: ${{ secrets.SCRAPEGRAPHAI_PACKAGIST_USERNAME || secrets.PACKAGIST_USERNAME }} + PACKAGIST_SAFE_KEY: ${{ secrets.SCRAPEGRAPHAI_PACKAGIST_SAFE_KEY || secrets.PACKAGIST_SAFE_KEY }} 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 1332969..3d2ac0b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.1" + ".": "0.1.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 636c6f9..6804ffb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 15 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/scrapegraphai%2Fscrapegraphai-969ebada41127057e4cda129b2e7206224743b5c7fd33aa8ae062ff71b775ac9.yml -openapi_spec_hash: 2b2c2c684e6f6885398efca5f2b1f854 -config_hash: 30d69c79e34a1ea6a0405573ce30d927 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/scrapegraphai%2Fscrapegraphai-633fdeab6abaefbe666099e8f86ce6b2acc9dacff1c33a80813bb04e8e437229.yml +openapi_spec_hash: f41ec90694ca8e7233bd20cc7ff1afbf +config_hash: 6889576ba0fdc14f2c71cea09a60a0f6 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cbc5a86 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,70 @@ +# Changelog + +## 0.1.0 (2025-10-11) + +Full Changelog: [v0.0.1...v0.1.0](https://github.com/ScrapeGraphAI/scrapegraphai-php/compare/v0.0.1...v0.1.0) + +### ⚠ BREAKING CHANGES + +* expose services and service contracts +* 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 + +* add files ([adb926e](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/adb926eb9cfb18594355fa39f642a41a76af19a8)) +* add files ([f728b05](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/f728b05e4bcd0b29355c77cf529d2f18b5cbce11)) +* **client:** add raw methods ([2c62976](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/2c62976824aa8bc2b4119990a4253bd5918852a3)) +* **client:** add streaming ([d191c29](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/d191c29f3e1889640e16911918041bd3850fd4e8)) +* **client:** improve error handling ([b3eab16](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/b3eab16fb85eafac7eba187d23d9fc9a5678fd15)) +* **client:** support raw responses ([7ea4904](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/7ea49045827081bda452412b5ef967924b61cd76)) +* **client:** use named parameters in methods ([2a938ad](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/2a938ad3e8e2658d53813570165fca69ebca554d)) +* **client:** use real enums ([196fd29](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/196fd2985d799749f11af3ef4df7e29f30a4455f)) +* **client:** use with for constructors ([d00f77a](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/d00f77a024ea0faa3f08b2fcdcac387b5c248c1a)) +* ensure `->toArray()` benefits from structural typing ([205ab97](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/205ab97f9f2bc8ca38e9847850163701827777dc)) +* expose services and service contracts ([fcb58d5](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/fcb58d5574acc36d7c2e0e41c2b973c7b6ff1613)) +* pagination field rename, and basic streaming docs ([d79ffc9](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/d79ffc9ea439c191f0ffed61f2752e7bfe47b00c)) +* **php:** differentiate null and omit ([f19a57a](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/f19a57af690dba72f269a940930da3efd35d4b2c)) +* **php:** rename internal types ([c55bbd7](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/c55bbd74aa39be1cf5ab1997fa140d4e92f96de3)) +* **refactor:** clean up pagination, errors, as well as request methods ([7135820](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/713582082c5123e8152bbdb5896926ae58496095)) +* **refactor:** namespacing cleanup ([8c75ed0](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/8c75ed0c279a15e9051ac891e69a5fa2c9e7be30)) +* rename errors to exceptions ([b568600](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/b568600fb25d39cbd315e98dc8654360383523fe)) +* use builders for RequestOptions ([c2300dc](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/c2300dcdc4d9370eb060b04ee25601572821f59c)) + + +### Bug Fixes + +* add create release workflow ([7361bd6](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/7361bd6438c71f72ca9e11290c008beeb04e390a)) +* basic pagination should work ([ecebf37](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/ecebf37bdf9a4b6ede506820789805c8d4ae4f9e)) +* **ci:** release doctor workflow ([1eb0061](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/1eb0061420ae6088f5d3323fd46a80a9c12d2783)) +* **client:** elide null named parameters ([7297553](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/7297553957c8444178ca2dce6c0c89fd743a0a1e)) +* minor bugs ([d905655](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/d90565586eb26ccf73f67961856133e9c0bc90fd)) +* remove inaccurate `license` field in composer.json ([1cfba5f](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/1cfba5fa543a792ecf6651259309247fa3ce726b)) +* streaming internals ([2516566](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/25165664e3d3c7fd4f11f72589f89baa1305eb56)) + + +### Chores + +* add additional php doc tags ([3a18c70](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/3a18c70daa9fa2d26264429cddd2b092bfcd5473)) +* add license ([d3fcfed](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/d3fcfede9458ad6d62b76363dec9d87e8a4d0469)) +* cleanup streaming ([372bf28](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/372bf283cdd910023fd38ecbfb16570870450afd)) +* configure new SDK language ([57c78ba](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/57c78ba863893bb956cfc44b9a34037a52d7d4cf)) +* **docs:** update readme formatting ([ecf153b](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/ecf153b12a488ad416d25e3b494e523ffd08f2c8)) +* document parameter object usage ([707cff0](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/707cff08ced7c6f1da574965a81e4b30a531b391)) +* fix lints in UnionOf ([5e8ce6f](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/5e8ce6f7137f9ce2a2bf3fce709cd363f4b8c196)) +* improve model annotations ([585d282](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/585d282dfdc9c34081e6cb44c0c83256037be12f)) +* **internal:** refactor base client internals ([ec19244](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/ec1924477e3428e9bc066a7ef7473084f06a768c)) +* **internal:** refactored internal codepaths ([d3577db](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/d3577db6255af45ab790915545f4b8044e90c921)) +* intuitively order union types ([0cd048d](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/0cd048d4ce590110cdf01edc1649d4ef2e707a6d)) +* make more targeted phpstan ignores ([c33963e](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/c33963e7a8f9f8fbd461ca989e027974da1e8476)) +* readme improvements ([c38da93](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/c38da939ecdc0ee7a092a58faf41e63a92b61b11)) +* refactor methods ([ddd88d8](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/ddd88d881ca5924d6d2f25e6ae4e4656e7ff5722)) +* refactor request options ([3898066](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/389806624293626a1246486efa870095a959eca5)) +* **refactor:** simplify base page interface ([77b2f76](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/77b2f7692824f3d6d7ff151704174cfe550b1339)) +* remove `php-http/multipart-stream-builder` as a required dependency ([03a02b4](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/03a02b472282af3977cab5be1296edee8a8750ce)) +* remove type aliases ([586c954](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/586c9548945e0522a71f888803bc3c9e7804ccba)) +* simplify model initialization ([4a166fc](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/4a166fcc6584323b68d900e6809453a8d0eb1bcd)) +* update SDK settings ([f566b27](https://github.com/ScrapeGraphAI/scrapegraphai-php/commit/f566b27a274db61218c98bc775642fec51799888)) diff --git a/README.md b/README.md index 193884d..126bda9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ > > This library has not yet been exhaustively tested in production environments and may be missing some features you'd expect in a stable release. As we continue development, there may be breaking changes that require updates to your code. > -> **We'd love your feedback!** Please share any suggestions, bug reports, feature requests, or general thoughts by [filing an issue](https://www.github.com/stainless-sdks/scrapegraphai-php/issues/new). +> **We'd love your feedback!** Please share any suggestions, bug reports, feature requests, or general thoughts by [filing an issue](https://www.github.com/ScrapeGraphAI/scrapegraphai-php/issues/new). The Scrapegraphai PHP library provides convenient access to the Scrapegraphai REST API from any PHP 8.1.0+ application. @@ -19,12 +19,14 @@ The REST API documentation can be found on [scrapegraphai.com](https://scrapegra To use this package, install via Composer by adding the following to your application's `composer.json`: + + ```json { "repositories": [ { "type": "vcs", - "url": "git@github.com:stainless-sdks/scrapegraphai-php.git" + "url": "git@github.com:ScrapeGraphAI/scrapegraphai-php.git" } ], "require": { @@ -33,68 +35,76 @@ 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 smartscraper->create( userPrompt: "Extract the product name, price, and description" ); -$completedSmartscraper = $client->smartscraper->create($params); var_dump($completedSmartscraper->request_id); ``` +### 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 `Scrapegraphai\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 `Scrapegraphai\Core\Exceptions\APIException` will be thrown: ```php smartscraper->create($params); -} catch (APIConnectionError $e) { - echo "The server could not be reached", PHP_EOL; - var_dump($e->getPrevious()); + $completedSmartscraper = $client->smartscraper->create( + userPrompt: "Extract the product name, price, and description" + ); +} 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 @@ -102,25 +112,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 smartscraper - ->create($params, new RequestOptions(maxRetries: 5)); +$result = $client->smartscraper->create( + userPrompt: "Extract the product name, price, and description", + requestOptions: RequestOptions::with(maxRetries: 5), +); ``` ## Advanced concepts @@ -131,22 +138,16 @@ $result = $client 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 smartscraper - ->create( - $params, - new RequestOptions( +$completedSmartscraper = $client->smartscraper->create( + userPrompt: "Extract the product name, price, and description", + requestOptions: RequestOptions::with( extraQueryParams: ["my_query_parameter" => "value"], extraBodyParams: ["my_body_parameter" => "value"], extraHeaders: ["my-header" => "value"], @@ -236,4 +237,4 @@ PHP 8.1.0 or higher. ## Contributing -See [the contributing documentation](https://github.com/stainless-sdks/scrapegraphai-php/tree/main/CONTRIBUTING.md). +See [the contributing documentation](https://github.com/ScrapeGraphAI/scrapegraphai-php/tree/main/CONTRIBUTING.md). 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 17df3ea..8a62463 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,9 @@ { "$schema": "https://getcomposer.org/schema.json", + "license": "Apache-2.0", "autoload": { "files": [ + "src/Core.php", "src/Client.php" ], "psr-4": { @@ -25,13 +27,11 @@ "preferred-install": "dist", "sort-packages": true }, - "license": "APACHE-2.0", "description": "Scrapegraphai PHP SDK", "name": "org-placeholder/scrapegraphai", "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 +48,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/scripts/lint b/scripts/lint index 6d629c2..13f2f01 100755 --- a/scripts/lint +++ b/scripts/lint @@ -5,4 +5,4 @@ set -e cd -- "$(dirname -- "$0")/.." echo "==> Running PHPStan" -exec -- ./vendor/bin/phpstan analyse --memory-limit=1G +exec -- ./vendor/bin/phpstan analyse --memory-limit=2G diff --git a/src/Client.php b/src/Client.php index daf0fc9..9cf7405 100644 --- a/src/Client.php +++ b/src/Client.php @@ -4,53 +4,89 @@ namespace Scrapegraphai; +use Http\Discovery\Psr17FactoryDiscovery; +use Http\Discovery\Psr18ClientDiscovery; use Scrapegraphai\Core\BaseClient; -use Scrapegraphai\Crawl\CrawlService; -use Scrapegraphai\Credits\CreditsService; -use Scrapegraphai\Feedback\FeedbackService; -use Scrapegraphai\GenerateSchema\GenerateSchemaService; -use Scrapegraphai\Healthz\HealthzService; -use Scrapegraphai\Markdownify\MarkdownifyService; -use Scrapegraphai\Searchscraper\SearchscraperService; -use Scrapegraphai\Smartscraper\SmartscraperService; -use Scrapegraphai\Validate\ValidateService; +use Scrapegraphai\Services\CrawlService; +use Scrapegraphai\Services\CreditsService; +use Scrapegraphai\Services\FeedbackService; +use Scrapegraphai\Services\GenerateSchemaService; +use Scrapegraphai\Services\HealthzService; +use Scrapegraphai\Services\MarkdownifyService; +use Scrapegraphai\Services\SearchscraperService; +use Scrapegraphai\Services\SmartscraperService; +use Scrapegraphai\Services\ValidateService; class Client extends BaseClient { public string $apiKey; + /** + * @api + */ public SmartscraperService $smartscraper; + /** + * @api + */ public MarkdownifyService $markdownify; + /** + * @api + */ public SearchscraperService $searchscraper; + /** + * @api + */ public GenerateSchemaService $generateSchema; + /** + * @api + */ public CrawlService $crawl; + /** + * @api + */ public CreditsService $credits; + /** + * @api + */ public ValidateService $validate; + /** + * @api + */ public FeedbackService $feedback; + /** + * @api + */ public HealthzService $healthz; public function __construct(?string $apiKey = null, ?string $baseUrl = null) { $this->apiKey = (string) ($apiKey ?? getenv('SCRAPEGRAPHAI_API_KEY')); - $base = $baseUrl ?? getenv( + $baseUrl ??= getenv( 'SCRAPEGRAPHAI_BASE_URL' ) ?: 'https://api.scrapegraphai.com/v1'; + $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, + baseUrl: $baseUrl, + options: $options, ); $this->smartscraper = new SmartscraperService($this); diff --git a/src/Contracts/CrawlContract.php b/src/Contracts/CrawlContract.php deleted file mode 100644 index beeb74e..0000000 --- a/src/Contracts/CrawlContract.php +++ /dev/null @@ -1,37 +0,0 @@ -, steps?: list - * }|MarkdownifyConvertParams $params - */ - public function convert( - array|MarkdownifyConvertParams $params, - ?RequestOptions $requestOptions = null, - ): CompletedMarkdownify; - - public function retrieveStatus( - string $requestID, - ?RequestOptions $requestOptions = null - ): CompletedMarkdownify|FailedMarkdownifyResponse; -} diff --git a/src/Contracts/SearchscraperContract.php b/src/Contracts/SearchscraperContract.php deleted file mode 100644 index 2f7a099..0000000 --- a/src/Contracts/SearchscraperContract.php +++ /dev/null @@ -1,31 +0,0 @@ -, - * numResults?: int, - * outputSchema?: mixed, - * }|SearchscraperCreateParams $params - */ - public function create( - array|SearchscraperCreateParams $params, - ?RequestOptions $requestOptions = null, - ): CompletedSearchScraper; - - public function retrieveStatus( - string $requestID, - ?RequestOptions $requestOptions = null - ): CompletedSearchScraper|FailedSearchScraperResponse; -} diff --git a/src/Contracts/SmartscraperContract.php b/src/Contracts/SmartscraperContract.php deleted file mode 100644 index 9b99f80..0000000 --- a/src/Contracts/SmartscraperContract.php +++ /dev/null @@ -1,41 +0,0 @@ -, - * headers?: array, - * numberOfScrolls?: int, - * outputSchema?: mixed, - * renderHeavyJs?: bool, - * steps?: list, - * totalPages?: int, - * websiteHTML?: string, - * websiteURL?: string, - * }|SmartscraperCreateParams $params - */ - public function create( - array|SmartscraperCreateParams $params, - ?RequestOptions $requestOptions = null, - ): CompletedSmartscraper; - - public function retrieve( - string $requestID, - ?RequestOptions $requestOptions = null - ): CompletedSmartscraper|FailedSmartscraper; - - public function list( - ?RequestOptions $requestOptions = null - ): CompletedSmartscraper|FailedSmartscraper; -} diff --git a/src/Core.php b/src/Core.php new file mode 100644 index 0000000..299a3d2 --- /dev/null +++ b/src/Core.php @@ -0,0 +1,9 @@ + $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 a8b0d69..3ee64ba 100644 --- a/src/Core/Attributes/Api.php +++ b/src/Core/Attributes/Api.php @@ -6,6 +6,9 @@ use Scrapegraphai\Core\Conversion\Contracts\Converter; use Scrapegraphai\Core\Conversion\Contracts\ConverterSource; +use Scrapegraphai\Core\Conversion\EnumOf; +use Scrapegraphai\Core\Conversion\ListOf; +use Scrapegraphai\Core\Conversion\MapOf; /** * @internal @@ -13,24 +16,51 @@ #[\Attribute(\Attribute::TARGET_PROPERTY)] final class Api { - /** - * @var null|class-string|Converter|string - */ - public readonly null|Converter|string $type; + /** @var class-string|Converter|string|null */ + public readonly Converter|string|null $type; + + /** @var array */ + private static array $enumConverters = []; /** - * @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<\BackedEnum>|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; + $type ??= $union; + if (null !== $list) { + $type ??= new ListOf($list); + } + if (null !== $map) { + $type ??= new MapOf($map); + } + if (null !== $enum) { + $type ??= $enum instanceof Converter ? $enum : $this->getEnumConverter($enum); + } + + $this->type = $type; + } + + /** @property class-string<\BackedEnum> $enum */ + private function getEnumConverter(string $enum): Converter + { + if (!isset(self::$enumConverters[$enum])) { + $converter = new EnumOf(array_column($enum::cases(), 'value')); // @phpstan-ignore-line + self::$enumConverters[$enum] = $converter; + } + + return self::$enumConverters[$enum]; } } diff --git a/src/Core/BaseClient.php b/src/Core/BaseClient.php index 04e3a10..9230b1f 100644 --- a/src/Core/BaseClient.php +++ b/src/Core/BaseClient.php @@ -4,8 +4,7 @@ namespace Scrapegraphai\Core; -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; @@ -13,131 +12,161 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UriFactoryInterface; use Psr\Http\Message\UriInterface; -use Scrapegraphai\Errors\APIStatusError; +use Scrapegraphai\Core\Contracts\BasePage; +use Scrapegraphai\Core\Contracts\BaseStream; +use Scrapegraphai\Core\Conversion\Contracts\Converter; +use Scrapegraphai\Core\Conversion\Contracts\ConverterSource; +use Scrapegraphai\Core\Exceptions\APIConnectionException; +use Scrapegraphai\Core\Exceptions\APIStatusException; use Scrapegraphai\RequestOptions; -class BaseClient +/** + * @phpstan-type normalized_request = array{ + * method: string, + * path: string, + * query: array, + * headers: array>, + * body: mixed, + * } + */ +abstract 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); + + if (!is_null($stream)) { + return new $stream( + convert: $convert, + request: $request, + response: $rsp + ); } - return Util::decodeContent($rsp); - } + if (!is_null($page)) { + return new $page( + convert: $convert, + client: $this, + request: $req, + response: $rsp, + options: $opts, + ); + } - /** - * @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::coerceResponse($convert, response: $rsp); + } + + return Util::decodeContent($rsp); } /** @return array */ - protected function authHeaders(): array - { - return []; - } + abstract 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 +175,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 a355f9d..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 6382920..3654e8c 100644 --- a/src/Core/Concerns/Enum.php +++ b/src/Core/Concerns/SdkEnum.php @@ -7,7 +7,10 @@ use Scrapegraphai\Core\Conversion\Contracts\Converter; use Scrapegraphai\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 a3b773a..b08cb78 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..d5f8e01 --- /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..b6ba7e0 --- /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/SdkResponse.php b/src/Core/Concerns/SdkResponse.php new file mode 100644 index 0000000..bcda042 --- /dev/null +++ b/src/Core/Concerns/SdkResponse.php @@ -0,0 +1,29 @@ +_rawResponse = $response; + $instance->__unserialize(Util::decodeContent($response)); // @phpstan-ignore-line + + return $instance; + } + + public function getRawResponse(): ?ResponseInterface + { + return $this->_rawResponse; + } +} 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 e34110a..cd54e03 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 ab70baa..88996d6 100644 --- a/src/Core/Contracts/BasePage.php +++ b/src/Core/Contracts/BasePage.php @@ -4,13 +4,50 @@ namespace Scrapegraphai\Core\Contracts; +use Psr\Http\Message\ResponseInterface; +use Scrapegraphai\Client; +use Scrapegraphai\Core\Conversion\Contracts\Converter; +use Scrapegraphai\Core\Conversion\Contracts\ConverterSource; +use Scrapegraphai\RequestOptions; + /** * @internal + * + * @phpstan-import-type normalized_request from \Scrapegraphai\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, + ResponseInterface $response, + ); + + 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..1e5e69a --- /dev/null +++ b/src/Core/Contracts/BaseStream.php @@ -0,0 +1,32 @@ + + */ +interface BaseStream extends \IteratorAggregate +{ + public function __construct( + Converter|ConverterSource|string $convert, + RequestInterface $request, + ResponseInterface $response, + ); + + /** + * Manually force the stream to close early. + * Iterating through will automatically close as well. + */ + public function close(): void; +} diff --git a/src/Core/Conversion.php b/src/Core/Conversion.php index b1a12ab..4fc8c96 100644 --- a/src/Core/Conversion.php +++ b/src/Core/Conversion.php @@ -4,11 +4,16 @@ namespace Scrapegraphai\Core; +use Psr\Http\Message\ResponseInterface; use Scrapegraphai\Core\Conversion\CoerceState; use Scrapegraphai\Core\Conversion\Contracts\Converter; use Scrapegraphai\Core\Conversion\Contracts\ConverterSource; +use Scrapegraphai\Core\Conversion\Contracts\ResponseConverter; use Scrapegraphai\Core\Conversion\DumpState; +/** + * @internal + */ final class Conversion { public static function dump_unknown(mixed $value, DumpState $state): mixed @@ -38,6 +43,15 @@ public static function dump_unknown(mixed $value, DumpState $state): mixed return $value; } + public static function coerceResponse(Converter|ConverterSource|string $target, ResponseInterface $response): mixed + { + if (is_a($target, ResponseConverter::class, allow_string: true)) { + return $target::fromResponse($response); + } + + return self::coerce($target, Util::decodeContent($response)); + } + public static function coerce(Converter|ConverterSource|string $target, mixed $value, CoerceState $state = new CoerceState): mixed { if ($value instanceof $target) { diff --git a/src/Core/Conversion/Concerns/ArrayOf.php b/src/Core/Conversion/Concerns/ArrayOf.php index ef71a03..46ac460 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/Contracts/ResponseConverter.php b/src/Core/Conversion/Contracts/ResponseConverter.php new file mode 100644 index 0000000..ed64242 --- /dev/null +++ b/src/Core/Conversion/Contracts/ResponseConverter.php @@ -0,0 +1,18 @@ + $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 306c717..8207dbc 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 7098314..f19391a 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 c302fc0..d5dfb9b 100644 --- a/src/Core/Conversion/UnionOf.php +++ b/src/Core/Conversion/UnionOf.php @@ -9,6 +9,9 @@ use Scrapegraphai\Core\Conversion\Contracts\Converter; use Scrapegraphai\Core\Conversion\Contracts\ConverterSource; +/** + * @internal + */ final class UnionOf implements Converter { /** @@ -62,7 +65,7 @@ public function coerce(mixed $value, CoerceState $state): mixed public function dump(mixed $value, DumpState $state): mixed { - if (!is_null($target = $this->resolveVariant(value: $value))) { + if (null !== ($target = $this->resolveVariant(value: $value))) { return Conversion::dump($target, value: $value, state: $state); } @@ -77,13 +80,20 @@ 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; } - if (!is_null($this->discriminator) && is_array($value) && array_key_exists($this->discriminator, array: $value)) { + if ( + null !== $this->discriminator + && is_array($value) + && array_key_exists($this->discriminator, array: $value) + ) { $discriminator = $value[$this->discriminator]; + if (!is_string($discriminator)) { + return null; + } return $this->variants[$discriminator] ?? null; } 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 35e1581..90de671 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..8ed25cf --- /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 67% rename from src/Errors/APITimeoutError.php rename to src/Core/Exceptions/APITimeoutException.php index 3f13097..4db21ac 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 8b780ac..0000000 --- a/src/Core/Pagination/PageRequestOptions.php +++ /dev/null @@ -1,73 +0,0 @@ - @@ -45,11 +52,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 +88,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 +102,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 +141,7 @@ public static function joinUri( } /** - * @param array|string> $headers + * @param array|null> $headers */ public static function withSetHeaders( RequestInterface $req, @@ -165,46 +182,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 +244,9 @@ public static function decodeLines(\Iterator $stream): \Iterator /** * @param \Iterator $lines * - * @return \Iterator< - * array{ - * event?: null|string, data?: null|string, id?: null|string, retry?: null|int - * }, - * > + * @return \Generator */ - 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 +303,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 +344,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 +407,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/Crawl/CrawlGetResultsResponse.php b/src/Crawl/CrawlGetResultsResponse.php new file mode 100644 index 0000000..0d8611c --- /dev/null +++ b/src/Crawl/CrawlGetResultsResponse.php @@ -0,0 +1,122 @@ +, + * taskID?: string, + * traceback?: string|null, + * } + */ +final class CrawlGetResultsResponse implements BaseModel, ResponseConverter +{ + /** @use SdkModel */ + use SdkModel; + + use SdkResponse; + + /** + * Successful crawl results. + * + * @var mixed|string|null $result + */ + #[Api(union: Result::class, optional: true)] + public mixed $result; + + /** @var value-of|null $status */ + #[Api(enum: Status::class, optional: true)] + public ?string $status; + + #[Api('task_id', optional: true)] + public ?string $taskID; + + /** + * Error traceback for failed tasks. + */ + #[Api(nullable: true, optional: true)] + public ?string $traceback; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param mixed|string $result + * @param Status|value-of $status + */ + public static function with( + mixed $result = null, + Status|string|null $status = null, + ?string $taskID = null, + ?string $traceback = null, + ): self { + $obj = new self; + + null !== $result && $obj->result = $result; + null !== $status && $obj['status'] = $status; + null !== $taskID && $obj->taskID = $taskID; + null !== $traceback && $obj->traceback = $traceback; + + return $obj; + } + + /** + * Successful crawl results. + * + * @param mixed|string $result + */ + public function withResult(mixed $result): self + { + $obj = clone $this; + $obj->result = $result; + + return $obj; + } + + /** + * @param Status|value-of $status + */ + public function withStatus(Status|string $status): self + { + $obj = clone $this; + $obj['status'] = $status; + + return $obj; + } + + public function withTaskID(string $taskID): self + { + $obj = clone $this; + $obj->taskID = $taskID; + + return $obj; + } + + /** + * Error traceback for failed tasks. + */ + public function withTraceback(?string $traceback): self + { + $obj = clone $this; + $obj->traceback = $traceback; + + return $obj; + } +} diff --git a/src/Responses/Crawl/CrawlGetResultsResponse/Result.php b/src/Crawl/CrawlGetResultsResponse/Result.php similarity index 55% rename from src/Responses/Crawl/CrawlGetResultsResponse/Result.php rename to src/Crawl/CrawlGetResultsResponse/Result.php index 0982820..4ace754 100644 --- a/src/Responses/Crawl/CrawlGetResultsResponse/Result.php +++ b/src/Crawl/CrawlGetResultsResponse/Result.php @@ -2,24 +2,22 @@ declare(strict_types=1); -namespace Scrapegraphai\Responses\Crawl\CrawlGetResultsResponse; +namespace Scrapegraphai\Crawl\CrawlGetResultsResponse; -use Scrapegraphai\Core\Concerns\Union; +use Scrapegraphai\Core\Concerns\SdkUnion; use Scrapegraphai\Core\Conversion\Contracts\Converter; use Scrapegraphai\Core\Conversion\Contracts\ConverterSource; /** * Successful crawl results. - * - * @phpstan-type result_alias = mixed|string */ final class Result implements ConverterSource { - use Union; + use SdkUnion; /** - * @return array|list + * @return list|array */ public static function variants(): array { diff --git a/src/Crawl/CrawlGetResultsResponse/Status.php b/src/Crawl/CrawlGetResultsResponse/Status.php new file mode 100644 index 0000000..01f20ee --- /dev/null +++ b/src/Crawl/CrawlGetResultsResponse/Status.php @@ -0,0 +1,20 @@ +client->request( - method: 'get', - path: ['crawl/%1$s', $taskID], - options: $requestOptions - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(CrawlGetResultsResponse::class, value: $resp); - } - - /** - * Initiate comprehensive website crawling with sitemap support. - * Supports both AI extraction mode and markdown conversion mode. - * Returns a task ID for async processing. - * - * @param array{ - * url: string, - * depth?: int, - * extractionMode?: bool, - * maxPages?: int, - * prompt?: null|string, - * renderHeavyJs?: bool, - * rules?: Rules, - * schema?: mixed, - * sitemap?: bool, - * }|CrawlStartParams $params - */ - public function start( - array|CrawlStartParams $params, - ?RequestOptions $requestOptions = null - ): CrawlStartResponse { - [$parsed, $options] = CrawlStartParams::parseRequest( - $params, - $requestOptions - ); - $resp = $this->client->request( - method: 'post', - path: 'crawl', - body: (object) $parsed, - options: $options - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(CrawlStartResponse::class, value: $resp); - } -} diff --git a/src/Crawl/CrawlStartParams.php b/src/Crawl/CrawlStartParams.php index e6de992..f52ba5c 100644 --- a/src/Crawl/CrawlStartParams.php +++ b/src/Crawl/CrawlStartParams.php @@ -5,17 +5,30 @@ namespace Scrapegraphai\Crawl; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; -use Scrapegraphai\Core\Concerns\Params; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Concerns\SdkParams; use Scrapegraphai\Core\Contracts\BaseModel; use Scrapegraphai\Crawl\CrawlStartParams\Rules; /** + * An object containing the method's parameters. + * Example usage: + * ``` + * $params = (new CrawlStartParams); // set properties as needed + * $client->crawl->start(...$params->toArray()); + * ``` * Initiate comprehensive website crawling with sitemap support. * Supports both AI extraction mode and markdown conversion mode. * Returns a task ID for async processing. * - * @phpstan-type start_params = array{ + * @method toArray() + * Returns the parameters as an associative array suitable for passing to the client method. + * + * `$client->crawl->start(...$params->toArray());` + * + * @see Scrapegraphai\Crawl->start + * + * @phpstan-type crawl_start_params = array{ * url: string, * depth?: int, * extractionMode?: bool, @@ -29,8 +42,9 @@ */ final class CrawlStartParams implements BaseModel { - use Model; - use Params; + /** @use SdkModel */ + use SdkModel; + use SdkParams; /** * Starting URL for crawling. @@ -59,7 +73,7 @@ final class CrawlStartParams implements BaseModel /** * Extraction prompt (required if extraction_mode is true). */ - #[Api(optional: true)] + #[Api(nullable: true, optional: true)] public ?string $prompt; /** @@ -74,7 +88,7 @@ final class CrawlStartParams implements BaseModel /** * Output schema for extraction. */ - #[Api(optional: true)] + #[Api(nullable: true, optional: true)] public mixed $schema; /** @@ -83,10 +97,23 @@ final class CrawlStartParams implements BaseModel #[Api(optional: true)] public ?bool $sitemap; + /** + * `new CrawlStartParams()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * CrawlStartParams::with(url: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new CrawlStartParams)->withURL(...) + * ``` + */ public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -94,7 +121,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. */ - public static function from( + public static function with( string $url, ?int $depth = null, ?bool $extractionMode = null, @@ -124,87 +151,96 @@ public static function from( /** * Starting URL for crawling. */ - public function setURL(string $url): self + public function withURL(string $url): self { - $this->url = $url; + $obj = clone $this; + $obj->url = $url; - return $this; + return $obj; } /** * Maximum crawl depth from starting URL. */ - public function setDepth(int $depth): self + public function withDepth(int $depth): self { - $this->depth = $depth; + $obj = clone $this; + $obj->depth = $depth; - return $this; + return $obj; } /** * Use AI extraction (true) or markdown conversion (false). */ - public function setExtractionMode(bool $extractionMode): self + public function withExtractionMode(bool $extractionMode): self { - $this->extractionMode = $extractionMode; + $obj = clone $this; + $obj->extractionMode = $extractionMode; - return $this; + return $obj; } /** * Maximum number of pages to crawl. */ - public function setMaxPages(int $maxPages): self + public function withMaxPages(int $maxPages): self { - $this->maxPages = $maxPages; + $obj = clone $this; + $obj->maxPages = $maxPages; - return $this; + return $obj; } /** * Extraction prompt (required if extraction_mode is true). */ - public function setPrompt(?string $prompt): self + public function withPrompt(?string $prompt): self { - $this->prompt = $prompt; + $obj = clone $this; + $obj->prompt = $prompt; - return $this; + return $obj; } /** * Enable heavy JavaScript rendering. */ - public function setRenderHeavyJs(bool $renderHeavyJs): self + public function withRenderHeavyJs(bool $renderHeavyJs): self { - $this->renderHeavyJs = $renderHeavyJs; + $obj = clone $this; + $obj->renderHeavyJs = $renderHeavyJs; - return $this; + return $obj; } - public function setRules(Rules $rules): self + public function withRules(Rules $rules): self { - $this->rules = $rules; + $obj = clone $this; + $obj->rules = $rules; - return $this; + return $obj; } /** * Output schema for extraction. */ - public function setSchema(mixed $schema): self + public function withSchema(mixed $schema): self { - $this->schema = $schema; + $obj = clone $this; + $obj->schema = $schema; - return $this; + return $obj; } /** * Use sitemap for crawling. */ - public function setSitemap(bool $sitemap): self + public function withSitemap(bool $sitemap): self { - $this->sitemap = $sitemap; + $obj = clone $this; + $obj->sitemap = $sitemap; - return $this; + return $obj; } } diff --git a/src/Crawl/CrawlStartParams/Rules.php b/src/Crawl/CrawlStartParams/Rules.php index c352317..5d745c6 100644 --- a/src/Crawl/CrawlStartParams/Rules.php +++ b/src/Crawl/CrawlStartParams/Rules.php @@ -5,23 +5,23 @@ namespace Scrapegraphai\Crawl\CrawlStartParams; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; +use Scrapegraphai\Core\Concerns\SdkModel; use Scrapegraphai\Core\Contracts\BaseModel; -use Scrapegraphai\Core\Conversion\ListOf; /** * @phpstan-type rules_alias = array{exclude?: list, sameDomain?: bool} */ final class Rules implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * URL patterns to exclude from crawling. * - * @var null|list $exclude + * @var list|null $exclude */ - #[Api(type: new ListOf('string'), optional: true)] + #[Api(list: 'string', optional: true)] public ?array $exclude; /** @@ -32,8 +32,7 @@ final class Rules implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -41,9 +40,9 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|list $exclude + * @param list $exclude */ - public static function from( + public static function with( ?array $exclude = null, ?bool $sameDomain = null ): self { @@ -60,20 +59,22 @@ public static function from( * * @param list $exclude */ - public function setExclude(array $exclude): self + public function withExclude(array $exclude): self { - $this->exclude = $exclude; + $obj = clone $this; + $obj->exclude = $exclude; - return $this; + return $obj; } /** * Restrict crawling to same domain. */ - public function setSameDomain(bool $sameDomain): self + public function withSameDomain(bool $sameDomain): self { - $this->sameDomain = $sameDomain; + $obj = clone $this; + $obj->sameDomain = $sameDomain; - return $this; + return $obj; } } diff --git a/src/Responses/Crawl/CrawlStartResponse.php b/src/Crawl/CrawlStartResponse.php similarity index 50% rename from src/Responses/Crawl/CrawlStartResponse.php rename to src/Crawl/CrawlStartResponse.php index 74277a9..5ea9580 100644 --- a/src/Responses/Crawl/CrawlStartResponse.php +++ b/src/Crawl/CrawlStartResponse.php @@ -2,18 +2,23 @@ declare(strict_types=1); -namespace Scrapegraphai\Responses\Crawl; +namespace Scrapegraphai\Crawl; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Concerns\SdkResponse; use Scrapegraphai\Core\Contracts\BaseModel; +use Scrapegraphai\Core\Conversion\Contracts\ResponseConverter; /** - * @phpstan-type crawl_start_response_alias = array{taskID?: string} + * @phpstan-type crawl_start_response = array{taskID?: string} */ -final class CrawlStartResponse implements BaseModel +final class CrawlStartResponse implements BaseModel, ResponseConverter { - use Model; + /** @use SdkModel */ + use SdkModel; + + use SdkResponse; /** * Celery task identifier. @@ -23,8 +28,7 @@ final class CrawlStartResponse implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -32,7 +36,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. */ - public static function from(?string $taskID = null): self + public static function with(?string $taskID = null): self { $obj = new self; @@ -44,10 +48,11 @@ public static function from(?string $taskID = null): self /** * Celery task identifier. */ - public function setTaskID(string $taskID): self + public function withTaskID(string $taskID): self { - $this->taskID = $taskID; + $obj = clone $this; + $obj->taskID = $taskID; - return $this; + return $obj; } } diff --git a/src/Responses/Credits/CreditGetResponse.php b/src/Credits/CreditGetResponse.php similarity index 58% rename from src/Responses/Credits/CreditGetResponse.php rename to src/Credits/CreditGetResponse.php index 6e388c8..5b9d31a 100644 --- a/src/Responses/Credits/CreditGetResponse.php +++ b/src/Credits/CreditGetResponse.php @@ -2,20 +2,25 @@ declare(strict_types=1); -namespace Scrapegraphai\Responses\Credits; +namespace Scrapegraphai\Credits; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Concerns\SdkResponse; use Scrapegraphai\Core\Contracts\BaseModel; +use Scrapegraphai\Core\Conversion\Contracts\ResponseConverter; /** - * @phpstan-type credit_get_response_alias = array{ + * @phpstan-type credit_get_response = array{ * remainingCredits?: int, totalCreditsUsed?: int * } */ -final class CreditGetResponse implements BaseModel +final class CreditGetResponse implements BaseModel, ResponseConverter { - use Model; + /** @use SdkModel */ + use SdkModel; + + use SdkResponse; /** * Number of credits remaining. @@ -31,8 +36,7 @@ final class CreditGetResponse implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -40,7 +44,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. */ - public static function from( + public static function with( ?int $remainingCredits = null, ?int $totalCreditsUsed = null ): self { @@ -55,20 +59,22 @@ public static function from( /** * Number of credits remaining. */ - public function setRemainingCredits(int $remainingCredits): self + public function withRemainingCredits(int $remainingCredits): self { - $this->remainingCredits = $remainingCredits; + $obj = clone $this; + $obj->remainingCredits = $remainingCredits; - return $this; + return $obj; } /** * Total credits consumed. */ - public function setTotalCreditsUsed(int $totalCreditsUsed): self + public function withTotalCreditsUsed(int $totalCreditsUsed): self { - $this->totalCreditsUsed = $totalCreditsUsed; + $obj = clone $this; + $obj->totalCreditsUsed = $totalCreditsUsed; - return $this; + return $obj; } } diff --git a/src/Errors/APIStatusError.php b/src/Errors/APIStatusError.php deleted file mode 100644 index 5de7126..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 116781e..0000000 --- a/src/Errors/AuthenticationError.php +++ /dev/null @@ -1,9 +0,0 @@ -client->request( - method: 'post', - path: 'feedback', - body: (object) $parsed, - options: $options, - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(FeedbackSubmitResponse::class, value: $resp); - } -} diff --git a/src/Feedback/FeedbackSubmitParams.php b/src/Feedback/FeedbackSubmitParams.php index f7d25ef..b3d2cac 100644 --- a/src/Feedback/FeedbackSubmitParams.php +++ b/src/Feedback/FeedbackSubmitParams.php @@ -5,21 +5,35 @@ namespace Scrapegraphai\Feedback; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; -use Scrapegraphai\Core\Concerns\Params; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Concerns\SdkParams; use Scrapegraphai\Core\Contracts\BaseModel; /** + * An object containing the method's parameters. + * Example usage: + * ``` + * $params = (new FeedbackSubmitParams); // set properties as needed + * $client->feedback->submit(...$params->toArray()); + * ``` * Submit feedback for a specific request. * - * @phpstan-type submit_params = array{ + * @method toArray() + * Returns the parameters as an associative array suitable for passing to the client method. + * + * `$client->feedback->submit(...$params->toArray());` + * + * @see Scrapegraphai\Feedback->submit + * + * @phpstan-type feedback_submit_params = array{ * rating: int, requestID: string, feedbackText?: string|null * } */ final class FeedbackSubmitParams implements BaseModel { - use Model; - use Params; + /** @use SdkModel */ + use SdkModel; + use SdkParams; /** * Rating score. @@ -36,13 +50,26 @@ final class FeedbackSubmitParams implements BaseModel /** * Optional feedback comments. */ - #[Api('feedback_text', optional: true)] + #[Api('feedback_text', nullable: true, optional: true)] public ?string $feedbackText; + /** + * `new FeedbackSubmitParams()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * FeedbackSubmitParams::with(rating: ..., requestID: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new FeedbackSubmitParams)->withRating(...)->withRequestID(...) + * ``` + */ public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -50,7 +77,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. */ - public static function from( + public static function with( int $rating, string $requestID, ?string $feedbackText = null @@ -68,30 +95,33 @@ public static function from( /** * Rating score. */ - public function setRating(int $rating): self + public function withRating(int $rating): self { - $this->rating = $rating; + $obj = clone $this; + $obj->rating = $rating; - return $this; + return $obj; } /** * Request to provide feedback for. */ - public function setRequestID(string $requestID): self + public function withRequestID(string $requestID): self { - $this->requestID = $requestID; + $obj = clone $this; + $obj->requestID = $requestID; - return $this; + return $obj; } /** * Optional feedback comments. */ - public function setFeedbackText(?string $feedbackText): self + public function withFeedbackText(?string $feedbackText): self { - $this->feedbackText = $feedbackText; + $obj = clone $this; + $obj->feedbackText = $feedbackText; - return $this; + return $obj; } } diff --git a/src/Responses/Feedback/FeedbackSubmitResponse.php b/src/Feedback/FeedbackSubmitResponse.php similarity index 56% rename from src/Responses/Feedback/FeedbackSubmitResponse.php rename to src/Feedback/FeedbackSubmitResponse.php index d7e6791..739ca1f 100644 --- a/src/Responses/Feedback/FeedbackSubmitResponse.php +++ b/src/Feedback/FeedbackSubmitResponse.php @@ -2,23 +2,28 @@ declare(strict_types=1); -namespace Scrapegraphai\Responses\Feedback; +namespace Scrapegraphai\Feedback; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Concerns\SdkResponse; use Scrapegraphai\Core\Contracts\BaseModel; +use Scrapegraphai\Core\Conversion\Contracts\ResponseConverter; /** - * @phpstan-type feedback_submit_response_alias = array{ + * @phpstan-type feedback_submit_response = array{ * feedbackID?: string, * feedbackTimestamp?: \DateTimeInterface, * message?: string, * requestID?: string, * } */ -final class FeedbackSubmitResponse implements BaseModel +final class FeedbackSubmitResponse implements BaseModel, ResponseConverter { - use Model; + /** @use SdkModel */ + use SdkModel; + + use SdkResponse; #[Api('feedback_id', optional: true)] public ?string $feedbackID; @@ -34,8 +39,7 @@ final class FeedbackSubmitResponse implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -43,7 +47,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. */ - public static function from( + public static function with( ?string $feedbackID = null, ?\DateTimeInterface $feedbackTimestamp = null, ?string $message = null, @@ -59,32 +63,36 @@ public static function from( return $obj; } - public function setFeedbackID(string $feedbackID): self + public function withFeedbackID(string $feedbackID): self { - $this->feedbackID = $feedbackID; + $obj = clone $this; + $obj->feedbackID = $feedbackID; - return $this; + return $obj; } - public function setFeedbackTimestamp( + public function withFeedbackTimestamp( \DateTimeInterface $feedbackTimestamp ): self { - $this->feedbackTimestamp = $feedbackTimestamp; + $obj = clone $this; + $obj->feedbackTimestamp = $feedbackTimestamp; - return $this; + return $obj; } - public function setMessage(string $message): self + public function withMessage(string $message): self { - $this->message = $message; + $obj = clone $this; + $obj->message = $message; - return $this; + return $obj; } - public function setRequestID(string $requestID): self + public function withRequestID(string $requestID): self { - $this->requestID = $requestID; + $obj = clone $this; + $obj->requestID = $requestID; - return $this; + return $obj; } } diff --git a/src/GenerateSchema/GenerateSchemaCreateParams.php b/src/GenerateSchema/GenerateSchemaCreateParams.php index ff0158c..f7cfde5 100644 --- a/src/GenerateSchema/GenerateSchemaCreateParams.php +++ b/src/GenerateSchema/GenerateSchemaCreateParams.php @@ -5,20 +5,36 @@ namespace Scrapegraphai\GenerateSchema; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; -use Scrapegraphai\Core\Concerns\Params; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Concerns\SdkParams; use Scrapegraphai\Core\Contracts\BaseModel; /** + * An object containing the method's parameters. + * Example usage: + * ``` + * $params = (new GenerateSchemaCreateParams); // set properties as needed + * $client->generateSchema->create(...$params->toArray()); + * ``` * Generate or modify JSON schemas based on natural language descriptions. * Can create new schemas or extend existing ones. * - * @phpstan-type create_params = array{userPrompt: string, existingSchema?: mixed} + * @method toArray() + * Returns the parameters as an associative array suitable for passing to the client method. + * + * `$client->generateSchema->create(...$params->toArray());` + * + * @see Scrapegraphai\GenerateSchema->create + * + * @phpstan-type generate_schema_create_params = array{ + * userPrompt: string, existingSchema?: mixed + * } */ final class GenerateSchemaCreateParams implements BaseModel { - use Model; - use Params; + /** @use SdkModel */ + use SdkModel; + use SdkParams; /** * Natural language description of desired schema. @@ -29,13 +45,26 @@ final class GenerateSchemaCreateParams implements BaseModel /** * Existing schema to modify or extend. */ - #[Api('existing_schema', optional: true)] + #[Api('existing_schema', nullable: true, optional: true)] public mixed $existingSchema; + /** + * `new GenerateSchemaCreateParams()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * GenerateSchemaCreateParams::with(userPrompt: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new GenerateSchemaCreateParams)->withUserPrompt(...) + * ``` + */ public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -43,7 +72,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. */ - public static function from( + public static function with( string $userPrompt, mixed $existingSchema = null ): self { @@ -59,20 +88,22 @@ public static function from( /** * Natural language description of desired schema. */ - public function setUserPrompt(string $userPrompt): self + public function withUserPrompt(string $userPrompt): self { - $this->userPrompt = $userPrompt; + $obj = clone $this; + $obj->userPrompt = $userPrompt; - return $this; + return $obj; } /** * Existing schema to modify or extend. */ - public function setExistingSchema(mixed $existingSchema): self + public function withExistingSchema(mixed $existingSchema): self { - $this->existingSchema = $existingSchema; + $obj = clone $this; + $obj->existingSchema = $existingSchema; - return $this; + return $obj; } } diff --git a/src/GenerateSchema/GenerateSchemaGetResponse.php b/src/GenerateSchema/GenerateSchemaGetResponse.php new file mode 100644 index 0000000..4718cf2 --- /dev/null +++ b/src/GenerateSchema/GenerateSchemaGetResponse.php @@ -0,0 +1,28 @@ +|array + */ + public static function variants(): array + { + return [ + CompletedSchemaGenerationResponse::class, + FailedSchemaGenerationResponse::class, + ]; + } +} diff --git a/src/Responses/GenerateSchema/GenerateSchemaGetResponse/CompletedSchemaGenerationResponse.php b/src/GenerateSchema/GenerateSchemaGetResponse/CompletedSchemaGenerationResponse.php similarity index 50% rename from src/Responses/GenerateSchema/GenerateSchemaGetResponse/CompletedSchemaGenerationResponse.php rename to src/GenerateSchema/GenerateSchemaGetResponse/CompletedSchemaGenerationResponse.php index c1cd234..5970da7 100644 --- a/src/Responses/GenerateSchema/GenerateSchemaGetResponse/CompletedSchemaGenerationResponse.php +++ b/src/GenerateSchema/GenerateSchemaGetResponse/CompletedSchemaGenerationResponse.php @@ -2,28 +2,29 @@ declare(strict_types=1); -namespace Scrapegraphai\Responses\GenerateSchema\GenerateSchemaGetResponse; +namespace Scrapegraphai\GenerateSchema\GenerateSchemaGetResponse; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; +use Scrapegraphai\Core\Concerns\SdkModel; use Scrapegraphai\Core\Contracts\BaseModel; -use Scrapegraphai\Responses\GenerateSchema\GenerateSchemaGetResponse\CompletedSchemaGenerationResponse\Status; +use Scrapegraphai\GenerateSchema\GenerateSchemaGetResponse\CompletedSchemaGenerationResponse\Status; /** - * @phpstan-type completed_schema_generation_response_alias = array{ + * @phpstan-type completed_schema_generation_response = array{ * error?: string|null, * generatedSchema?: mixed, * refinedPrompt?: string, * requestID?: string, - * status?: Status::*, + * status?: value-of, * userPrompt?: string, * } */ final class CompletedSchemaGenerationResponse implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; - #[Api(optional: true)] + #[Api(nullable: true, optional: true)] public ?string $error; #[Api('generated_schema', optional: true)] @@ -35,7 +36,7 @@ final class CompletedSchemaGenerationResponse implements BaseModel #[Api('request_id', optional: true)] public ?string $requestID; - /** @var null|Status::* $status */ + /** @var value-of|null $status */ #[Api(enum: Status::class, optional: true)] public ?string $status; @@ -44,8 +45,7 @@ final class CompletedSchemaGenerationResponse implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -53,14 +53,14 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|Status::* $status + * @param Status|value-of $status */ - public static function from( + public static function with( ?string $error = null, mixed $generatedSchema = null, ?string $refinedPrompt = null, ?string $requestID = null, - ?string $status = null, + Status|string|null $status = null, ?string $userPrompt = null, ): self { $obj = new self; @@ -69,54 +69,60 @@ public static function from( null !== $generatedSchema && $obj->generatedSchema = $generatedSchema; null !== $refinedPrompt && $obj->refinedPrompt = $refinedPrompt; null !== $requestID && $obj->requestID = $requestID; - null !== $status && $obj->status = $status; + null !== $status && $obj['status'] = $status; null !== $userPrompt && $obj->userPrompt = $userPrompt; return $obj; } - public function setError(?string $error): self + public function withError(?string $error): self { - $this->error = $error; + $obj = clone $this; + $obj->error = $error; - return $this; + return $obj; } - public function setGeneratedSchema(mixed $generatedSchema): self + public function withGeneratedSchema(mixed $generatedSchema): self { - $this->generatedSchema = $generatedSchema; + $obj = clone $this; + $obj->generatedSchema = $generatedSchema; - return $this; + return $obj; } - public function setRefinedPrompt(string $refinedPrompt): self + public function withRefinedPrompt(string $refinedPrompt): self { - $this->refinedPrompt = $refinedPrompt; + $obj = clone $this; + $obj->refinedPrompt = $refinedPrompt; - return $this; + return $obj; } - public function setRequestID(string $requestID): self + public function withRequestID(string $requestID): self { - $this->requestID = $requestID; + $obj = clone $this; + $obj->requestID = $requestID; - return $this; + return $obj; } /** - * @param Status::* $status + * @param Status|value-of $status */ - public function setStatus(string $status): self + public function withStatus(Status|string $status): self { - $this->status = $status; + $obj = clone $this; + $obj['status'] = $status; - return $this; + return $obj; } - public function setUserPrompt(string $userPrompt): self + public function withUserPrompt(string $userPrompt): self { - $this->userPrompt = $userPrompt; + $obj = clone $this; + $obj->userPrompt = $userPrompt; - return $this; + return $obj; } } diff --git a/src/GenerateSchema/GenerateSchemaGetResponse/CompletedSchemaGenerationResponse/Status.php b/src/GenerateSchema/GenerateSchemaGetResponse/CompletedSchemaGenerationResponse/Status.php new file mode 100644 index 0000000..76dee4c --- /dev/null +++ b/src/GenerateSchema/GenerateSchemaGetResponse/CompletedSchemaGenerationResponse/Status.php @@ -0,0 +1,10 @@ +, + * userPrompt?: string, + * } + */ +final class FailedSchemaGenerationResponse implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + #[Api(optional: true)] + public ?string $error; + + #[Api('generated_schema', nullable: true, optional: true)] + public mixed $generatedSchema; + + #[Api('refined_prompt', nullable: true, optional: true)] + public ?string $refinedPrompt; + + #[Api('request_id', optional: true)] + public ?string $requestID; + + /** @var value-of|null $status */ + #[Api(enum: Status::class, optional: true)] + public ?string $status; + + #[Api('user_prompt', optional: true)] + public ?string $userPrompt; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Status|value-of $status + */ + public static function with( + ?string $error = null, + mixed $generatedSchema = null, + ?string $refinedPrompt = null, + ?string $requestID = null, + Status|string|null $status = null, + ?string $userPrompt = null, + ): self { + $obj = new self; + + null !== $error && $obj->error = $error; + null !== $generatedSchema && $obj->generatedSchema = $generatedSchema; + null !== $refinedPrompt && $obj->refinedPrompt = $refinedPrompt; + null !== $requestID && $obj->requestID = $requestID; + null !== $status && $obj['status'] = $status; + null !== $userPrompt && $obj->userPrompt = $userPrompt; + + return $obj; + } + + public function withError(string $error): self + { + $obj = clone $this; + $obj->error = $error; + + return $obj; + } + + public function withGeneratedSchema(mixed $generatedSchema): self + { + $obj = clone $this; + $obj->generatedSchema = $generatedSchema; + + return $obj; + } + + public function withRefinedPrompt(?string $refinedPrompt): self + { + $obj = clone $this; + $obj->refinedPrompt = $refinedPrompt; + + return $obj; + } + + public function withRequestID(string $requestID): self + { + $obj = clone $this; + $obj->requestID = $requestID; + + return $obj; + } + + /** + * @param Status|value-of $status + */ + public function withStatus(Status|string $status): self + { + $obj = clone $this; + $obj['status'] = $status; + + return $obj; + } + + public function withUserPrompt(string $userPrompt): self + { + $obj = clone $this; + $obj->userPrompt = $userPrompt; + + return $obj; + } +} diff --git a/src/GenerateSchema/GenerateSchemaGetResponse/FailedSchemaGenerationResponse/Status.php b/src/GenerateSchema/GenerateSchemaGetResponse/FailedSchemaGenerationResponse/Status.php new file mode 100644 index 0000000..b894cca --- /dev/null +++ b/src/GenerateSchema/GenerateSchemaGetResponse/FailedSchemaGenerationResponse/Status.php @@ -0,0 +1,10 @@ +, * userPrompt?: string, * } */ -final class GenerateSchemaNewResponse implements BaseModel +final class GenerateSchemaNewResponse implements BaseModel, ResponseConverter { - use Model; + /** @use SdkModel */ + use SdkModel; - #[Api(optional: true)] + use SdkResponse; + + #[Api(nullable: true, optional: true)] public ?string $error; /** @@ -41,7 +46,7 @@ final class GenerateSchemaNewResponse implements BaseModel #[Api('request_id', optional: true)] public ?string $requestID; - /** @var null|Status::* $status */ + /** @var value-of|null $status */ #[Api(enum: Status::class, optional: true)] public ?string $status; @@ -50,8 +55,7 @@ final class GenerateSchemaNewResponse implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -59,14 +63,14 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|Status::* $status + * @param Status|value-of $status */ - public static function from( + public static function with( ?string $error = null, mixed $generatedSchema = null, ?string $refinedPrompt = null, ?string $requestID = null, - ?string $status = null, + Status|string|null $status = null, ?string $userPrompt = null, ): self { $obj = new self; @@ -75,60 +79,66 @@ public static function from( null !== $generatedSchema && $obj->generatedSchema = $generatedSchema; null !== $refinedPrompt && $obj->refinedPrompt = $refinedPrompt; null !== $requestID && $obj->requestID = $requestID; - null !== $status && $obj->status = $status; + null !== $status && $obj['status'] = $status; null !== $userPrompt && $obj->userPrompt = $userPrompt; return $obj; } - public function setError(?string $error): self + public function withError(?string $error): self { - $this->error = $error; + $obj = clone $this; + $obj->error = $error; - return $this; + return $obj; } /** * Generated JSON schema. */ - public function setGeneratedSchema(mixed $generatedSchema): self + public function withGeneratedSchema(mixed $generatedSchema): self { - $this->generatedSchema = $generatedSchema; + $obj = clone $this; + $obj->generatedSchema = $generatedSchema; - return $this; + return $obj; } /** * Enhanced search prompt generated from user input. */ - public function setRefinedPrompt(string $refinedPrompt): self + public function withRefinedPrompt(string $refinedPrompt): self { - $this->refinedPrompt = $refinedPrompt; + $obj = clone $this; + $obj->refinedPrompt = $refinedPrompt; - return $this; + return $obj; } - public function setRequestID(string $requestID): self + public function withRequestID(string $requestID): self { - $this->requestID = $requestID; + $obj = clone $this; + $obj->requestID = $requestID; - return $this; + return $obj; } /** - * @param Status::* $status + * @param Status|value-of $status */ - public function setStatus(string $status): self + public function withStatus(Status|string $status): self { - $this->status = $status; + $obj = clone $this; + $obj['status'] = $status; - return $this; + return $obj; } - public function setUserPrompt(string $userPrompt): self + public function withUserPrompt(string $userPrompt): self { - $this->userPrompt = $userPrompt; + $obj = clone $this; + $obj->userPrompt = $userPrompt; - return $this; + return $obj; } } diff --git a/src/GenerateSchema/GenerateSchemaNewResponse/Status.php b/src/GenerateSchema/GenerateSchemaNewResponse/Status.php new file mode 100644 index 0000000..373b084 --- /dev/null +++ b/src/GenerateSchema/GenerateSchemaNewResponse/Status.php @@ -0,0 +1,10 @@ +client->request( - method: 'post', - path: 'generate_schema', - body: (object) $parsed, - options: $options, - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(GenerateSchemaNewResponse::class, value: $resp); - } - - /** - * Retrieve the status and results of a schema generation request. - */ - public function retrieve( - string $requestID, - ?RequestOptions $requestOptions = null - ): CompletedSchemaGenerationResponse|FailedSchemaGenerationResponse { - $resp = $this->client->request( - method: 'get', - path: ['generate_schema/%1$s', $requestID], - options: $requestOptions, - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(GenerateSchemaGetResponse::class, value: $resp); - } -} diff --git a/src/Healthz/HealthzCheckResponse.php b/src/Healthz/HealthzCheckResponse.php new file mode 100644 index 0000000..7d79a33 --- /dev/null +++ b/src/Healthz/HealthzCheckResponse.php @@ -0,0 +1,74 @@ +, status?: string + * } + */ +final class HealthzCheckResponse implements BaseModel, ResponseConverter +{ + /** @use SdkModel */ + use SdkModel; + + use SdkResponse; + + /** @var array|null $services */ + #[Api(map: 'string', optional: true)] + public ?array $services; + + #[Api(optional: true)] + public ?string $status; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param array $services + */ + public static function with( + ?array $services = null, + ?string $status = null + ): self { + $obj = new self; + + null !== $services && $obj->services = $services; + null !== $status && $obj->status = $status; + + return $obj; + } + + /** + * @param array $services + */ + public function withServices(array $services): self + { + $obj = clone $this; + $obj->services = $services; + + return $obj; + } + + public function withStatus(string $status): self + { + $obj = clone $this; + $obj->status = $status; + + return $obj; + } +} diff --git a/src/Markdownify/CompletedMarkdownify.php b/src/Markdownify/CompletedMarkdownify.php index 3c535a8..3c7a923 100644 --- a/src/Markdownify/CompletedMarkdownify.php +++ b/src/Markdownify/CompletedMarkdownify.php @@ -5,22 +5,27 @@ namespace Scrapegraphai\Markdownify; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Concerns\SdkResponse; use Scrapegraphai\Core\Contracts\BaseModel; +use Scrapegraphai\Core\Conversion\Contracts\ResponseConverter; use Scrapegraphai\Markdownify\CompletedMarkdownify\Status; /** - * @phpstan-type completed_markdownify_alias = array{ + * @phpstan-type completed_markdownify = array{ * error?: string, * requestID?: string, * result?: string|null, - * status?: Status::*, + * status?: value-of, * websiteURL?: string, * } */ -final class CompletedMarkdownify implements BaseModel +final class CompletedMarkdownify implements BaseModel, ResponseConverter { - use Model; + /** @use SdkModel */ + use SdkModel; + + use SdkResponse; #[Api(optional: true)] public ?string $error; @@ -31,10 +36,10 @@ final class CompletedMarkdownify implements BaseModel /** * Markdown content. */ - #[Api(optional: true)] + #[Api(nullable: true, optional: true)] public ?string $result; - /** @var null|Status::* $status */ + /** @var value-of|null $status */ #[Api(enum: Status::class, optional: true)] public ?string $status; @@ -43,8 +48,7 @@ final class CompletedMarkdownify implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -52,13 +56,13 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|Status::* $status + * @param Status|value-of $status */ - public static function from( + public static function with( ?string $error = null, ?string $requestID = null, ?string $result = null, - ?string $status = null, + Status|string|null $status = null, ?string $websiteURL = null, ): self { $obj = new self; @@ -66,50 +70,55 @@ public static function from( null !== $error && $obj->error = $error; null !== $requestID && $obj->requestID = $requestID; null !== $result && $obj->result = $result; - null !== $status && $obj->status = $status; + null !== $status && $obj['status'] = $status; null !== $websiteURL && $obj->websiteURL = $websiteURL; return $obj; } - public function setError(string $error): self + public function withError(string $error): self { - $this->error = $error; + $obj = clone $this; + $obj->error = $error; - return $this; + return $obj; } - public function setRequestID(string $requestID): self + public function withRequestID(string $requestID): self { - $this->requestID = $requestID; + $obj = clone $this; + $obj->requestID = $requestID; - return $this; + return $obj; } /** * Markdown content. */ - public function setResult(?string $result): self + public function withResult(?string $result): self { - $this->result = $result; + $obj = clone $this; + $obj->result = $result; - return $this; + return $obj; } /** - * @param Status::* $status + * @param Status|value-of $status */ - public function setStatus(string $status): self + public function withStatus(Status|string $status): self { - $this->status = $status; + $obj = clone $this; + $obj['status'] = $status; - return $this; + return $obj; } - public function setWebsiteURL(string $websiteURL): self + public function withWebsiteURL(string $websiteURL): self { - $this->websiteURL = $websiteURL; + $obj = clone $this; + $obj->websiteURL = $websiteURL; - return $this; + return $obj; } } diff --git a/src/Markdownify/CompletedMarkdownify/Status.php b/src/Markdownify/CompletedMarkdownify/Status.php index d6e8ce9..1a370bd 100644 --- a/src/Markdownify/CompletedMarkdownify/Status.php +++ b/src/Markdownify/CompletedMarkdownify/Status.php @@ -4,19 +4,11 @@ namespace Scrapegraphai\Markdownify\CompletedMarkdownify; -use Scrapegraphai\Core\Concerns\Enum; -use Scrapegraphai\Core\Conversion\Contracts\ConverterSource; - -/** - * @phpstan-type status_alias = Status::* - */ -final class Status implements ConverterSource +enum Status: string { - use Enum; - - public const QUEUED = 'queued'; + case QUEUED = 'queued'; - public const PROCESSING = 'processing'; + case PROCESSING = 'processing'; - public const COMPLETED = 'completed'; + case COMPLETED = 'completed'; } diff --git a/src/Markdownify/MarkdownifyConvertParams.php b/src/Markdownify/MarkdownifyConvertParams.php index 74eec44..7e7eefd 100644 --- a/src/Markdownify/MarkdownifyConvertParams.php +++ b/src/Markdownify/MarkdownifyConvertParams.php @@ -5,23 +5,35 @@ namespace Scrapegraphai\Markdownify; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; -use Scrapegraphai\Core\Concerns\Params; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Concerns\SdkParams; use Scrapegraphai\Core\Contracts\BaseModel; -use Scrapegraphai\Core\Conversion\ListOf; -use Scrapegraphai\Core\Conversion\MapOf; /** + * An object containing the method's parameters. + * Example usage: + * ``` + * $params = (new MarkdownifyConvertParams); // set properties as needed + * $client->markdownify->convert(...$params->toArray()); + * ``` * Convert web page content to clean Markdown format. * - * @phpstan-type convert_params = array{ + * @method toArray() + * Returns the parameters as an associative array suitable for passing to the client method. + * + * `$client->markdownify->convert(...$params->toArray());` + * + * @see Scrapegraphai\Markdownify->convert + * + * @phpstan-type markdownify_convert_params = array{ * websiteURL: string, headers?: array, steps?: list * } */ final class MarkdownifyConvertParams implements BaseModel { - use Model; - use Params; + /** @use SdkModel */ + use SdkModel; + use SdkParams; /** * URL to convert to markdown. @@ -29,22 +41,35 @@ final class MarkdownifyConvertParams implements BaseModel #[Api('website_url')] public string $websiteURL; - /** @var null|array $headers */ - #[Api(type: new MapOf('string'), optional: true)] + /** @var array|null $headers */ + #[Api(map: 'string', optional: true)] public ?array $headers; /** * Interaction steps before conversion. * - * @var null|list $steps + * @var list|null $steps */ - #[Api(type: new ListOf('string'), optional: true)] + #[Api(list: 'string', optional: true)] public ?array $steps; + /** + * `new MarkdownifyConvertParams()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * MarkdownifyConvertParams::with(websiteURL: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new MarkdownifyConvertParams)->withWebsiteURL(...) + * ``` + */ public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -52,10 +77,10 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|array $headers - * @param null|list $steps + * @param array $headers + * @param list $steps */ - public static function from( + public static function with( string $websiteURL, ?array $headers = null, ?array $steps = null @@ -73,21 +98,23 @@ public static function from( /** * URL to convert to markdown. */ - public function setWebsiteURL(string $websiteURL): self + public function withWebsiteURL(string $websiteURL): self { - $this->websiteURL = $websiteURL; + $obj = clone $this; + $obj->websiteURL = $websiteURL; - return $this; + return $obj; } /** * @param array $headers */ - public function setHeaders(array $headers): self + public function withHeaders(array $headers): self { - $this->headers = $headers; + $obj = clone $this; + $obj->headers = $headers; - return $this; + return $obj; } /** @@ -95,10 +122,11 @@ public function setHeaders(array $headers): self * * @param list $steps */ - public function setSteps(array $steps): self + public function withSteps(array $steps): self { - $this->steps = $steps; + $obj = clone $this; + $obj->steps = $steps; - return $this; + return $obj; } } diff --git a/src/Markdownify/MarkdownifyGetStatusResponse.php b/src/Markdownify/MarkdownifyGetStatusResponse.php new file mode 100644 index 0000000..c8fd76f --- /dev/null +++ b/src/Markdownify/MarkdownifyGetStatusResponse.php @@ -0,0 +1,24 @@ +|array + */ + public static function variants(): array + { + return [CompletedMarkdownify::class, FailedMarkdownifyResponse::class]; + } +} diff --git a/src/Markdownify/MarkdownifyGetStatusResponse/FailedMarkdownifyResponse.php b/src/Markdownify/MarkdownifyGetStatusResponse/FailedMarkdownifyResponse.php new file mode 100644 index 0000000..1cc820a --- /dev/null +++ b/src/Markdownify/MarkdownifyGetStatusResponse/FailedMarkdownifyResponse.php @@ -0,0 +1,114 @@ +, + * websiteURL?: string, + * } + */ +final class FailedMarkdownifyResponse implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + #[Api(optional: true)] + public ?string $error; + + #[Api('request_id', optional: true)] + public ?string $requestID; + + #[Api(nullable: true, optional: true)] + public ?string $result; + + /** @var value-of|null $status */ + #[Api(enum: Status::class, optional: true)] + public ?string $status; + + #[Api('website_url', optional: true)] + public ?string $websiteURL; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Status|value-of $status + */ + public static function with( + ?string $error = null, + ?string $requestID = null, + ?string $result = null, + Status|string|null $status = null, + ?string $websiteURL = null, + ): self { + $obj = new self; + + null !== $error && $obj->error = $error; + null !== $requestID && $obj->requestID = $requestID; + null !== $result && $obj->result = $result; + null !== $status && $obj['status'] = $status; + null !== $websiteURL && $obj->websiteURL = $websiteURL; + + return $obj; + } + + public function withError(string $error): self + { + $obj = clone $this; + $obj->error = $error; + + return $obj; + } + + public function withRequestID(string $requestID): self + { + $obj = clone $this; + $obj->requestID = $requestID; + + return $obj; + } + + public function withResult(?string $result): self + { + $obj = clone $this; + $obj->result = $result; + + return $obj; + } + + /** + * @param Status|value-of $status + */ + public function withStatus(Status|string $status): self + { + $obj = clone $this; + $obj['status'] = $status; + + return $obj; + } + + public function withWebsiteURL(string $websiteURL): self + { + $obj = clone $this; + $obj->websiteURL = $websiteURL; + + return $obj; + } +} diff --git a/src/Markdownify/MarkdownifyGetStatusResponse/FailedMarkdownifyResponse/Status.php b/src/Markdownify/MarkdownifyGetStatusResponse/FailedMarkdownifyResponse/Status.php new file mode 100644 index 0000000..89511c8 --- /dev/null +++ b/src/Markdownify/MarkdownifyGetStatusResponse/FailedMarkdownifyResponse/Status.php @@ -0,0 +1,10 @@ +, steps?: list - * }|MarkdownifyConvertParams $params - */ - public function convert( - array|MarkdownifyConvertParams $params, - ?RequestOptions $requestOptions = null, - ): CompletedMarkdownify { - [$parsed, $options] = MarkdownifyConvertParams::parseRequest( - $params, - $requestOptions - ); - $resp = $this->client->request( - method: 'post', - path: 'markdownify', - body: (object) $parsed, - options: $options, - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(CompletedMarkdownify::class, value: $resp); - } - - /** - * Retrieve the status and results of a markdown conversion. - */ - public function retrieveStatus( - string $requestID, - ?RequestOptions $requestOptions = null - ): CompletedMarkdownify|FailedMarkdownifyResponse { - $resp = $this->client->request( - method: 'get', - path: ['markdownify/%1$s', $requestID], - options: $requestOptions, - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce( - MarkdownifyGetStatusResponse::class, - value: $resp - ); - } -} diff --git a/src/RequestOptions.php b/src/RequestOptions.php index 801bed6..41cca88 100644 --- a/src/RequestOptions.php +++ b/src/RequestOptions.php @@ -4,113 +4,216 @@ namespace Scrapegraphai; -class RequestOptions +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\UriFactoryInterface; +use Scrapegraphai\Core\Attributes\Api as Property; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Contracts\BaseModel; +use Scrapegraphai\Core\Implementation\Omit; + +use const Scrapegraphai\Core\OMIT as omit; + +/** + * @phpstan-type request_options = array{ + * timeout?: float|null, + * maxRetries?: int|null, + * initialRetryDelay?: float|null, + * maxRetryDelay?: float|null, + * extraHeaders?: array>|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/src/Responses/Crawl/CrawlGetResultsResponse.php b/src/Responses/Crawl/CrawlGetResultsResponse.php deleted file mode 100644 index 6c6f4f2..0000000 --- a/src/Responses/Crawl/CrawlGetResultsResponse.php +++ /dev/null @@ -1,114 +0,0 @@ -unsetOptionalProperties(); - } - - /** - * Construct an instance from the required parameters. - * - * You must use named parameters to construct any parameters with a default value. - * - * @param null|mixed|string $result - * @param null|Status::* $status - */ - public static function from( - mixed $result = null, - ?string $status = null, - ?string $taskID = null, - ?string $traceback = null, - ): self { - $obj = new self; - - null !== $result && $obj->result = $result; - null !== $status && $obj->status = $status; - null !== $taskID && $obj->taskID = $taskID; - null !== $traceback && $obj->traceback = $traceback; - - return $obj; - } - - /** - * Successful crawl results. - * - * @param mixed|string $result - */ - public function setResult(mixed $result): self - { - $this->result = $result; - - return $this; - } - - /** - * @param Status::* $status - */ - public function setStatus(string $status): self - { - $this->status = $status; - - return $this; - } - - public function setTaskID(string $taskID): self - { - $this->taskID = $taskID; - - return $this; - } - - /** - * Error traceback for failed tasks. - */ - public function setTraceback(?string $traceback): self - { - $this->traceback = $traceback; - - return $this; - } -} diff --git a/src/Responses/Crawl/CrawlGetResultsResponse/Status.php b/src/Responses/Crawl/CrawlGetResultsResponse/Status.php deleted file mode 100644 index 17b18ac..0000000 --- a/src/Responses/Crawl/CrawlGetResultsResponse/Status.php +++ /dev/null @@ -1,28 +0,0 @@ -|list - */ - public static function variants(): array - { - return [ - CompletedSchemaGenerationResponse::class, - FailedSchemaGenerationResponse::class, - ]; - } -} diff --git a/src/Responses/GenerateSchema/GenerateSchemaGetResponse/CompletedSchemaGenerationResponse/Status.php b/src/Responses/GenerateSchema/GenerateSchemaGetResponse/CompletedSchemaGenerationResponse/Status.php deleted file mode 100644 index 6c3f821..0000000 --- a/src/Responses/GenerateSchema/GenerateSchemaGetResponse/CompletedSchemaGenerationResponse/Status.php +++ /dev/null @@ -1,18 +0,0 @@ -unsetOptionalProperties(); - } - - /** - * Construct an instance from the required parameters. - * - * You must use named parameters to construct any parameters with a default value. - * - * @param null|Status::* $status - */ - public static function from( - ?string $error = null, - mixed $generatedSchema = null, - ?string $refinedPrompt = null, - ?string $requestID = null, - ?string $status = null, - ?string $userPrompt = null, - ): self { - $obj = new self; - - null !== $error && $obj->error = $error; - null !== $generatedSchema && $obj->generatedSchema = $generatedSchema; - null !== $refinedPrompt && $obj->refinedPrompt = $refinedPrompt; - null !== $requestID && $obj->requestID = $requestID; - null !== $status && $obj->status = $status; - null !== $userPrompt && $obj->userPrompt = $userPrompt; - - return $obj; - } - - public function setError(string $error): self - { - $this->error = $error; - - return $this; - } - - public function setGeneratedSchema(mixed $generatedSchema): self - { - $this->generatedSchema = $generatedSchema; - - return $this; - } - - public function setRefinedPrompt(?string $refinedPrompt): self - { - $this->refinedPrompt = $refinedPrompt; - - return $this; - } - - public function setRequestID(string $requestID): self - { - $this->requestID = $requestID; - - return $this; - } - - /** - * @param Status::* $status - */ - public function setStatus(string $status): self - { - $this->status = $status; - - return $this; - } - - public function setUserPrompt(string $userPrompt): self - { - $this->userPrompt = $userPrompt; - - return $this; - } -} diff --git a/src/Responses/GenerateSchema/GenerateSchemaGetResponse/FailedSchemaGenerationResponse/Status.php b/src/Responses/GenerateSchema/GenerateSchemaGetResponse/FailedSchemaGenerationResponse/Status.php deleted file mode 100644 index 9315df6..0000000 --- a/src/Responses/GenerateSchema/GenerateSchemaGetResponse/FailedSchemaGenerationResponse/Status.php +++ /dev/null @@ -1,18 +0,0 @@ -, status?: string - * } - */ -final class HealthzCheckResponse implements BaseModel -{ - use Model; - - /** @var null|array $services */ - #[Api(type: new MapOf('string'), optional: true)] - public ?array $services; - - #[Api(optional: true)] - public ?string $status; - - public function __construct() - { - self::introspect(); - $this->unsetOptionalProperties(); - } - - /** - * Construct an instance from the required parameters. - * - * You must use named parameters to construct any parameters with a default value. - * - * @param null|array $services - */ - public static function from( - ?array $services = null, - ?string $status = null - ): self { - $obj = new self; - - null !== $services && $obj->services = $services; - null !== $status && $obj->status = $status; - - return $obj; - } - - /** - * @param array $services - */ - public function setServices(array $services): self - { - $this->services = $services; - - return $this; - } - - public function setStatus(string $status): self - { - $this->status = $status; - - return $this; - } -} diff --git a/src/Responses/Markdownify/MarkdownifyGetStatusResponse.php b/src/Responses/Markdownify/MarkdownifyGetStatusResponse.php deleted file mode 100644 index d5b9236..0000000 --- a/src/Responses/Markdownify/MarkdownifyGetStatusResponse.php +++ /dev/null @@ -1,28 +0,0 @@ -|list - */ - public static function variants(): array - { - return [CompletedMarkdownify::class, FailedMarkdownifyResponse::class]; - } -} diff --git a/src/Responses/Markdownify/MarkdownifyGetStatusResponse/FailedMarkdownifyResponse.php b/src/Responses/Markdownify/MarkdownifyGetStatusResponse/FailedMarkdownifyResponse.php deleted file mode 100644 index 11f2a2f..0000000 --- a/src/Responses/Markdownify/MarkdownifyGetStatusResponse/FailedMarkdownifyResponse.php +++ /dev/null @@ -1,109 +0,0 @@ -unsetOptionalProperties(); - } - - /** - * Construct an instance from the required parameters. - * - * You must use named parameters to construct any parameters with a default value. - * - * @param null|Status::* $status - */ - public static function from( - ?string $error = null, - ?string $requestID = null, - ?string $result = null, - ?string $status = null, - ?string $websiteURL = null, - ): self { - $obj = new self; - - null !== $error && $obj->error = $error; - null !== $requestID && $obj->requestID = $requestID; - null !== $result && $obj->result = $result; - null !== $status && $obj->status = $status; - null !== $websiteURL && $obj->websiteURL = $websiteURL; - - return $obj; - } - - public function setError(string $error): self - { - $this->error = $error; - - return $this; - } - - public function setRequestID(string $requestID): self - { - $this->requestID = $requestID; - - return $this; - } - - public function setResult(?string $result): self - { - $this->result = $result; - - return $this; - } - - /** - * @param Status::* $status - */ - public function setStatus(string $status): self - { - $this->status = $status; - - return $this; - } - - public function setWebsiteURL(string $websiteURL): self - { - $this->websiteURL = $websiteURL; - - return $this; - } -} diff --git a/src/Responses/Markdownify/MarkdownifyGetStatusResponse/FailedMarkdownifyResponse/Status.php b/src/Responses/Markdownify/MarkdownifyGetStatusResponse/FailedMarkdownifyResponse/Status.php deleted file mode 100644 index 7be697e..0000000 --- a/src/Responses/Markdownify/MarkdownifyGetStatusResponse/FailedMarkdownifyResponse/Status.php +++ /dev/null @@ -1,18 +0,0 @@ -|list - */ - public static function variants(): array - { - return [CompletedSearchScraper::class, FailedSearchScraperResponse::class]; - } -} diff --git a/src/Responses/Searchscraper/SearchscraperGetStatusResponse/FailedSearchScraperResponse.php b/src/Responses/Searchscraper/SearchscraperGetStatusResponse/FailedSearchScraperResponse.php deleted file mode 100644 index a2f0ba1..0000000 --- a/src/Responses/Searchscraper/SearchscraperGetStatusResponse/FailedSearchScraperResponse.php +++ /dev/null @@ -1,141 +0,0 @@ -, - * requestID?: string, - * result?: mixed, - * status?: Status::*, - * userPrompt?: string, - * } - */ -final class FailedSearchScraperResponse implements BaseModel -{ - use Model; - - #[Api(optional: true)] - public ?string $error; - - #[Api('num_results', optional: true)] - public ?int $numResults; - - /** @var null|list $referenceURLs */ - #[Api('reference_urls', type: new ListOf('string'), optional: true)] - public ?array $referenceURLs; - - #[Api('request_id', optional: true)] - public ?string $requestID; - - #[Api(optional: true)] - public mixed $result; - - /** @var null|Status::* $status */ - #[Api(enum: Status::class, optional: true)] - public ?string $status; - - #[Api('user_prompt', optional: true)] - public ?string $userPrompt; - - public function __construct() - { - self::introspect(); - $this->unsetOptionalProperties(); - } - - /** - * Construct an instance from the required parameters. - * - * You must use named parameters to construct any parameters with a default value. - * - * @param null|list $referenceURLs - * @param null|Status::* $status - */ - public static function from( - ?string $error = null, - ?int $numResults = null, - ?array $referenceURLs = null, - ?string $requestID = null, - mixed $result = null, - ?string $status = null, - ?string $userPrompt = null, - ): self { - $obj = new self; - - null !== $error && $obj->error = $error; - null !== $numResults && $obj->numResults = $numResults; - null !== $referenceURLs && $obj->referenceURLs = $referenceURLs; - null !== $requestID && $obj->requestID = $requestID; - null !== $result && $obj->result = $result; - null !== $status && $obj->status = $status; - null !== $userPrompt && $obj->userPrompt = $userPrompt; - - return $obj; - } - - public function setError(string $error): self - { - $this->error = $error; - - return $this; - } - - public function setNumResults(int $numResults): self - { - $this->numResults = $numResults; - - return $this; - } - - /** - * @param list $referenceURLs - */ - public function setReferenceURLs(array $referenceURLs): self - { - $this->referenceURLs = $referenceURLs; - - return $this; - } - - public function setRequestID(string $requestID): self - { - $this->requestID = $requestID; - - return $this; - } - - public function setResult(mixed $result): self - { - $this->result = $result; - - return $this; - } - - /** - * @param Status::* $status - */ - public function setStatus(string $status): self - { - $this->status = $status; - - return $this; - } - - public function setUserPrompt(string $userPrompt): self - { - $this->userPrompt = $userPrompt; - - return $this; - } -} diff --git a/src/Responses/Searchscraper/SearchscraperGetStatusResponse/FailedSearchScraperResponse/Status.php b/src/Responses/Searchscraper/SearchscraperGetStatusResponse/FailedSearchScraperResponse/Status.php deleted file mode 100644 index 36824a3..0000000 --- a/src/Responses/Searchscraper/SearchscraperGetStatusResponse/FailedSearchScraperResponse/Status.php +++ /dev/null @@ -1,18 +0,0 @@ -|list - */ - public static function variants(): array - { - return [CompletedSmartscraper::class, FailedSmartscraper::class]; - } -} diff --git a/src/Responses/Smartscraper/SmartscraperListResponse.php b/src/Responses/Smartscraper/SmartscraperListResponse.php deleted file mode 100644 index 3297bac..0000000 --- a/src/Responses/Smartscraper/SmartscraperListResponse.php +++ /dev/null @@ -1,28 +0,0 @@ -|list - */ - public static function variants(): array - { - return [CompletedSmartscraper::class, FailedSmartscraper::class]; - } -} diff --git a/src/Responses/Validate/ValidateAPIKeyResponse.php b/src/Responses/Validate/ValidateAPIKeyResponse.php deleted file mode 100644 index bed9c37..0000000 --- a/src/Responses/Validate/ValidateAPIKeyResponse.php +++ /dev/null @@ -1,47 +0,0 @@ -unsetOptionalProperties(); - } - - /** - * Construct an instance from the required parameters. - * - * You must use named parameters to construct any parameters with a default value. - */ - public static function from(?string $email = null): self - { - $obj = new self; - - null !== $email && $obj->email = $email; - - return $obj; - } - - public function setEmail(string $email): self - { - $this->email = $email; - - return $this; - } -} diff --git a/src/Searchscraper/CompletedSearchScraper.php b/src/Searchscraper/CompletedSearchScraper.php index b6fe395..5c6f734 100644 --- a/src/Searchscraper/CompletedSearchScraper.php +++ b/src/Searchscraper/CompletedSearchScraper.php @@ -5,27 +5,31 @@ namespace Scrapegraphai\Searchscraper; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Concerns\SdkResponse; use Scrapegraphai\Core\Contracts\BaseModel; -use Scrapegraphai\Core\Conversion\ListOf; +use Scrapegraphai\Core\Conversion\Contracts\ResponseConverter; use Scrapegraphai\Searchscraper\CompletedSearchScraper\Status; /** - * @phpstan-type completed_search_scraper_alias = array{ + * @phpstan-type completed_search_scraper = array{ * error?: string|null, * numResults?: int, * referenceURLs?: list, * requestID?: string, * result?: mixed, - * status?: Status::*, + * status?: value-of, * userPrompt?: string, * } */ -final class CompletedSearchScraper implements BaseModel +final class CompletedSearchScraper implements BaseModel, ResponseConverter { - use Model; + /** @use SdkModel */ + use SdkModel; - #[Api(optional: true)] + use SdkResponse; + + #[Api(nullable: true, optional: true)] public ?string $error; #[Api('num_results', optional: true)] @@ -34,9 +38,9 @@ final class CompletedSearchScraper implements BaseModel /** * URLs of sources used. * - * @var null|list $referenceURLs + * @var list|null $referenceURLs */ - #[Api('reference_urls', type: new ListOf('string'), optional: true)] + #[Api('reference_urls', list: 'string', optional: true)] public ?array $referenceURLs; #[Api('request_id', optional: true)] @@ -48,7 +52,7 @@ final class CompletedSearchScraper implements BaseModel #[Api(optional: true)] public mixed $result; - /** @var null|Status::* $status */ + /** @var value-of|null $status */ #[Api(enum: Status::class, optional: true)] public ?string $status; @@ -57,8 +61,7 @@ final class CompletedSearchScraper implements BaseModel public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -66,16 +69,16 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|list $referenceURLs - * @param null|Status::* $status + * @param list $referenceURLs + * @param Status|value-of $status */ - public static function from( + public static function with( ?string $error = null, ?int $numResults = null, ?array $referenceURLs = null, ?string $requestID = null, mixed $result = null, - ?string $status = null, + Status|string|null $status = null, ?string $userPrompt = null, ): self { $obj = new self; @@ -85,24 +88,26 @@ public static function from( null !== $referenceURLs && $obj->referenceURLs = $referenceURLs; null !== $requestID && $obj->requestID = $requestID; null !== $result && $obj->result = $result; - null !== $status && $obj->status = $status; + null !== $status && $obj['status'] = $status; null !== $userPrompt && $obj->userPrompt = $userPrompt; return $obj; } - public function setError(?string $error): self + public function withError(?string $error): self { - $this->error = $error; + $obj = clone $this; + $obj->error = $error; - return $this; + return $obj; } - public function setNumResults(int $numResults): self + public function withNumResults(int $numResults): self { - $this->numResults = $numResults; + $obj = clone $this; + $obj->numResults = $numResults; - return $this; + return $obj; } /** @@ -110,44 +115,49 @@ public function setNumResults(int $numResults): self * * @param list $referenceURLs */ - public function setReferenceURLs(array $referenceURLs): self + public function withReferenceURLs(array $referenceURLs): self { - $this->referenceURLs = $referenceURLs; + $obj = clone $this; + $obj->referenceURLs = $referenceURLs; - return $this; + return $obj; } - public function setRequestID(string $requestID): self + public function withRequestID(string $requestID): self { - $this->requestID = $requestID; + $obj = clone $this; + $obj->requestID = $requestID; - return $this; + return $obj; } /** * Merged results from all scraped websites. */ - public function setResult(mixed $result): self + public function withResult(mixed $result): self { - $this->result = $result; + $obj = clone $this; + $obj->result = $result; - return $this; + return $obj; } /** - * @param Status::* $status + * @param Status|value-of $status */ - public function setStatus(string $status): self + public function withStatus(Status|string $status): self { - $this->status = $status; + $obj = clone $this; + $obj['status'] = $status; - return $this; + return $obj; } - public function setUserPrompt(string $userPrompt): self + public function withUserPrompt(string $userPrompt): self { - $this->userPrompt = $userPrompt; + $obj = clone $this; + $obj->userPrompt = $userPrompt; - return $this; + return $obj; } } diff --git a/src/Searchscraper/CompletedSearchScraper/Status.php b/src/Searchscraper/CompletedSearchScraper/Status.php index cb362fa..8b63933 100644 --- a/src/Searchscraper/CompletedSearchScraper/Status.php +++ b/src/Searchscraper/CompletedSearchScraper/Status.php @@ -4,19 +4,11 @@ namespace Scrapegraphai\Searchscraper\CompletedSearchScraper; -use Scrapegraphai\Core\Concerns\Enum; -use Scrapegraphai\Core\Conversion\Contracts\ConverterSource; - -/** - * @phpstan-type status_alias = Status::* - */ -final class Status implements ConverterSource +enum Status: string { - use Enum; - - public const QUEUED = 'queued'; + case QUEUED = 'queued'; - public const PROCESSING = 'processing'; + case PROCESSING = 'processing'; - public const COMPLETED = 'completed'; + case COMPLETED = 'completed'; } diff --git a/src/Searchscraper/SearchscraperCreateParams.php b/src/Searchscraper/SearchscraperCreateParams.php index 7901869..3c3ef60 100644 --- a/src/Searchscraper/SearchscraperCreateParams.php +++ b/src/Searchscraper/SearchscraperCreateParams.php @@ -5,16 +5,28 @@ namespace Scrapegraphai\Searchscraper; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; -use Scrapegraphai\Core\Concerns\Params; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Concerns\SdkParams; use Scrapegraphai\Core\Contracts\BaseModel; -use Scrapegraphai\Core\Conversion\MapOf; /** + * An object containing the method's parameters. + * Example usage: + * ``` + * $params = (new SearchscraperCreateParams); // set properties as needed + * $client->searchscraper->create(...$params->toArray()); + * ``` * Performs web search, selects relevant URLs, and extracts structured data from multiple websites. * Uses LLM to refine search queries and merge results from different sources. * - * @phpstan-type create_params = array{ + * @method toArray() + * Returns the parameters as an associative array suitable for passing to the client method. + * + * `$client->searchscraper->create(...$params->toArray());` + * + * @see Scrapegraphai\Searchscraper->create + * + * @phpstan-type searchscraper_create_params = array{ * userPrompt: string, * headers?: array, * numResults?: int, @@ -23,8 +35,9 @@ */ final class SearchscraperCreateParams implements BaseModel { - use Model; - use Params; + /** @use SdkModel */ + use SdkModel; + use SdkParams; /** * Search query and extraction instruction. @@ -32,8 +45,8 @@ final class SearchscraperCreateParams implements BaseModel #[Api('user_prompt')] public string $userPrompt; - /** @var null|array $headers */ - #[Api(type: new MapOf('string'), optional: true)] + /** @var array|null $headers */ + #[Api(map: 'string', optional: true)] public ?array $headers; /** @@ -48,10 +61,23 @@ final class SearchscraperCreateParams implements BaseModel #[Api('output_schema', optional: true)] public mixed $outputSchema; + /** + * `new SearchscraperCreateParams()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * SearchscraperCreateParams::with(userPrompt: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new SearchscraperCreateParams)->withUserPrompt(...) + * ``` + */ public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -59,9 +85,9 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|array $headers + * @param array $headers */ - public static function from( + public static function with( string $userPrompt, ?array $headers = null, ?int $numResults = null, @@ -81,40 +107,44 @@ public static function from( /** * Search query and extraction instruction. */ - public function setUserPrompt(string $userPrompt): self + public function withUserPrompt(string $userPrompt): self { - $this->userPrompt = $userPrompt; + $obj = clone $this; + $obj->userPrompt = $userPrompt; - return $this; + return $obj; } /** * @param array $headers */ - public function setHeaders(array $headers): self + public function withHeaders(array $headers): self { - $this->headers = $headers; + $obj = clone $this; + $obj->headers = $headers; - return $this; + return $obj; } /** * Number of websites to scrape from search results. */ - public function setNumResults(int $numResults): self + public function withNumResults(int $numResults): self { - $this->numResults = $numResults; + $obj = clone $this; + $obj->numResults = $numResults; - return $this; + return $obj; } /** * JSON schema for structured output. */ - public function setOutputSchema(mixed $outputSchema): self + public function withOutputSchema(mixed $outputSchema): self { - $this->outputSchema = $outputSchema; + $obj = clone $this; + $obj->outputSchema = $outputSchema; - return $this; + return $obj; } } diff --git a/src/Searchscraper/SearchscraperGetStatusResponse.php b/src/Searchscraper/SearchscraperGetStatusResponse.php new file mode 100644 index 0000000..4d14289 --- /dev/null +++ b/src/Searchscraper/SearchscraperGetStatusResponse.php @@ -0,0 +1,24 @@ +|array + */ + public static function variants(): array + { + return [CompletedSearchScraper::class, FailedSearchScraperResponse::class]; + } +} diff --git a/src/Searchscraper/SearchscraperGetStatusResponse/FailedSearchScraperResponse.php b/src/Searchscraper/SearchscraperGetStatusResponse/FailedSearchScraperResponse.php new file mode 100644 index 0000000..9f1ffcd --- /dev/null +++ b/src/Searchscraper/SearchscraperGetStatusResponse/FailedSearchScraperResponse.php @@ -0,0 +1,147 @@ +, + * requestID?: string, + * result?: mixed, + * status?: value-of, + * userPrompt?: string, + * } + */ +final class FailedSearchScraperResponse implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + #[Api(optional: true)] + public ?string $error; + + #[Api('num_results', optional: true)] + public ?int $numResults; + + /** @var list|null $referenceURLs */ + #[Api('reference_urls', list: 'string', optional: true)] + public ?array $referenceURLs; + + #[Api('request_id', optional: true)] + public ?string $requestID; + + #[Api(nullable: true, optional: true)] + public mixed $result; + + /** @var value-of|null $status */ + #[Api(enum: Status::class, optional: true)] + public ?string $status; + + #[Api('user_prompt', optional: true)] + public ?string $userPrompt; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param list $referenceURLs + * @param Status|value-of $status + */ + public static function with( + ?string $error = null, + ?int $numResults = null, + ?array $referenceURLs = null, + ?string $requestID = null, + mixed $result = null, + Status|string|null $status = null, + ?string $userPrompt = null, + ): self { + $obj = new self; + + null !== $error && $obj->error = $error; + null !== $numResults && $obj->numResults = $numResults; + null !== $referenceURLs && $obj->referenceURLs = $referenceURLs; + null !== $requestID && $obj->requestID = $requestID; + null !== $result && $obj->result = $result; + null !== $status && $obj['status'] = $status; + null !== $userPrompt && $obj->userPrompt = $userPrompt; + + return $obj; + } + + public function withError(string $error): self + { + $obj = clone $this; + $obj->error = $error; + + return $obj; + } + + public function withNumResults(int $numResults): self + { + $obj = clone $this; + $obj->numResults = $numResults; + + return $obj; + } + + /** + * @param list $referenceURLs + */ + public function withReferenceURLs(array $referenceURLs): self + { + $obj = clone $this; + $obj->referenceURLs = $referenceURLs; + + return $obj; + } + + public function withRequestID(string $requestID): self + { + $obj = clone $this; + $obj->requestID = $requestID; + + return $obj; + } + + public function withResult(mixed $result): self + { + $obj = clone $this; + $obj->result = $result; + + return $obj; + } + + /** + * @param Status|value-of $status + */ + public function withStatus(Status|string $status): self + { + $obj = clone $this; + $obj['status'] = $status; + + return $obj; + } + + public function withUserPrompt(string $userPrompt): self + { + $obj = clone $this; + $obj->userPrompt = $userPrompt; + + return $obj; + } +} diff --git a/src/Searchscraper/SearchscraperGetStatusResponse/FailedSearchScraperResponse/Status.php b/src/Searchscraper/SearchscraperGetStatusResponse/FailedSearchScraperResponse/Status.php new file mode 100644 index 0000000..62743e8 --- /dev/null +++ b/src/Searchscraper/SearchscraperGetStatusResponse/FailedSearchScraperResponse/Status.php @@ -0,0 +1,10 @@ +, - * numResults?: int, - * outputSchema?: mixed, - * }|SearchscraperCreateParams $params - */ - public function create( - array|SearchscraperCreateParams $params, - ?RequestOptions $requestOptions = null, - ): CompletedSearchScraper { - [$parsed, $options] = SearchscraperCreateParams::parseRequest( - $params, - $requestOptions - ); - $resp = $this->client->request( - method: 'post', - path: 'searchscraper', - body: (object) $parsed, - options: $options, - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(CompletedSearchScraper::class, value: $resp); - } - - /** - * Retrieve the status and results of a search scraping operation. - */ - public function retrieveStatus( - string $requestID, - ?RequestOptions $requestOptions = null - ): CompletedSearchScraper|FailedSearchScraperResponse { - $resp = $this->client->request( - method: 'get', - path: ['searchscraper/%1$s', $requestID], - options: $requestOptions, - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce( - SearchscraperGetStatusResponse::class, - value: $resp - ); - } -} diff --git a/src/ServiceContracts/CrawlContract.php b/src/ServiceContracts/CrawlContract.php new file mode 100644 index 0000000..a758a89 --- /dev/null +++ b/src/ServiceContracts/CrawlContract.php @@ -0,0 +1,66 @@ + $params + * + * @throws APIException + */ + public function startRaw( + array $params, + ?RequestOptions $requestOptions = null + ): CrawlStartResponse; +} diff --git a/src/Contracts/CreditsContract.php b/src/ServiceContracts/CreditsContract.php similarity index 50% rename from src/Contracts/CreditsContract.php rename to src/ServiceContracts/CreditsContract.php index 39219ae..ce8fe81 100644 --- a/src/Contracts/CreditsContract.php +++ b/src/ServiceContracts/CreditsContract.php @@ -2,13 +2,19 @@ declare(strict_types=1); -namespace Scrapegraphai\Contracts; +namespace Scrapegraphai\ServiceContracts; +use Scrapegraphai\Core\Exceptions\APIException; +use Scrapegraphai\Credits\CreditGetResponse; use Scrapegraphai\RequestOptions; -use Scrapegraphai\Responses\Credits\CreditGetResponse; interface CreditsContract { + /** + * @api + * + * @throws APIException + */ public function retrieve( ?RequestOptions $requestOptions = null ): CreditGetResponse; diff --git a/src/ServiceContracts/FeedbackContract.php b/src/ServiceContracts/FeedbackContract.php new file mode 100644 index 0000000..9309192 --- /dev/null +++ b/src/ServiceContracts/FeedbackContract.php @@ -0,0 +1,42 @@ + $params + * + * @throws APIException + */ + public function submitRaw( + array $params, + ?RequestOptions $requestOptions = null + ): FeedbackSubmitResponse; +} diff --git a/src/ServiceContracts/GenerateSchemaContract.php b/src/ServiceContracts/GenerateSchemaContract.php new file mode 100644 index 0000000..9ff584e --- /dev/null +++ b/src/ServiceContracts/GenerateSchemaContract.php @@ -0,0 +1,52 @@ + $params + * + * @throws APIException + */ + public function createRaw( + array $params, + ?RequestOptions $requestOptions = null + ): GenerateSchemaNewResponse; + + /** + * @api + * + * @throws APIException + */ + public function retrieve( + string $requestID, + ?RequestOptions $requestOptions = null + ): CompletedSchemaGenerationResponse|FailedSchemaGenerationResponse; +} diff --git a/src/Contracts/HealthzContract.php b/src/ServiceContracts/HealthzContract.php similarity index 50% rename from src/Contracts/HealthzContract.php rename to src/ServiceContracts/HealthzContract.php index 8b8f1f0..a20931b 100644 --- a/src/Contracts/HealthzContract.php +++ b/src/ServiceContracts/HealthzContract.php @@ -2,13 +2,19 @@ declare(strict_types=1); -namespace Scrapegraphai\Contracts; +namespace Scrapegraphai\ServiceContracts; +use Scrapegraphai\Core\Exceptions\APIException; +use Scrapegraphai\Healthz\HealthzCheckResponse; use Scrapegraphai\RequestOptions; -use Scrapegraphai\Responses\Healthz\HealthzCheckResponse; interface HealthzContract { + /** + * @api + * + * @throws APIException + */ public function check( ?RequestOptions $requestOptions = null ): HealthzCheckResponse; diff --git a/src/ServiceContracts/MarkdownifyContract.php b/src/ServiceContracts/MarkdownifyContract.php new file mode 100644 index 0000000..42b747f --- /dev/null +++ b/src/ServiceContracts/MarkdownifyContract.php @@ -0,0 +1,53 @@ + $headers + * @param list $steps Interaction steps before conversion + * + * @throws APIException + */ + public function convert( + $websiteURL, + $headers = omit, + $steps = omit, + ?RequestOptions $requestOptions = null, + ): CompletedMarkdownify; + + /** + * @api + * + * @param array $params + * + * @throws APIException + */ + public function convertRaw( + array $params, + ?RequestOptions $requestOptions = null + ): CompletedMarkdownify; + + /** + * @api + * + * @throws APIException + */ + public function retrieveStatus( + string $requestID, + ?RequestOptions $requestOptions = null + ): CompletedMarkdownify|FailedMarkdownifyResponse; +} diff --git a/src/ServiceContracts/SearchscraperContract.php b/src/ServiceContracts/SearchscraperContract.php new file mode 100644 index 0000000..9d4779f --- /dev/null +++ b/src/ServiceContracts/SearchscraperContract.php @@ -0,0 +1,55 @@ + $headers + * @param int $numResults Number of websites to scrape from search results + * @param mixed $outputSchema JSON schema for structured output + * + * @throws APIException + */ + public function create( + $userPrompt, + $headers = omit, + $numResults = omit, + $outputSchema = omit, + ?RequestOptions $requestOptions = null, + ): CompletedSearchScraper; + + /** + * @api + * + * @param array $params + * + * @throws APIException + */ + public function createRaw( + array $params, + ?RequestOptions $requestOptions = null + ): CompletedSearchScraper; + + /** + * @api + * + * @throws APIException + */ + public function retrieveStatus( + string $requestID, + ?RequestOptions $requestOptions = null + ): CompletedSearchScraper|FailedSearchScraperResponse; +} diff --git a/src/ServiceContracts/SmartscraperContract.php b/src/ServiceContracts/SmartscraperContract.php new file mode 100644 index 0000000..d1188f4 --- /dev/null +++ b/src/ServiceContracts/SmartscraperContract.php @@ -0,0 +1,76 @@ + $cookies Cookies to include in the request + * @param array $headers HTTP headers to include in the request + * @param int $numberOfScrolls Number of infinite scroll operations to perform + * @param mixed $outputSchema JSON schema defining the expected output structure + * @param bool $renderHeavyJs Enable heavy JavaScript rendering + * @param list $steps Website interaction steps (e.g., clicking buttons) + * @param int $totalPages Number of pages to process for pagination + * @param string $websiteHTML HTML content to process (max 2MB, mutually exclusive with website_url) + * @param string $websiteURL URL to scrape (mutually exclusive with website_html) + * + * @throws APIException + */ + public function create( + $userPrompt, + $cookies = omit, + $headers = omit, + $numberOfScrolls = omit, + $outputSchema = omit, + $renderHeavyJs = omit, + $steps = omit, + $totalPages = omit, + $websiteHTML = omit, + $websiteURL = omit, + ?RequestOptions $requestOptions = null, + ): CompletedSmartscraper; + + /** + * @api + * + * @param array $params + * + * @throws APIException + */ + public function createRaw( + array $params, + ?RequestOptions $requestOptions = null + ): CompletedSmartscraper; + + /** + * @api + * + * @throws APIException + */ + public function retrieve( + string $requestID, + ?RequestOptions $requestOptions = null + ): CompletedSmartscraper|FailedSmartscraper; + + /** + * @api + * + * @throws APIException + */ + public function list( + ?RequestOptions $requestOptions = null + ): CompletedSmartscraper|FailedSmartscraper; +} diff --git a/src/Contracts/ValidateContract.php b/src/ServiceContracts/ValidateContract.php similarity index 50% rename from src/Contracts/ValidateContract.php rename to src/ServiceContracts/ValidateContract.php index 3be846f..798ebec 100644 --- a/src/Contracts/ValidateContract.php +++ b/src/ServiceContracts/ValidateContract.php @@ -2,13 +2,19 @@ declare(strict_types=1); -namespace Scrapegraphai\Contracts; +namespace Scrapegraphai\ServiceContracts; +use Scrapegraphai\Core\Exceptions\APIException; use Scrapegraphai\RequestOptions; -use Scrapegraphai\Responses\Validate\ValidateAPIKeyResponse; +use Scrapegraphai\Validate\ValidateAPIKeyResponse; interface ValidateContract { + /** + * @api + * + * @throws APIException + */ public function apiKey( ?RequestOptions $requestOptions = null ): ValidateAPIKeyResponse; diff --git a/src/Services/CrawlService.php b/src/Services/CrawlService.php new file mode 100644 index 0000000..635c8d7 --- /dev/null +++ b/src/Services/CrawlService.php @@ -0,0 +1,116 @@ +client->request( + method: 'get', + path: ['crawl/%1$s', $taskID], + options: $requestOptions, + convert: CrawlGetResultsResponse::class, + ); + } + + /** + * @api + * + * Initiate comprehensive website crawling with sitemap support. + * Supports both AI extraction mode and markdown conversion mode. + * Returns a task ID for async processing. + * + * @param string $url Starting URL for crawling + * @param int $depth Maximum crawl depth from starting URL + * @param bool $extractionMode Use AI extraction (true) or markdown conversion (false) + * @param int $maxPages Maximum number of pages to crawl + * @param string|null $prompt Extraction prompt (required if extraction_mode is true) + * @param bool $renderHeavyJs Enable heavy JavaScript rendering + * @param Rules $rules + * @param mixed $schema Output schema for extraction + * @param bool $sitemap Use sitemap for crawling + * + * @throws APIException + */ + public function start( + $url, + $depth = omit, + $extractionMode = omit, + $maxPages = omit, + $prompt = omit, + $renderHeavyJs = omit, + $rules = omit, + $schema = omit, + $sitemap = omit, + ?RequestOptions $requestOptions = null, + ): CrawlStartResponse { + $params = [ + 'url' => $url, + 'depth' => $depth, + 'extractionMode' => $extractionMode, + 'maxPages' => $maxPages, + 'prompt' => $prompt, + 'renderHeavyJs' => $renderHeavyJs, + 'rules' => $rules, + 'schema' => $schema, + 'sitemap' => $sitemap, + ]; + + return $this->startRaw($params, $requestOptions); + } + + /** + * @api + * + * @param array $params + * + * @throws APIException + */ + public function startRaw( + array $params, + ?RequestOptions $requestOptions = null + ): CrawlStartResponse { + [$parsed, $options] = CrawlStartParams::parseRequest( + $params, + $requestOptions + ); + + // @phpstan-ignore-next-line; + return $this->client->request( + method: 'post', + path: 'crawl', + body: (object) $parsed, + options: $options, + convert: CrawlStartResponse::class, + ); + } +} diff --git a/src/Credits/CreditsService.php b/src/Services/CreditsService.php similarity index 55% rename from src/Credits/CreditsService.php rename to src/Services/CreditsService.php index 0384910..2584fc7 100644 --- a/src/Credits/CreditsService.php +++ b/src/Services/CreditsService.php @@ -2,31 +2,37 @@ declare(strict_types=1); -namespace Scrapegraphai\Credits; +namespace Scrapegraphai\Services; use Scrapegraphai\Client; -use Scrapegraphai\Contracts\CreditsContract; -use Scrapegraphai\Core\Conversion; +use Scrapegraphai\Core\Exceptions\APIException; +use Scrapegraphai\Credits\CreditGetResponse; use Scrapegraphai\RequestOptions; -use Scrapegraphai\Responses\Credits\CreditGetResponse; +use Scrapegraphai\ServiceContracts\CreditsContract; final class CreditsService implements CreditsContract { + /** + * @internal + */ public function __construct(private Client $client) {} /** - * Retrieve the current credit balance and usage for the authenticated user. + * @api + * + * Retrieve the current credit balance and usage for the authenticated user + * + * @throws APIException */ public function retrieve( ?RequestOptions $requestOptions = null ): CreditGetResponse { - $resp = $this->client->request( + // @phpstan-ignore-next-line; + return $this->client->request( method: 'get', path: 'credits', - options: $requestOptions + options: $requestOptions, + convert: CreditGetResponse::class, ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(CreditGetResponse::class, value: $resp); } } diff --git a/src/Services/FeedbackService.php b/src/Services/FeedbackService.php new file mode 100644 index 0000000..58442c8 --- /dev/null +++ b/src/Services/FeedbackService.php @@ -0,0 +1,74 @@ + $rating, + 'requestID' => $requestID, + 'feedbackText' => $feedbackText, + ]; + + return $this->submitRaw($params, $requestOptions); + } + + /** + * @api + * + * @param array $params + * + * @throws APIException + */ + public function submitRaw( + array $params, + ?RequestOptions $requestOptions = null + ): FeedbackSubmitResponse { + [$parsed, $options] = FeedbackSubmitParams::parseRequest( + $params, + $requestOptions + ); + + // @phpstan-ignore-next-line; + return $this->client->request( + method: 'post', + path: 'feedback', + body: (object) $parsed, + options: $options, + convert: FeedbackSubmitResponse::class, + ); + } +} diff --git a/src/Services/GenerateSchemaService.php b/src/Services/GenerateSchemaService.php new file mode 100644 index 0000000..db55f39 --- /dev/null +++ b/src/Services/GenerateSchemaService.php @@ -0,0 +1,94 @@ + $userPrompt, 'existingSchema' => $existingSchema, + ]; + + return $this->createRaw($params, $requestOptions); + } + + /** + * @api + * + * @param array $params + * + * @throws APIException + */ + public function createRaw( + array $params, + ?RequestOptions $requestOptions = null + ): GenerateSchemaNewResponse { + [$parsed, $options] = GenerateSchemaCreateParams::parseRequest( + $params, + $requestOptions + ); + + // @phpstan-ignore-next-line; + return $this->client->request( + method: 'post', + path: 'generate_schema', + body: (object) $parsed, + options: $options, + convert: GenerateSchemaNewResponse::class, + ); + } + + /** + * @api + * + * Retrieve the status and results of a schema generation request + * + * @throws APIException + */ + public function retrieve( + string $requestID, + ?RequestOptions $requestOptions = null + ): CompletedSchemaGenerationResponse|FailedSchemaGenerationResponse { + // @phpstan-ignore-next-line; + return $this->client->request( + method: 'get', + path: ['generate_schema/%1$s', $requestID], + options: $requestOptions, + convert: GenerateSchemaGetResponse::class, + ); + } +} diff --git a/src/Healthz/HealthzService.php b/src/Services/HealthzService.php similarity index 50% rename from src/Healthz/HealthzService.php rename to src/Services/HealthzService.php index 9b919de..4e7c81c 100644 --- a/src/Healthz/HealthzService.php +++ b/src/Services/HealthzService.php @@ -2,31 +2,37 @@ declare(strict_types=1); -namespace Scrapegraphai\Healthz; +namespace Scrapegraphai\Services; use Scrapegraphai\Client; -use Scrapegraphai\Contracts\HealthzContract; -use Scrapegraphai\Core\Conversion; +use Scrapegraphai\Core\Exceptions\APIException; +use Scrapegraphai\Healthz\HealthzCheckResponse; use Scrapegraphai\RequestOptions; -use Scrapegraphai\Responses\Healthz\HealthzCheckResponse; +use Scrapegraphai\ServiceContracts\HealthzContract; final class HealthzService implements HealthzContract { + /** + * @internal + */ public function __construct(private Client $client) {} /** - * Check the health status of the service. + * @api + * + * Check the health status of the service + * + * @throws APIException */ public function check( ?RequestOptions $requestOptions = null ): HealthzCheckResponse { - $resp = $this->client->request( + // @phpstan-ignore-next-line; + return $this->client->request( method: 'get', path: 'healthz', - options: $requestOptions + options: $requestOptions, + convert: HealthzCheckResponse::class, ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(HealthzCheckResponse::class, value: $resp); } } diff --git a/src/Services/MarkdownifyService.php b/src/Services/MarkdownifyService.php new file mode 100644 index 0000000..2cae2a8 --- /dev/null +++ b/src/Services/MarkdownifyService.php @@ -0,0 +1,94 @@ + $headers + * @param list $steps Interaction steps before conversion + * + * @throws APIException + */ + public function convert( + $websiteURL, + $headers = omit, + $steps = omit, + ?RequestOptions $requestOptions = null, + ): CompletedMarkdownify { + $params = [ + 'websiteURL' => $websiteURL, 'headers' => $headers, 'steps' => $steps, + ]; + + return $this->convertRaw($params, $requestOptions); + } + + /** + * @api + * + * @param array $params + * + * @throws APIException + */ + public function convertRaw( + array $params, + ?RequestOptions $requestOptions = null + ): CompletedMarkdownify { + [$parsed, $options] = MarkdownifyConvertParams::parseRequest( + $params, + $requestOptions + ); + + // @phpstan-ignore-next-line; + return $this->client->request( + method: 'post', + path: 'markdownify', + body: (object) $parsed, + options: $options, + convert: CompletedMarkdownify::class, + ); + } + + /** + * @api + * + * Retrieve the status and results of a markdown conversion + * + * @throws APIException + */ + public function retrieveStatus( + string $requestID, + ?RequestOptions $requestOptions = null + ): CompletedMarkdownify|FailedMarkdownifyResponse { + // @phpstan-ignore-next-line; + return $this->client->request( + method: 'get', + path: ['markdownify/%1$s', $requestID], + options: $requestOptions, + convert: MarkdownifyGetStatusResponse::class, + ); + } +} diff --git a/src/Services/SearchscraperService.php b/src/Services/SearchscraperService.php new file mode 100644 index 0000000..69dc02a --- /dev/null +++ b/src/Services/SearchscraperService.php @@ -0,0 +1,100 @@ + $headers + * @param int $numResults Number of websites to scrape from search results + * @param mixed $outputSchema JSON schema for structured output + * + * @throws APIException + */ + public function create( + $userPrompt, + $headers = omit, + $numResults = omit, + $outputSchema = omit, + ?RequestOptions $requestOptions = null, + ): CompletedSearchScraper { + $params = [ + 'userPrompt' => $userPrompt, + 'headers' => $headers, + 'numResults' => $numResults, + 'outputSchema' => $outputSchema, + ]; + + return $this->createRaw($params, $requestOptions); + } + + /** + * @api + * + * @param array $params + * + * @throws APIException + */ + public function createRaw( + array $params, + ?RequestOptions $requestOptions = null + ): CompletedSearchScraper { + [$parsed, $options] = SearchscraperCreateParams::parseRequest( + $params, + $requestOptions + ); + + // @phpstan-ignore-next-line; + return $this->client->request( + method: 'post', + path: 'searchscraper', + body: (object) $parsed, + options: $options, + convert: CompletedSearchScraper::class, + ); + } + + /** + * @api + * + * Retrieve the status and results of a search scraping operation + * + * @throws APIException + */ + public function retrieveStatus( + string $requestID, + ?RequestOptions $requestOptions = null + ): CompletedSearchScraper|FailedSearchScraperResponse { + // @phpstan-ignore-next-line; + return $this->client->request( + method: 'get', + path: ['searchscraper/%1$s', $requestID], + options: $requestOptions, + convert: SearchscraperGetStatusResponse::class, + ); + } +} diff --git a/src/Services/SmartscraperService.php b/src/Services/SmartscraperService.php new file mode 100644 index 0000000..2b5e079 --- /dev/null +++ b/src/Services/SmartscraperService.php @@ -0,0 +1,138 @@ + $cookies Cookies to include in the request + * @param array $headers HTTP headers to include in the request + * @param int $numberOfScrolls Number of infinite scroll operations to perform + * @param mixed $outputSchema JSON schema defining the expected output structure + * @param bool $renderHeavyJs Enable heavy JavaScript rendering + * @param list $steps Website interaction steps (e.g., clicking buttons) + * @param int $totalPages Number of pages to process for pagination + * @param string $websiteHTML HTML content to process (max 2MB, mutually exclusive with website_url) + * @param string $websiteURL URL to scrape (mutually exclusive with website_html) + * + * @throws APIException + */ + public function create( + $userPrompt, + $cookies = omit, + $headers = omit, + $numberOfScrolls = omit, + $outputSchema = omit, + $renderHeavyJs = omit, + $steps = omit, + $totalPages = omit, + $websiteHTML = omit, + $websiteURL = omit, + ?RequestOptions $requestOptions = null, + ): CompletedSmartscraper { + $params = [ + 'userPrompt' => $userPrompt, + 'cookies' => $cookies, + 'headers' => $headers, + 'numberOfScrolls' => $numberOfScrolls, + 'outputSchema' => $outputSchema, + 'renderHeavyJs' => $renderHeavyJs, + 'steps' => $steps, + 'totalPages' => $totalPages, + 'websiteHTML' => $websiteHTML, + 'websiteURL' => $websiteURL, + ]; + + return $this->createRaw($params, $requestOptions); + } + + /** + * @api + * + * @param array $params + * + * @throws APIException + */ + public function createRaw( + array $params, + ?RequestOptions $requestOptions = null + ): CompletedSmartscraper { + [$parsed, $options] = SmartscraperCreateParams::parseRequest( + $params, + $requestOptions + ); + + // @phpstan-ignore-next-line; + return $this->client->request( + method: 'post', + path: 'smartscraper', + body: (object) $parsed, + options: $options, + convert: CompletedSmartscraper::class, + ); + } + + /** + * @api + * + * Retrieve the status and results of a scraping operation + * + * @throws APIException + */ + public function retrieve( + string $requestID, + ?RequestOptions $requestOptions = null + ): CompletedSmartscraper|FailedSmartscraper { + // @phpstan-ignore-next-line; + return $this->client->request( + method: 'get', + path: ['smartscraper/%1$s', $requestID], + options: $requestOptions, + convert: SmartscraperGetResponse::class, + ); + } + + /** + * @api + * + * Retrieve the status and results of a scraping operation + * + * @throws APIException + */ + public function list( + ?RequestOptions $requestOptions = null + ): CompletedSmartscraper|FailedSmartscraper { + // @phpstan-ignore-next-line; + return $this->client->request( + method: 'get', + path: 'smartscraper', + options: $requestOptions, + convert: SmartscraperListResponse::class, + ); + } +} diff --git a/src/Services/ValidateService.php b/src/Services/ValidateService.php new file mode 100644 index 0000000..c57a1e7 --- /dev/null +++ b/src/Services/ValidateService.php @@ -0,0 +1,38 @@ +client->request( + method: 'get', + path: 'validate', + options: $requestOptions, + convert: ValidateAPIKeyResponse::class, + ); + } +} diff --git a/src/Smartscraper/CompletedSmartscraper.php b/src/Smartscraper/CompletedSmartscraper.php index 288bc66..4b8744f 100644 --- a/src/Smartscraper/CompletedSmartscraper.php +++ b/src/Smartscraper/CompletedSmartscraper.php @@ -5,23 +5,28 @@ namespace Scrapegraphai\Smartscraper; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Concerns\SdkResponse; use Scrapegraphai\Core\Contracts\BaseModel; +use Scrapegraphai\Core\Conversion\Contracts\ResponseConverter; use Scrapegraphai\Smartscraper\CompletedSmartscraper\Status; /** - * @phpstan-type completed_smartscraper_alias = array{ + * @phpstan-type completed_smartscraper = array{ * error?: string, * requestID?: string, * result?: mixed, - * status?: Status::*, + * status?: value-of, * userPrompt?: string, * websiteURL?: string|null, * } */ -final class CompletedSmartscraper implements BaseModel +final class CompletedSmartscraper implements BaseModel, ResponseConverter { - use Model; + /** @use SdkModel */ + use SdkModel; + + use SdkResponse; /** * Error message (empty on success). @@ -38,13 +43,13 @@ final class CompletedSmartscraper implements BaseModel /** * Extracted data based on prompt/schema. */ - #[Api(optional: true)] + #[Api(nullable: true, optional: true)] public mixed $result; /** * Processing status. * - * @var null|Status::* $status + * @var value-of|null $status */ #[Api(enum: Status::class, optional: true)] public ?string $status; @@ -52,13 +57,12 @@ final class CompletedSmartscraper implements BaseModel #[Api('user_prompt', optional: true)] public ?string $userPrompt; - #[Api('website_url', optional: true)] + #[Api('website_url', nullable: true, optional: true)] public ?string $websiteURL; public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -66,13 +70,13 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|Status::* $status + * @param Status|value-of $status */ - public static function from( + public static function with( ?string $error = null, ?string $requestID = null, mixed $result = null, - ?string $status = null, + Status|string|null $status = null, ?string $userPrompt = null, ?string $websiteURL = null, ): self { @@ -81,7 +85,7 @@ public static function from( null !== $error && $obj->error = $error; null !== $requestID && $obj->requestID = $requestID; null !== $result && $obj->result = $result; - null !== $status && $obj->status = $status; + null !== $status && $obj['status'] = $status; null !== $userPrompt && $obj->userPrompt = $userPrompt; null !== $websiteURL && $obj->websiteURL = $websiteURL; @@ -91,56 +95,62 @@ public static function from( /** * Error message (empty on success). */ - public function setError(string $error): self + public function withError(string $error): self { - $this->error = $error; + $obj = clone $this; + $obj->error = $error; - return $this; + return $obj; } /** * Unique request identifier. */ - public function setRequestID(string $requestID): self + public function withRequestID(string $requestID): self { - $this->requestID = $requestID; + $obj = clone $this; + $obj->requestID = $requestID; - return $this; + return $obj; } /** * Extracted data based on prompt/schema. */ - public function setResult(mixed $result): self + public function withResult(mixed $result): self { - $this->result = $result; + $obj = clone $this; + $obj->result = $result; - return $this; + return $obj; } /** * Processing status. * - * @param Status::* $status + * @param Status|value-of $status */ - public function setStatus(string $status): self + public function withStatus(Status|string $status): self { - $this->status = $status; + $obj = clone $this; + $obj['status'] = $status; - return $this; + return $obj; } - public function setUserPrompt(string $userPrompt): self + public function withUserPrompt(string $userPrompt): self { - $this->userPrompt = $userPrompt; + $obj = clone $this; + $obj->userPrompt = $userPrompt; - return $this; + return $obj; } - public function setWebsiteURL(?string $websiteURL): self + public function withWebsiteURL(?string $websiteURL): self { - $this->websiteURL = $websiteURL; + $obj = clone $this; + $obj->websiteURL = $websiteURL; - return $this; + return $obj; } } diff --git a/src/Smartscraper/CompletedSmartscraper/Status.php b/src/Smartscraper/CompletedSmartscraper/Status.php index e2569bf..f9331d4 100644 --- a/src/Smartscraper/CompletedSmartscraper/Status.php +++ b/src/Smartscraper/CompletedSmartscraper/Status.php @@ -4,21 +4,14 @@ namespace Scrapegraphai\Smartscraper\CompletedSmartscraper; -use Scrapegraphai\Core\Concerns\Enum; -use Scrapegraphai\Core\Conversion\Contracts\ConverterSource; - /** * Processing status. - * - * @phpstan-type status_alias = Status::* */ -final class Status implements ConverterSource +enum Status: string { - use Enum; - - public const QUEUED = 'queued'; + case QUEUED = 'queued'; - public const PROCESSING = 'processing'; + case PROCESSING = 'processing'; - public const COMPLETED = 'completed'; + case COMPLETED = 'completed'; } diff --git a/src/Smartscraper/FailedSmartscraper.php b/src/Smartscraper/FailedSmartscraper.php index 7bd42e0..2f4ee5e 100644 --- a/src/Smartscraper/FailedSmartscraper.php +++ b/src/Smartscraper/FailedSmartscraper.php @@ -5,23 +5,24 @@ namespace Scrapegraphai\Smartscraper; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; +use Scrapegraphai\Core\Concerns\SdkModel; use Scrapegraphai\Core\Contracts\BaseModel; use Scrapegraphai\Smartscraper\FailedSmartscraper\Status; /** - * @phpstan-type failed_smartscraper_alias = array{ + * @phpstan-type failed_smartscraper = array{ * error?: string, * requestID?: string, * result?: mixed, - * status?: Status::*, + * status?: value-of, * userPrompt?: string, * websiteURL?: string|null, * } */ final class FailedSmartscraper implements BaseModel { - use Model; + /** @use SdkModel */ + use SdkModel; /** * Error description. @@ -32,23 +33,22 @@ final class FailedSmartscraper implements BaseModel #[Api('request_id', optional: true)] public ?string $requestID; - #[Api(optional: true)] + #[Api(nullable: true, optional: true)] public mixed $result; - /** @var null|Status::* $status */ + /** @var value-of|null $status */ #[Api(enum: Status::class, optional: true)] public ?string $status; #[Api('user_prompt', optional: true)] public ?string $userPrompt; - #[Api('website_url', optional: true)] + #[Api('website_url', nullable: true, optional: true)] public ?string $websiteURL; public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -56,13 +56,13 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|Status::* $status + * @param Status|value-of $status */ - public static function from( + public static function with( ?string $error = null, ?string $requestID = null, mixed $result = null, - ?string $status = null, + Status|string|null $status = null, ?string $userPrompt = null, ?string $websiteURL = null, ): self { @@ -71,7 +71,7 @@ public static function from( null !== $error && $obj->error = $error; null !== $requestID && $obj->requestID = $requestID; null !== $result && $obj->result = $result; - null !== $status && $obj->status = $status; + null !== $status && $obj['status'] = $status; null !== $userPrompt && $obj->userPrompt = $userPrompt; null !== $websiteURL && $obj->websiteURL = $websiteURL; @@ -81,48 +81,54 @@ public static function from( /** * Error description. */ - public function setError(string $error): self + public function withError(string $error): self { - $this->error = $error; + $obj = clone $this; + $obj->error = $error; - return $this; + return $obj; } - public function setRequestID(string $requestID): self + public function withRequestID(string $requestID): self { - $this->requestID = $requestID; + $obj = clone $this; + $obj->requestID = $requestID; - return $this; + return $obj; } - public function setResult(mixed $result): self + public function withResult(mixed $result): self { - $this->result = $result; + $obj = clone $this; + $obj->result = $result; - return $this; + return $obj; } /** - * @param Status::* $status + * @param Status|value-of $status */ - public function setStatus(string $status): self + public function withStatus(Status|string $status): self { - $this->status = $status; + $obj = clone $this; + $obj['status'] = $status; - return $this; + return $obj; } - public function setUserPrompt(string $userPrompt): self + public function withUserPrompt(string $userPrompt): self { - $this->userPrompt = $userPrompt; + $obj = clone $this; + $obj->userPrompt = $userPrompt; - return $this; + return $obj; } - public function setWebsiteURL(?string $websiteURL): self + public function withWebsiteURL(?string $websiteURL): self { - $this->websiteURL = $websiteURL; + $obj = clone $this; + $obj->websiteURL = $websiteURL; - return $this; + return $obj; } } diff --git a/src/Smartscraper/FailedSmartscraper/Status.php b/src/Smartscraper/FailedSmartscraper/Status.php index b5b7bd8..e8c349e 100644 --- a/src/Smartscraper/FailedSmartscraper/Status.php +++ b/src/Smartscraper/FailedSmartscraper/Status.php @@ -4,15 +4,7 @@ namespace Scrapegraphai\Smartscraper\FailedSmartscraper; -use Scrapegraphai\Core\Concerns\Enum; -use Scrapegraphai\Core\Conversion\Contracts\ConverterSource; - -/** - * @phpstan-type status_alias = Status::* - */ -final class Status implements ConverterSource +enum Status: string { - use Enum; - - public const FAILED = 'failed'; + case FAILED = 'failed'; } diff --git a/src/Smartscraper/SmartscraperCreateParams.php b/src/Smartscraper/SmartscraperCreateParams.php index 77f2885..ad75b1c 100644 --- a/src/Smartscraper/SmartscraperCreateParams.php +++ b/src/Smartscraper/SmartscraperCreateParams.php @@ -5,17 +5,28 @@ namespace Scrapegraphai\Smartscraper; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; -use Scrapegraphai\Core\Concerns\Params; +use Scrapegraphai\Core\Concerns\SdkModel; +use Scrapegraphai\Core\Concerns\SdkParams; use Scrapegraphai\Core\Contracts\BaseModel; -use Scrapegraphai\Core\Conversion\ListOf; -use Scrapegraphai\Core\Conversion\MapOf; /** + * An object containing the method's parameters. + * Example usage: + * ``` + * $params = (new SmartscraperCreateParams); // set properties as needed + * $client->smartscraper->create(...$params->toArray()); + * ``` * Main scraping endpoint with LLM-powered content analysis. Supports various fetching providers, * infinite scrolling, pagination, and custom output schemas. * - * @phpstan-type create_params = array{ + * @method toArray() + * Returns the parameters as an associative array suitable for passing to the client method. + * + * `$client->smartscraper->create(...$params->toArray());` + * + * @see Scrapegraphai\Smartscraper->create + * + * @phpstan-type smartscraper_create_params = array{ * userPrompt: string, * cookies?: array, * headers?: array, @@ -30,8 +41,9 @@ */ final class SmartscraperCreateParams implements BaseModel { - use Model; - use Params; + /** @use SdkModel */ + use SdkModel; + use SdkParams; /** * Extraction instruction for the LLM. @@ -42,17 +54,17 @@ final class SmartscraperCreateParams implements BaseModel /** * Cookies to include in the request. * - * @var null|array $cookies + * @var array|null $cookies */ - #[Api(type: new MapOf('string'), optional: true)] + #[Api(map: 'string', optional: true)] public ?array $cookies; /** * HTTP headers to include in the request. * - * @var null|array $headers + * @var array|null $headers */ - #[Api(type: new MapOf('string'), optional: true)] + #[Api(map: 'string', optional: true)] public ?array $headers; /** @@ -76,9 +88,9 @@ final class SmartscraperCreateParams implements BaseModel /** * Website interaction steps (e.g., clicking buttons). * - * @var null|list $steps + * @var list|null $steps */ - #[Api(type: new ListOf('string'), optional: true)] + #[Api(list: 'string', optional: true)] public ?array $steps; /** @@ -99,10 +111,23 @@ final class SmartscraperCreateParams implements BaseModel #[Api('website_url', optional: true)] public ?string $websiteURL; + /** + * `new SmartscraperCreateParams()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * SmartscraperCreateParams::with(userPrompt: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new SmartscraperCreateParams)->withUserPrompt(...) + * ``` + */ public function __construct() { - self::introspect(); - $this->unsetOptionalProperties(); + $this->initialize(); } /** @@ -110,11 +135,11 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param null|array $cookies - * @param null|array $headers - * @param null|list $steps + * @param array $cookies + * @param array $headers + * @param list $steps */ - public static function from( + public static function with( string $userPrompt, ?array $cookies = null, ?array $headers = null, @@ -146,11 +171,12 @@ public static function from( /** * Extraction instruction for the LLM. */ - public function setUserPrompt(string $userPrompt): self + public function withUserPrompt(string $userPrompt): self { - $this->userPrompt = $userPrompt; + $obj = clone $this; + $obj->userPrompt = $userPrompt; - return $this; + return $obj; } /** @@ -158,11 +184,12 @@ public function setUserPrompt(string $userPrompt): self * * @param array $cookies */ - public function setCookies(array $cookies): self + public function withCookies(array $cookies): self { - $this->cookies = $cookies; + $obj = clone $this; + $obj->cookies = $cookies; - return $this; + return $obj; } /** @@ -170,41 +197,45 @@ public function setCookies(array $cookies): self * * @param array $headers */ - public function setHeaders(array $headers): self + public function withHeaders(array $headers): self { - $this->headers = $headers; + $obj = clone $this; + $obj->headers = $headers; - return $this; + return $obj; } /** * Number of infinite scroll operations to perform. */ - public function setNumberOfScrolls(int $numberOfScrolls): self + public function withNumberOfScrolls(int $numberOfScrolls): self { - $this->numberOfScrolls = $numberOfScrolls; + $obj = clone $this; + $obj->numberOfScrolls = $numberOfScrolls; - return $this; + return $obj; } /** * JSON schema defining the expected output structure. */ - public function setOutputSchema(mixed $outputSchema): self + public function withOutputSchema(mixed $outputSchema): self { - $this->outputSchema = $outputSchema; + $obj = clone $this; + $obj->outputSchema = $outputSchema; - return $this; + return $obj; } /** * Enable heavy JavaScript rendering. */ - public function setRenderHeavyJs(bool $renderHeavyJs): self + public function withRenderHeavyJs(bool $renderHeavyJs): self { - $this->renderHeavyJs = $renderHeavyJs; + $obj = clone $this; + $obj->renderHeavyJs = $renderHeavyJs; - return $this; + return $obj; } /** @@ -212,40 +243,44 @@ public function setRenderHeavyJs(bool $renderHeavyJs): self * * @param list $steps */ - public function setSteps(array $steps): self + public function withSteps(array $steps): self { - $this->steps = $steps; + $obj = clone $this; + $obj->steps = $steps; - return $this; + return $obj; } /** * Number of pages to process for pagination. */ - public function setTotalPages(int $totalPages): self + public function withTotalPages(int $totalPages): self { - $this->totalPages = $totalPages; + $obj = clone $this; + $obj->totalPages = $totalPages; - return $this; + return $obj; } /** * HTML content to process (max 2MB, mutually exclusive with website_url). */ - public function setWebsiteHTML(string $websiteHTML): self + public function withWebsiteHTML(string $websiteHTML): self { - $this->websiteHTML = $websiteHTML; + $obj = clone $this; + $obj->websiteHTML = $websiteHTML; - return $this; + return $obj; } /** * URL to scrape (mutually exclusive with website_html). */ - public function setWebsiteURL(string $websiteURL): self + public function withWebsiteURL(string $websiteURL): self { - $this->websiteURL = $websiteURL; + $obj = clone $this; + $obj->websiteURL = $websiteURL; - return $this; + return $obj; } } diff --git a/src/Smartscraper/SmartscraperGetResponse.php b/src/Smartscraper/SmartscraperGetResponse.php new file mode 100644 index 0000000..474b71d --- /dev/null +++ b/src/Smartscraper/SmartscraperGetResponse.php @@ -0,0 +1,23 @@ +|array + */ + public static function variants(): array + { + return [CompletedSmartscraper::class, FailedSmartscraper::class]; + } +} diff --git a/src/Smartscraper/SmartscraperListResponse.php b/src/Smartscraper/SmartscraperListResponse.php new file mode 100644 index 0000000..b776c46 --- /dev/null +++ b/src/Smartscraper/SmartscraperListResponse.php @@ -0,0 +1,23 @@ +|array + */ + public static function variants(): array + { + return [CompletedSmartscraper::class, FailedSmartscraper::class]; + } +} diff --git a/src/Smartscraper/SmartscraperService.php b/src/Smartscraper/SmartscraperService.php deleted file mode 100644 index 09f6806..0000000 --- a/src/Smartscraper/SmartscraperService.php +++ /dev/null @@ -1,86 +0,0 @@ -, - * headers?: array, - * numberOfScrolls?: int, - * outputSchema?: mixed, - * renderHeavyJs?: bool, - * steps?: list, - * totalPages?: int, - * websiteHTML?: string, - * websiteURL?: string, - * }|SmartscraperCreateParams $params - */ - public function create( - array|SmartscraperCreateParams $params, - ?RequestOptions $requestOptions = null, - ): CompletedSmartscraper { - [$parsed, $options] = SmartscraperCreateParams::parseRequest( - $params, - $requestOptions - ); - $resp = $this->client->request( - method: 'post', - path: 'smartscraper', - body: (object) $parsed, - options: $options, - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(CompletedSmartscraper::class, value: $resp); - } - - /** - * Retrieve the status and results of a scraping operation. - */ - public function retrieve( - string $requestID, - ?RequestOptions $requestOptions = null - ): CompletedSmartscraper|FailedSmartscraper { - $resp = $this->client->request( - method: 'get', - path: ['smartscraper/%1$s', $requestID], - options: $requestOptions, - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(SmartscraperGetResponse::class, value: $resp); - } - - /** - * Retrieve the status and results of a scraping operation. - */ - public function list( - ?RequestOptions $requestOptions = null - ): CompletedSmartscraper|FailedSmartscraper { - $resp = $this->client->request( - method: 'get', - path: 'smartscraper', - options: $requestOptions - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(SmartscraperListResponse::class, value: $resp); - } -} diff --git a/src/Validate/ValidateAPIKeyResponse.php b/src/Validate/ValidateAPIKeyResponse.php new file mode 100644 index 0000000..9d2e350 --- /dev/null +++ b/src/Validate/ValidateAPIKeyResponse.php @@ -0,0 +1,52 @@ + */ + use SdkModel; + + use SdkResponse; + + #[Api(optional: true)] + public ?string $email; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + */ + public static function with(?string $email = null): self + { + $obj = new self; + + null !== $email && $obj->email = $email; + + return $obj; + } + + public function withEmail(string $email): self + { + $obj = clone $this; + $obj->email = $email; + + return $obj; + } +} diff --git a/src/Validate/ValidateService.php b/src/Validate/ValidateService.php deleted file mode 100644 index e40031a..0000000 --- a/src/Validate/ValidateService.php +++ /dev/null @@ -1,32 +0,0 @@ -client->request( - method: 'get', - path: 'validate', - options: $requestOptions - ); - - // @phpstan-ignore-next-line; - return Conversion::coerce(ValidateAPIKeyResponse::class, value: $resp); - } -} diff --git a/tests/Core/TestModel.php b/tests/Core/TestModel.php index 1b2e6c9..4435f18 100644 --- a/tests/Core/TestModel.php +++ b/tests/Core/TestModel.php @@ -6,12 +6,13 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Scrapegraphai\Core\Attributes\Api; -use Scrapegraphai\Core\Concerns\Model; +use Scrapegraphai\Core\Concerns\SdkModel; use Scrapegraphai\Core\Contracts\BaseModel; 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/CrawlTest.php b/tests/Services/CrawlTest.php similarity index 58% rename from tests/Resources/CrawlTest.php rename to tests/Services/CrawlTest.php index 97139dd..6480cfb 100644 --- a/tests/Resources/CrawlTest.php +++ b/tests/Services/CrawlTest.php @@ -1,13 +1,11 @@ client->crawl->retrieveResults('task_id'); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } #[Test] @@ -47,10 +45,9 @@ public function testStart(): void $this->markTestSkipped('Prism tests are disabled'); } - $params = CrawlStartParams::from(url: 'https://example.com'); - $result = $this->client->crawl->start($params); + $result = $this->client->crawl->start(url: 'https://example.com'); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } #[Test] @@ -60,19 +57,8 @@ public function testStartWithOptionalParams(): void $this->markTestSkipped('Prism tests are disabled'); } - $params = CrawlStartParams::from( - url: 'https://example.com', - depth: 0, - extractionMode: true, - maxPages: 1, - prompt: 'prompt', - renderHeavyJs: true, - rules: (new Rules)->setExclude(['string'])->setSameDomain(true), - schema: (object) [], - sitemap: true, - ); - $result = $this->client->crawl->start($params); + $result = $this->client->crawl->start(url: 'https://example.com'); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } } diff --git a/tests/Resources/CreditsTest.php b/tests/Services/CreditsTest.php similarity index 88% rename from tests/Resources/CreditsTest.php rename to tests/Services/CreditsTest.php index f87fe38..e23f6db 100644 --- a/tests/Resources/CreditsTest.php +++ b/tests/Services/CreditsTest.php @@ -1,6 +1,6 @@ client->credits->retrieve(); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } } diff --git a/tests/Resources/FeedbackTest.php b/tests/Services/FeedbackTest.php similarity index 67% rename from tests/Resources/FeedbackTest.php rename to tests/Services/FeedbackTest.php index 2da6474..185d4f7 100644 --- a/tests/Resources/FeedbackTest.php +++ b/tests/Services/FeedbackTest.php @@ -1,12 +1,11 @@ markTestSkipped('Prism tests are disabled'); } - $params = FeedbackSubmitParams::from( + $result = $this->client->feedback->submit( rating: 0, requestID: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e' ); - $result = $this->client->feedback->submit($params); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } #[Test] @@ -50,13 +48,11 @@ public function testSubmitWithOptionalParams(): void $this->markTestSkipped('Prism tests are disabled'); } - $params = FeedbackSubmitParams::from( + $result = $this->client->feedback->submit( rating: 0, - requestID: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', - feedbackText: 'feedback_text', + requestID: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e' ); - $result = $this->client->feedback->submit($params); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } } diff --git a/tests/Resources/GenerateSchemaTest.php b/tests/Services/GenerateSchemaTest.php similarity index 66% rename from tests/Resources/GenerateSchemaTest.php rename to tests/Services/GenerateSchemaTest.php index a148898..a7ee938 100644 --- a/tests/Resources/GenerateSchemaTest.php +++ b/tests/Services/GenerateSchemaTest.php @@ -1,12 +1,11 @@ markTestSkipped('Prism tests are disabled'); } - $params = GenerateSchemaCreateParams::from( + $result = $this->client->generateSchema->create( userPrompt: 'Create a schema for product information including name, price, and reviews', ); - $result = $this->client->generateSchema->create($params); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } #[Test] @@ -49,13 +47,11 @@ public function testCreateWithOptionalParams(): void $this->markTestSkipped('Prism tests are disabled'); } - $params = GenerateSchemaCreateParams::from( + $result = $this->client->generateSchema->create( userPrompt: 'Create a schema for product information including name, price, and reviews', - existingSchema: (object) [], ); - $result = $this->client->generateSchema->create($params); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } #[Test] @@ -65,12 +61,10 @@ public function testRetrieve(): void $this->markTestSkipped('Prism tests are disabled'); } - $result = $this - ->client - ->generateSchema - ->retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e') - ; + $result = $this->client->generateSchema->retrieve( + '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e' + ); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } } diff --git a/tests/Resources/HealthzTest.php b/tests/Services/HealthzTest.php similarity index 88% rename from tests/Resources/HealthzTest.php rename to tests/Services/HealthzTest.php index 9eef797..9fddcab 100644 --- a/tests/Resources/HealthzTest.php +++ b/tests/Services/HealthzTest.php @@ -1,6 +1,6 @@ client->healthz->check(); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } } diff --git a/tests/Resources/MarkdownifyTest.php b/tests/Services/MarkdownifyTest.php similarity index 59% rename from tests/Resources/MarkdownifyTest.php rename to tests/Services/MarkdownifyTest.php index 0b7866d..cf98528 100644 --- a/tests/Resources/MarkdownifyTest.php +++ b/tests/Services/MarkdownifyTest.php @@ -1,12 +1,11 @@ markTestSkipped('Prism tests are disabled'); } - $params = MarkdownifyConvertParams::from(websiteURL: 'https://example.com'); - $result = $this->client->markdownify->convert($params); + $result = $this->client->markdownify->convert( + websiteURL: 'https://example.com' + ); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } #[Test] @@ -47,14 +47,11 @@ public function testConvertWithOptionalParams(): void $this->markTestSkipped('Prism tests are disabled'); } - $params = MarkdownifyConvertParams::from( - websiteURL: 'https://example.com', - headers: ['foo' => 'string'], - steps: ['string'], + $result = $this->client->markdownify->convert( + websiteURL: 'https://example.com' ); - $result = $this->client->markdownify->convert($params); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } #[Test] @@ -64,12 +61,10 @@ public function testRetrieveStatus(): void $this->markTestSkipped('Prism tests are disabled'); } - $result = $this - ->client - ->markdownify - ->retrieveStatus('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e') - ; + $result = $this->client->markdownify->retrieveStatus( + '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e' + ); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } } diff --git a/tests/Resources/SearchscraperTest.php b/tests/Services/SearchscraperTest.php similarity index 62% rename from tests/Resources/SearchscraperTest.php rename to tests/Services/SearchscraperTest.php index d355d3c..2831df2 100644 --- a/tests/Resources/SearchscraperTest.php +++ b/tests/Services/SearchscraperTest.php @@ -1,12 +1,11 @@ markTestSkipped('Prism tests are disabled'); } - $params = SearchscraperCreateParams::from( + $result = $this->client->searchscraper->create( userPrompt: 'Find the latest AI news and extract headlines and summaries' ); - $result = $this->client->searchscraper->create($params); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } #[Test] @@ -49,15 +47,11 @@ public function testCreateWithOptionalParams(): void $this->markTestSkipped('Prism tests are disabled'); } - $params = SearchscraperCreateParams::from( - userPrompt: 'Find the latest AI news and extract headlines and summaries', - headers: ['foo' => 'string'], - numResults: 3, - outputSchema: (object) [], + $result = $this->client->searchscraper->create( + userPrompt: 'Find the latest AI news and extract headlines and summaries' ); - $result = $this->client->searchscraper->create($params); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } #[Test] @@ -67,12 +61,10 @@ public function testRetrieveStatus(): void $this->markTestSkipped('Prism tests are disabled'); } - $result = $this - ->client - ->searchscraper - ->retrieveStatus('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e') - ; + $result = $this->client->searchscraper->retrieveStatus( + '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e' + ); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } } diff --git a/tests/Resources/SmartscraperTest.php b/tests/Services/SmartscraperTest.php similarity index 59% rename from tests/Resources/SmartscraperTest.php rename to tests/Services/SmartscraperTest.php index 2ae80d5..8c2e846 100644 --- a/tests/Resources/SmartscraperTest.php +++ b/tests/Services/SmartscraperTest.php @@ -1,12 +1,11 @@ markTestSkipped('Prism tests are disabled'); } - $params = SmartscraperCreateParams::from( + $result = $this->client->smartscraper->create( userPrompt: 'Extract the product name, price, and description' ); - $result = $this->client->smartscraper->create($params); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } #[Test] @@ -49,21 +47,11 @@ public function testCreateWithOptionalParams(): void $this->markTestSkipped('Prism tests are disabled'); } - $params = SmartscraperCreateParams::from( - userPrompt: 'Extract the product name, price, and description', - cookies: ['foo' => 'string'], - headers: ['foo' => 'string'], - numberOfScrolls: 0, - outputSchema: (object) [], - renderHeavyJs: true, - steps: ['string'], - totalPages: 1, - websiteHTML: 'website_html', - websiteURL: 'https://example.com/product', + $result = $this->client->smartscraper->create( + userPrompt: 'Extract the product name, price, and description' ); - $result = $this->client->smartscraper->create($params); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } #[Test] @@ -73,13 +61,11 @@ public function testRetrieve(): void $this->markTestSkipped('Prism tests are disabled'); } - $result = $this - ->client - ->smartscraper - ->retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e') - ; + $result = $this->client->smartscraper->retrieve( + '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e' + ); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } #[Test] @@ -91,6 +77,6 @@ public function testList(): void $result = $this->client->smartscraper->list(); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } } diff --git a/tests/Resources/ValidateTest.php b/tests/Services/ValidateTest.php similarity index 88% rename from tests/Resources/ValidateTest.php rename to tests/Services/ValidateTest.php index b1e9d91..7d45ecc 100644 --- a/tests/Resources/ValidateTest.php +++ b/tests/Services/ValidateTest.php @@ -1,6 +1,6 @@ client->validate->apiKey(); - $this->assertTrue(true); // @phpstan-ignore-line + $this->assertTrue(true); // @phpstan-ignore method.alreadyNarrowedType } }