private CompletionStage<String> convertId(String input, RestRequest restRequest, BlobProperties blobProperties)
        throws RestServiceException {
      CompletionStage<String> conversionFuture;
      LOGGER.debug("input for convertId : " + input);
      LOGGER.debug("restRequest for convertId : " + restRequest);

      boolean localGetHeader = RestUtils.getBooleanHeader(restRequest.getArgs(), LOCAL_GET, false);
      if (RequestPath.matchesOperation(input, Operations.NAMED_BLOB)) {
        NamedBlobPath namedBlobPath = NamedBlobPath.parse(input, Collections.emptyMap());
        GetOption getOption = RestUtils.getGetOption(restRequest, GetOption.None);
        if (restRequest.getRestMethod() == RestMethod.DELETE) {
          // on delete requests we can soft delete the record from NamedBlobDb and get the blob ID in one step.
          conversionFuture = getNamedBlobDb().delete(namedBlobPath.getAccountName(), namedBlobPath.getContainerName(),
              namedBlobPath.getBlobName()).thenApply(DeleteResult::getBlobIds);
        }  else if (restRequest.getRestMethod() == RestMethod.PUT && RestUtils.getRequestPath(restRequest)
            .matchesOperation(Operations.UPDATE_TTL)) {
          //If operation == UPDATE_TTL, we will get the version and blobId info from named blob first
          //and do update ttl in routerCallBack.
          conversionFuture = getNamedBlobDb().get(namedBlobPath.getAccountName(), namedBlobPath.getContainerName(),
              namedBlobPath.getBlobName(), getOption, false).thenApply(result -> {
            restRequest.setArg(NAMED_BLOB_VERSION, result.getVersion());
            return result.getBlobId();
          });
        } else {
          conversionFuture = getNamedBlobDb().get(namedBlobPath.getAccountName(), namedBlobPath.getContainerName(),
              namedBlobPath.getBlobName(), getOption, localGetHeader).thenApply(NamedBlobRecord::getBlobId);
        }
      } else if (isNamedBlobPutRequest(restRequest) || isS3MultipartUploadCompleteRequest(restRequest)) {
        if (restRequest.getArgs().containsKey(NAMED_BLOB_VERSION)) {
          long namedBlobVersion = (long) restRequest.getArgs().get(NAMED_BLOB_VERSION);
          String blobIdClean = RestUtils.stripSlashAndExtensionFromId(input);
          NamedBlobPath namedBlobPath =
              NamedBlobPath.parse(RestUtils.getRequestPath(restRequest), restRequest.getArgs());
          NamedBlobRecord record = new NamedBlobRecord(namedBlobPath.getAccountName(), namedBlobPath.getContainerName(),
              namedBlobPath.getBlobName(), blobIdClean, Utils.Infinite_Time, namedBlobVersion);
          conversionFuture = namedBlobDb.updateBlobTtlAndStateToReady(record).thenApply(result -> {
            return result.getInsertedRecord().getBlobId();
          });
        } else {
          Objects.requireNonNull(blobProperties, "blobProperties cannot be null.");
          NamedBlobPath namedBlobPath = NamedBlobPath.parse(RestUtils.getRequestPath(restRequest), restRequest.getArgs());
          String blobId = RestUtils.stripSlashAndExtensionFromId(input);
          long expirationTimeMs =
              Utils.addSecondsToEpochTime(blobProperties.getCreationTimeInMs(), blobProperties.getTimeToLiveInSeconds());
          // Please note that the modified_ts column in DB will be auto-populated by the DB server.
          NamedBlobRecord record = new NamedBlobRecord(namedBlobPath.getAccountName(), namedBlobPath.getContainerName(),
              namedBlobPath.getBlobName(), blobId, expirationTimeMs, 0, blobProperties.getBlobSize());
          NamedBlobState state = NamedBlobState.READY;
          if (blobProperties.getTimeToLiveInSeconds() == Utils.Infinite_Time) {
            // Set named blob state as 'IN_PROGRESS', will set the state to be 'READY' in the ttlUpdate success callback: routerTtlUpdateCallback
            state = NamedBlobState.IN_PROGRESS;
          }
          conversionFuture = getNamedBlobDb().put(record, state, RestUtils.isUpsertForNamedBlob(restRequest.getArgs())).thenApply(
              result -> {
                restRequest.setArg(NAMED_BLOB_VERSION, result.getInsertedRecord().getVersion());
                return result.getInsertedRecord().getBlobId();
              });
        }
      } else {
        String decryptedInput =
            parseSignedIdIfRequired(restRequest, input.startsWith("/") ? input.substring(1) : input);
        conversionFuture = CompletableFuture.completedFuture(RestUtils.stripSlashAndExtensionFromId(decryptedInput));
      }
      return conversionFuture;
    }
Convert the input ID to the requested output. If it's the named blob request, return the blobId from NameBlobDb,
otherwise return the input with leading slash and extension be stripped.

@param input          the input blob ID.
@param restRequest    the {@link RestRequest} to set arguments in.
@param blobProperties the {@link BlobProperties} for an uploaded blob. This will be used for named blob PUT requests.
@return the {@link CompletionStage} that will be completed with the converted ID
@throws RestServiceException
ambry-frontend/src/main/java/com/github/ambry/frontend/AmbryIdConverterFactory.java