You are here

public function SpecFetcher::fetchSpec in Apigee API Catalog 8

Same name and namespace in other branches
  1. 8.2 src/SpecFetcher.php \Drupal\apigee_api_catalog\SpecFetcher::fetchSpec()

Fetch OpenAPI specification file from URL.

Takes care of updating an ApiDoc file entity with the updated spec file. If "spec_file_source" uses a URL, it will fetch the specified file and put it in the "spec" file field. If it uses a "file", it won't change it. This method only updates the file entity if it completed without error (if it returns STATUS_UPDATED or STATUS_UNCHANGED), it does not save the ApiDoc entity.

Parameters

\Drupal\apigee_api_catalog\Entity\ApiDocInterface $apidoc: The ApiDoc entity.

Return value

string Returns the status of the operation. If it is STATUS_UPDATED or STATUS_UNCHANGED, the ApiDoc entity will need to be saved to store the changes.

Overrides SpecFetcherInterface::fetchSpec

File

src/SpecFetcher.php, line 102

Class

SpecFetcher
Class SpecFetcher.

Namespace

Drupal\apigee_api_catalog

Code

public function fetchSpec(ApiDocInterface $apidoc) : string {
  $spec_value = $apidoc
    ->get('spec')
    ->isEmpty() ? [] : $apidoc
    ->get('spec')
    ->getValue()[0];

  // If "spec_file_source" uses URL, grab file from "file_link" and save it
  // into the "spec" file field. The file_link field should already have
  // validated that a valid file exists at that URL.
  if ($apidoc
    ->get('spec_file_source')->value === ApiDocInterface::SPEC_AS_URL) {

    // If the file_link field is empty, return without changes.
    // TODO: The file link shouldn't be empty. Consider throwing an error.
    if ($apidoc
      ->get('file_link')
      ->isEmpty()) {
      return FALSE;
    }
    $source_uri = $apidoc
      ->get('file_link')
      ->getValue()[0]['uri'];
    $source_uri = Url::fromUri($source_uri, [
      'absolute' => TRUE,
    ])
      ->toString();
    $request = new Request('GET', $source_uri);
    $options = [
      'exceptions' => TRUE,
      'allow_redirects' => [
        'strict' => TRUE,
      ],
    ];

    // Generate conditional GET header.
    if (!$apidoc
      ->get('fetched_timestamp')
      ->isEmpty()) {
      $request = $request
        ->withAddedHeader('If-Modified-Since', gmdate(DateTimePlus::RFC7231, $apidoc
        ->get('fetched_timestamp')->value));
    }
    try {
      $response = $this->httpClient
        ->send($request, $options);
    } catch (GuzzleException $e) {
      $this
        ->log(LogLevel::ERROR, 'API Doc %label: Could not retrieve OpenAPI specification file located at %url.', [
        '%url' => $source_uri,
        '%label' => $apidoc
          ->label(),
      ]);
      return self::STATUS_ERROR;
    }

    // In case of a 304 Not Modified there are no changes, but update
    // last fetched timestamp.
    if ($response
      ->getStatusCode() === 304) {
      $apidoc
        ->set('fetched_timestamp', time());
      return self::STATUS_UNCHANGED;
    }
    $data = (string) $response
      ->getBody();
    if (($file_size = $response
      ->getBody()
      ->getSize()) && $file_size < 1) {
      $this
        ->log(LogLevel::ERROR, 'API Doc %label: OpenAPI specification file located at %url is empty.', [
        '%url' => $source_uri,
        '%label' => $apidoc
          ->label(),
      ]);
      return self::STATUS_ERROR;
    }

    // Only save file if it hasn't been fetched previously.
    $data_md5 = md5($data);
    $prev_md5 = $apidoc
      ->get('spec_md5')
      ->isEmpty() ? NULL : $apidoc
      ->get('spec_md5')->value;
    if ($prev_md5 != $data_md5) {
      $filename = $this->fileSystem
        ->basename($source_uri);
      $specs_definition = $apidoc
        ->getFieldDefinition('spec')
        ->getItemDefinition();
      $target_dir = $specs_definition
        ->getSetting('file_directory');
      $uri_scheme = $specs_definition
        ->getSetting('uri_scheme');
      $destination = "{$uri_scheme}://{$target_dir}/";
      try {
        $this
          ->checkRequirements($destination);
        $file = file_save_data($data, $destination . $filename, FileSystemInterface::EXISTS_RENAME);
        if (empty($file)) {
          throw new \Exception('Could not save API Doc specification file.');
        }
      } catch (\Exception $e) {
        $this
          ->log(LogLevel::ERROR, 'Error while saving API Doc spec file from URL on API Doc ID: %id. Error: %error', [
          '%id' => $apidoc
            ->id(),
          '%error' => $e
            ->getMessage(),
        ]);
        return self::STATUS_ERROR;
      }
      $spec_value = [
        'target_id' => $file
          ->id(),
      ] + $spec_value;
      $apidoc
        ->set('spec', $spec_value);
      $apidoc
        ->set('spec_md5', $data_md5);
      $apidoc
        ->set('fetched_timestamp', time());
      return self::STATUS_UPDATED;
    }
  }
  return self::STATUS_ERROR;
}