new BodyLocation(), 'header' => new HeaderLocation(), 'reasonPhrase' => new ReasonPhraseLocation(), 'statusCode' => new StatusCodeLocation(), 'xml' => new XmlLocation(), 'json' => new JsonLocation(), ]; } $this->responseLocations = $responseLocations + $defaultResponseLocations; $this->description = $description; $this->process = $process; } /** * Deserialize the response into the specified result representation * * @param ResponseInterface $response * @param RequestInterface|null $request * @param CommandInterface $command * @return Result|ResultInterface|void|ResponseInterface */ public function __invoke(ResponseInterface $response, RequestInterface $request, CommandInterface $command) { // If the user don't want to process the result, just return the plain response here if ($this->process === false) { return $response; } $name = $command->getName(); $operation = $this->description->getOperation($name); $this->handleErrorResponses($response, $request, $command, $operation); // Add a default Model as the result if no matching schema was found if (!($modelName = $operation->getResponseModel())) { // Not sure if this should be empty or contains the response. // Decided to do it how it was in the old version for now. return new Result(); } $model = $operation->getServiceDescription()->getModel($modelName); if (!$model) { throw new \RuntimeException("Unknown model: {$modelName}"); } return $this->visit($model, $response); } /** * Handles visit() and after() methods of the Response locations * * @param Parameter $model * @param ResponseInterface $response * @return Result|ResultInterface|void */ protected function visit(Parameter $model, ResponseInterface $response) { $result = new Result(); $context = ['visitors' => []]; if ($model->getType() === 'object') { $result = $this->visitOuterObject($model, $result, $response, $context); } elseif ($model->getType() === 'array') { $result = $this->visitOuterArray($model, $result, $response, $context); } else { throw new \InvalidArgumentException('Invalid response model: ' . $model->getType()); } // Call the after() method of each found visitor /** @var ResponseLocationInterface $visitor */ foreach ($context['visitors'] as $visitor) { $result = $visitor->after($result, $response, $model); } return $result; } /** * Handles the before() method of Response locations * * @param string $location * @param Parameter $model * @param ResultInterface $result * @param ResponseInterface $response * @param array $context * @return ResultInterface */ private function triggerBeforeVisitor( $location, Parameter $model, ResultInterface $result, ResponseInterface $response, array &$context ) { if (!isset($this->responseLocations[$location])) { throw new \RuntimeException("Unknown location: $location"); } $context['visitors'][$location] = $this->responseLocations[$location]; $result = $this->responseLocations[$location]->before( $result, $response, $model ); return $result; } /** * Visits the outer object * * @param Parameter $model * @param ResultInterface $result * @param ResponseInterface $response * @param array $context * @return ResultInterface */ private function visitOuterObject( Parameter $model, ResultInterface $result, ResponseInterface $response, array &$context ) { $parentLocation = $model->getLocation(); // If top-level additionalProperties is a schema, then visit it $additional = $model->getAdditionalProperties(); if ($additional instanceof Parameter) { // Use the model location if none set on additionalProperties. $location = $additional->getLocation() ?: $parentLocation; $result = $this->triggerBeforeVisitor($location, $model, $result, $response, $context); } // Use 'location' from all individual defined properties, but fall back // to the model location if no per-property location is set. Collect // the properties that need to be visited into an array. $visitProperties = []; foreach ($model->getProperties() as $schema) { $location = $schema->getLocation() ?: $parentLocation; if ($location) { $visitProperties[] = [$location, $schema]; // Trigger the before method on each unique visitor location if (!isset($context['visitors'][$location])) { $result = $this->triggerBeforeVisitor($location, $model, $result, $response, $context); } } } // Actually visit each response element foreach ($visitProperties as $property) { $result = $this->responseLocations[$property[0]]->visit($result, $response, $property[1]); } return $result; } /** * Visits the outer array * * @param Parameter $model * @param ResultInterface $result * @param ResponseInterface $response * @param array $context * @return ResultInterface|void */ private function visitOuterArray( Parameter $model, ResultInterface $result, ResponseInterface $response, array &$context ) { // Use 'location' defined on the top of the model if (!($location = $model->getLocation())) { return; } // Trigger the before method on each unique visitor location if (!isset($context['visitors'][$location])) { $result = $this->triggerBeforeVisitor($location, $model, $result, $response, $context); } // Visit each item in the response $result = $this->responseLocations[$location]->visit($result, $response, $model); return $result; } /** * Reads the "errorResponses" from commands, and trigger appropriate exceptions * * In order for the exception to be properly triggered, all your exceptions must be instance * of "GuzzleHttp\Command\Exception\CommandException". If that's not the case, your exceptions will be wrapped * around a CommandException * * @param ResponseInterface $response * @param RequestInterface $request * @param CommandInterface $command * @param Operation $operation */ protected function handleErrorResponses( ResponseInterface $response, RequestInterface $request, CommandInterface $command, Operation $operation ) { $errors = $operation->getErrorResponses(); // We iterate through each errors in service description. If the descriptor contains both a phrase and // status code, there must be an exact match of both. Otherwise, a match of status code is enough $bestException = null; foreach ($errors as $error) { $code = (int) $error['code']; if ($response->getStatusCode() !== $code) { continue; } if (isset($error['phrase']) && ! ($error['phrase'] === $response->getReasonPhrase())) { continue; } $bestException = $error['class']; // If there is an exact match of phrase + code, then we cannot find a more specialized exception in // the array, so we can break early instead of iterating the remaining ones if (isset($error['phrase'])) { break; } } if (null !== $bestException) { throw new $bestException($response->getReasonPhrase(), $command, null, $request, $response); } // If we reach here, no exception could be match from descriptor, and Guzzle exception will propagate if // option "http_errors" is set to true, which is the default setting. } }