Skip to content

Commit

Permalink
Add optional raw response processing to query context (#24)
Browse files Browse the repository at this point in the history
* Move response processing to query context

* Lint

* Adjust approach

* Ya ya, was working on it

* Add comment

* Add tests

* Lint

* more lint

* WS fix, wut.

* More comments

* Improve type annotations to reflect JsonObject capabilities

* Add additional test cases asserting flexible response data types

* Improve standard processing test

* Adjust verbiage, single call

* Lint

---------

Co-authored-by: chriszarate <[email protected]>
  • Loading branch information
mhsdef and chriszarate authored Sep 6, 2024
1 parent ca2ee2a commit 097ddee
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 17 deletions.
2 changes: 2 additions & 0 deletions inc/config/http-query-context/http-query-context.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ public function get_request_headers( array $input_variables ): array;
public function get_request_body( array $input_variables ): array|null;
public function get_query_name(): string;
public function get_query_runner(): QueryRunnerInterface;
public function is_response_data_collection(): bool;
public function process_response( string $raw_response_data, array $input_variables ): string|array|object|null;
}
25 changes: 24 additions & 1 deletion inc/config/query-context/query-context.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ public function get_request_method(): string {
/**
* Override this method to specify custom request headers for this query.
*
* @param array $input_variables The input variables for this query.
* @return array
*/
public function get_request_headers( array $input_variables ): array {
Expand All @@ -142,7 +143,7 @@ public function get_request_headers( array $input_variables ): array {
* @param array $input_variables The input variables for this query.
* @return array|null
*/
public function get_request_body( array $input_variables ): array|null {
public function get_request_body( array $input_variables ): ?array {
return null;
}

Expand All @@ -160,4 +161,26 @@ public function get_query_name(): string {
public function get_query_runner(): QueryRunnerInterface {
return new QueryRunner( $this );
}

/**
* Override this method to process the raw response data from the query before
* it is passed to the query runner and the output variables are extracted. The
* result can be a JSON string, a PHP associative array, a PHP object, or null.
*
* @param string $raw_response_data The raw response data.
* @param array $input_variables The input variables for this query.
* @return string|array|object|null
*/
public function process_response( string $raw_response_data, array $input_variables ): string|array|object|null {
return $raw_response_data;
}

/**
* Authoritative truth of whether output is expected to be a collection.
*
* @return bool
*/
final public function is_response_data_collection(): bool {
return $this->output_variables['is_collection'] ?? false;
}
}
30 changes: 24 additions & 6 deletions inc/config/query-runner/query-runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,19 @@ public function execute( array $input_variables ): array|WP_Error {
}

// The body is a stream... if we need to read it in chunks, etc. we can do so here.
$response_data = $response->getBody()->getContents();
$raw_response_data = $response->getBody()->getContents();

$is_collection = $this->query_context->output_variables['is_collection'] ?? false;

if ( isset( $response_data['errors'][0]['message'] ) ) {
if ( isset( $raw_response_data['errors'][0]['message'] ) ) {
$logger = LoggerManager::instance();
$logger->warning( sprintf( 'Query error: %s', esc_html( $response_data['errors'][0]['message'] ) ) );
$logger->warning( sprintf( 'Query error: %s', esc_html( $raw_response_data['errors'][0]['message'] ) ) );
}

// Optionally process the raw response data using query context custom logic.
$response_data = $this->query_context->process_response( $raw_response_data, $input_variables );

// Determine if the response data is expected to be a collection.
$is_collection = $this->query_context->is_response_data_collection();

// This method always returns an array, even if it's a single item. This
// ensures a consistent response shape. The requestor is expected to inspect
// is_collection and unwrap if necessary.
Expand All @@ -126,6 +130,12 @@ private function get_field_value( array|string $field_value, string $default_val
: ( $field_value[0] ?? $default_value );

switch ( $field_type ) {
case 'base64':
return base64_decode( $field_value_single );

case 'html':
return $field_value_single;

case 'price':
return sprintf( '$%s', number_format( $field_value_single, 2 ) );

Expand All @@ -136,7 +146,15 @@ private function get_field_value( array|string $field_value, string $default_val
return $field_value_single;
}

private function map_fields( $response_data, $is_collection = false ): array|null {
/**
* Map fields from the response data using the output variables defined by
* the query.
*
* @param string|array|object|null $response_data The response data to map. Can be JSON string, PHP associative array, PHP object, or null.
* @param bool $is_collection Whether the response data is a collection.
* @return array|null The mapped fields.
*/
private function map_fields( string|array|object|null $response_data, bool $is_collection ): ?array {
$root = $response_data;
$output_variables = $this->query_context->output_variables;

Expand Down
103 changes: 103 additions & 0 deletions tests/inc/config/QueryContextTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

namespace RemoteDataBlocks\Tests\Config;

use PHPUnit\Framework\TestCase;
use RemoteDataBlocks\Config\QueryContext;
use RemoteDataBlocks\Test\TestDatasource;
use GuzzleHttp\Psr7\Response;

class QueryContextTest extends TestCase {

private $datasource;
private $query_context;

protected function setUp(): void {
$this->datasource = new TestDatasource();
$this->query_context = new QueryContext( $this->datasource );
}

public function testGetEndpoint() {
$result = $this->query_context->get_endpoint( [] );
$this->assertEquals( 'https://example.com', $result );
}

public function testGetImageUrl() {
$result = $this->query_context->get_image_url();
$this->assertNull( $result );
}

public function testGetMetadata() {
$mock_response = new Response( 200, [ 'Age' => '60' ] );
$results = [ [ 'id' => 1 ], [ 'id' => 2 ] ];

$metadata = $this->query_context->get_metadata( $mock_response, $results );

$this->assertArrayHasKey( 'last_updated', $metadata );
$this->assertArrayHasKey( 'total_count', $metadata );
$this->assertEquals( 'Last updated', $metadata['last_updated']['name'] );
$this->assertEquals( 'string', $metadata['last_updated']['type'] );
$this->assertEquals( 'Total count', $metadata['total_count']['name'] );
$this->assertEquals( 'number', $metadata['total_count']['type'] );
$this->assertEquals( 2, $metadata['total_count']['value'] );
}

public function testGetRequestMethod() {
$this->assertEquals( 'GET', $this->query_context->get_request_method() );
}

public function testGetRequestHeaders() {
$result = $this->query_context->get_request_headers( [] );
$this->assertEquals( [ 'Content-Type' => 'application/json' ], $result );
}

public function testGetRequestBody() {
$this->assertNull( $this->query_context->get_request_body( [] ) );
}

public function testGetQueryName() {
$this->assertEquals( 'Query', $this->query_context->get_query_name() );
}

public function testIsResponseDataCollection() {
$this->assertFalse( $this->query_context->is_response_data_collection() );

$this->query_context->output_variables['is_collection'] = true;
$this->assertTrue( $this->query_context->is_response_data_collection() );
}

public function testDefaultProcessResponse() {
$raw_data = '{"key": "value"}';
$this->assertEquals( $raw_data, $this->query_context->process_response( $raw_data, [] ) );
}

public function testCustomProcessResponse() {
$custom_query_context = new class($this->datasource) extends QueryContext {
public function process_response( string $raw_response_data, array $input_variables ): string {
// Convert HTML to JSON
$dom = new \DOMDocument();
$dom->loadHTML( $raw_response_data, LIBXML_NOERROR );
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$title = $dom->getElementsByTagName( 'title' )->item( 0 )->nodeValue;
$paragraphs = $dom->getElementsByTagName( 'p' );
$content = [];
foreach ( $paragraphs as $p ) {
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$content[] = $p->nodeValue;
}

$data = [
'title' => $title,
'content' => $content,
];

return wp_json_encode( $data );
}
};

$html_data = '<html><head><title>Test Page</title></head><body><p>Paragraph 1</p><p>Paragraph 2</p></body></html>';
$expected_json = '{"title":"Test Page","content":["Paragraph 1","Paragraph 2"]}';

$this->assertEquals( $expected_json, $custom_query_context->process_response( $html_data, [] ) );
}
}
Loading

0 comments on commit 097ddee

Please sign in to comment.