All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer Maven / Gradle / Ivy

There is a newer version: 3.9.2
Show newest version
/*
 * Copyright (C) 2016-2024 Thomas Akehurst
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.tomakehurst.wiremock.extension.responsetemplating;

import static com.github.tomakehurst.wiremock.client.WireMock.serverError;
import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull;

import com.github.jknack.handlebars.HandlebarsException;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.common.FileSource;
import com.github.tomakehurst.wiremock.common.TextFile;
import com.github.tomakehurst.wiremock.common.url.PathTemplate;
import com.github.tomakehurst.wiremock.extension.*;
import com.github.tomakehurst.wiremock.http.*;
import com.github.tomakehurst.wiremock.stubbing.ServeEvent;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
import com.github.tomakehurst.wiremock.stubbing.SubEvent;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

public class ResponseTemplateTransformer
    implements StubLifecycleListener, ResponseDefinitionTransformerV2 {

  public static final String NAME = "response-template";

  private final boolean global;
  private final FileSource files;
  private final TemplateEngine templateEngine;

  private final List templateModelDataProviders;

  public ResponseTemplateTransformer(
      TemplateEngine templateEngine,
      boolean global,
      FileSource files,
      List templateModelDataProviders) {
    this.templateEngine = templateEngine;
    this.global = global;
    this.files = files;
    this.templateModelDataProviders = templateModelDataProviders;
  }

  @Override
  public boolean applyGlobally() {
    return global;
  }

  @Override
  public String getName() {
    return NAME;
  }

  @Override
  public ResponseDefinition transform(ServeEvent serveEvent) {
    try {
      final Request request = serveEvent.getRequest();
      final ResponseDefinition responseDefinition = serveEvent.getResponseDefinition();
      final Parameters parameters =
          getFirstNonNull(responseDefinition.getTransformerParameters(), Parameters.empty());

      ResponseDefinitionBuilder newResponseDefBuilder =
          ResponseDefinitionBuilder.like(responseDefinition);

      final PathTemplate pathTemplate =
          serveEvent.getStubMapping().getRequest().getUrlMatcher().getPathTemplate();

      final Map additionalModelData =
          templateModelDataProviders.stream()
              .map(provider -> provider.provideTemplateModelData(serveEvent).entrySet())
              .flatMap(Set::stream)
              .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

      final Map model = new HashMap<>();
      model.put("parameters", parameters);
      model.put("request", RequestTemplateModel.from(request, pathTemplate));
      model.putAll(addExtraModelElements(request, responseDefinition, files, parameters));
      model.putAll(additionalModelData);

      if (responseDefinition.specifiesTextBodyContent()) {
        boolean isJsonBody = responseDefinition.getReponseBody().isJson();
        HandlebarsOptimizedTemplate bodyTemplate =
            templateEngine.getTemplate(
                HttpTemplateCacheKey.forInlineBody(responseDefinition),
                responseDefinition.getTextBody());
        applyTemplatedResponseBody(newResponseDefBuilder, model, bodyTemplate, isJsonBody);
      } else if (responseDefinition.specifiesBodyFile()) {
        HandlebarsOptimizedTemplate filePathTemplate =
            templateEngine.getUncachedTemplate(responseDefinition.getBodyFileName());
        String compiledFilePath = uncheckedApplyTemplate(filePathTemplate, model);

        boolean disableBodyFileTemplating =
            parameters.getBoolean("disableBodyFileTemplating", false);
        if (disableBodyFileTemplating) {
          newResponseDefBuilder.withBodyFile(compiledFilePath);
        } else {
          TextFile file = files.getTextFileNamed(compiledFilePath);
          HandlebarsOptimizedTemplate bodyTemplate =
              templateEngine.getTemplate(
                  HttpTemplateCacheKey.forFileBody(responseDefinition, compiledFilePath),
                  file.readContentsAsString());
          applyTemplatedResponseBody(newResponseDefBuilder, model, bodyTemplate, false);
        }
      }

      if (responseDefinition.getHeaders() != null) {
        List newResponseHeaders =
            responseDefinition.getHeaders().all().stream()
                .map(
                    header -> {
                      ArrayList valueListBuilder = new ArrayList<>();
                      int index = 0;
                      for (String headerValue : header.values()) {
                        HandlebarsOptimizedTemplate template =
                            templateEngine.getTemplate(
                                HttpTemplateCacheKey.forHeader(
                                    responseDefinition, header.key(), index++),
                                headerValue);
                        valueListBuilder.add(uncheckedApplyTemplate(template, model));
                      }

                      return new HttpHeader(header.key(), valueListBuilder);
                    })
                .collect(Collectors.toList());
        newResponseDefBuilder.withHeaders(new HttpHeaders(newResponseHeaders));
      }

      if (responseDefinition.getProxyBaseUrl() != null) {
        HandlebarsOptimizedTemplate proxyBaseUrlTemplate =
            templateEngine.getTemplate(
                HttpTemplateCacheKey.forProxyUrl(responseDefinition),
                responseDefinition.getProxyBaseUrl());
        String newProxyBaseUrl = uncheckedApplyTemplate(proxyBaseUrlTemplate, model);

        ResponseDefinitionBuilder.ProxyResponseDefinitionBuilder newProxyResponseDefBuilder =
            newResponseDefBuilder.proxiedFrom(newProxyBaseUrl);

        if (responseDefinition.getAdditionalProxyRequestHeaders() != null) {
          List newResponseHeaders =
              responseDefinition.getAdditionalProxyRequestHeaders().all().stream()
                  .map(
                      header -> {
                        ArrayList valueListBuilder = new ArrayList<>();
                        int index = 0;
                        for (String headerValue : header.values()) {
                          HandlebarsOptimizedTemplate template =
                              templateEngine.getTemplate(
                                  HttpTemplateCacheKey.forHeader(
                                      responseDefinition, header.key(), index++),
                                  headerValue);
                          valueListBuilder.add(uncheckedApplyTemplate(template, model));
                        }
                        return new HttpHeader(header.key(), valueListBuilder);
                      })
                  .collect(Collectors.toList());
          HttpHeaders proxyHttpHeaders = new HttpHeaders(newResponseHeaders);
          for (String key : proxyHttpHeaders.keys()) {
            newProxyResponseDefBuilder.withAdditionalRequestHeader(
                key, proxyHttpHeaders.getHeader(key).firstValue());
          }
        }

        if (responseDefinition.getRemoveProxyRequestHeaders() != null) {
          for (String key : responseDefinition.getRemoveProxyRequestHeaders()) {
            newProxyResponseDefBuilder.withRemoveRequestHeader(key);
          }
        }

        return newProxyResponseDefBuilder.build();
      } else {
        return newResponseDefBuilder.build();
      }
    } catch (HandlebarsException he) {
      final String message = cleanUpHandlebarsErrorMessage(he.getMessage());
      serveEvent.appendSubEvent(SubEvent.error(message));
      return serverError()
          .withHeader(ContentTypeHeader.KEY, "text/plain")
          .withBody(message)
          .build();
    }
  }

  private static String cleanUpHandlebarsErrorMessage(String rawMessage) {
    return rawMessage.replaceAll("inline@[a-z0-9]+:", "").replaceAll("\n.*", "");
  }

  /** Override this to add extra elements to the template model */
  protected Map addExtraModelElements(
      Request request,
      ResponseDefinition responseDefinition,
      FileSource files,
      Parameters parameters) {
    return Collections.emptyMap();
  }

  private void applyTemplatedResponseBody(
      ResponseDefinitionBuilder newResponseDefBuilder,
      Map model,
      HandlebarsOptimizedTemplate bodyTemplate,
      boolean isJsonBody) {
    String bodyString = uncheckedApplyTemplate(bodyTemplate, model);
    Body body =
        isJsonBody
            ? Body.fromJsonBytes(bodyString.getBytes(StandardCharsets.UTF_8))
            : Body.fromOneOf(null, bodyString, null, null);
    newResponseDefBuilder.withResponseBody(body);
  }

  private String uncheckedApplyTemplate(HandlebarsOptimizedTemplate template, Object context) {
    return template.apply(context);
  }

  @Override
  public void afterStubRemoved(StubMapping stub) {
    templateEngine.invalidateCache();
  }

  @Override
  public void afterStubsReset() {
    templateEngine.invalidateCache();
  }

  public long getCacheSize() {
    return templateEngine.getCacheSize();
  }

  public Long getMaxCacheEntries() {
    return templateEngine.getMaxCacheEntries();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy