io.github.mike10004.harreplay.ReplayServerConfig Maven / Gradle / Ivy
package io.github.mike10004.harreplay;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import io.github.mike10004.harreplay.ReplayServerConfig.StringLiteral.StringLiteralTypeAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.Objects.requireNonNull;
/**
* Class that represents the configuration that a server replay process uses.
* You may find this to be a weird way to design a configuration object. The
* inspiration was the Node {@code har-replay-proxy} configuration object,
* and this remains compatible with that (mostly) while also (hopefully) being
* adequate for other replay server implementations.
*
* Instances are immutable; invoke {@link #builder()} to get a {@link Builder}.
*/
public class ReplayServerConfig {
/**
* Version. Use 1 for now.
*/
public final int version;
/**
* Maps from URLs to file system paths. Use these to serve matching URLs from the filesystem instead of the HAR.
*/
@JsonAdapter(ImmutableListTypeAdapterFactory.class)
public final ImmutableList mappings;
/**
* Definitions of replacements to execute for in the body of textual response content.
*/
@JsonAdapter(ImmutableListTypeAdapterFactory.class)
public final ImmutableList replacements;
@JsonAdapter(ImmutableListTypeAdapterFactory.class)
public final ImmutableList responseHeaderTransforms;
private ReplayServerConfig() {
this(1, ImmutableList.of(), ImmutableList.of(), ImmutableList.of());
}
/**
* Constructs an instance of the class.
* @param version the version
* @param mappings the mappings
* @param replacements the replacements
*/
public ReplayServerConfig(int version, Iterable mappings, Iterable replacements, Iterable responseHeaderTransforms) {
this.version = version;
this.mappings = ImmutableList.copyOf(mappings);
this.replacements = ImmutableList.copyOf(replacements);
this.responseHeaderTransforms = ImmutableList.copyOf(responseHeaderTransforms);
}
/**
* Constructs a new empty configuration object.
* @return the configuration object
*/
@SuppressWarnings("unused")
public static ReplayServerConfig empty() {
return new ReplayServerConfig();
}
public static Builder builder() {
return new Builder();
}
/**
* Interface for classes that represent the {@code match} field of a {@link Mapping}.
* Instances of this class represent a strategy to use when determining whether
* the URL of a request should be responded to with a file mapping.
*/
public interface MappingMatch {
/**
* Evaluates whether a given URL is a match for this instance.
* @param url the url
* @return true iff the URL is a match
*/
boolean evaluateUrlMatch(String url);
}
/**
* Interface for classes that represent the {@code path} field of a {@link Mapping}.
*/
public interface MappingPath {
/**
* Resolves the file to be served when the mapping matches.
* @param root the root to resolve relative paths against
* @param match the matching strategy instance
* @param url the URL that was matched
* @return the pathname
*/
File resolveFile(Path root, MappingMatch match, String url);
}
/**
* Interface for classes that represent the {@code match} field of a {@link Replacement}.
*/
public interface ReplacementMatch {}
/**
* Interface for classes that represent the {@code replace} field of a {@link Replacement}.
*/
public interface ReplacementReplace {
/**
* Produces the string that is to be used as replacement text.
* @param dictionary a dictionary (may not be necessary)
* @return the interpolated string
*/
String interpolate(VariableDictionary dictionary);
}
/**
* Parent interface of matching strategies for response header transforms.
*/
public interface ResponseHeaderTransformMatch {
Pattern asRegex();
}
/**
* Interface that represents a strategy for matching header names in response header transforms.
*/
public interface ResponseHeaderTransformNameMatch extends ResponseHeaderTransformMatch {
boolean isMatchingHeaderName(String headerName);
static ResponseHeaderTransformNameMatch always() {
return AlwaysMatch.getInstance();
}
}
/**
* Interface that represents a strategy for modifying a header name.
*/
public interface ResponseHeaderTransformNameImage {
/**
* Transforms the header name.
* @param headerName the header name
* @param nameMatchRegex the name matching regex
* @return the transformed name, or null if the header is to be removed
*/
@Nullable
String transformHeaderName(String headerName, Pattern nameMatchRegex);
/**
* Gets an instance that represents the identity transform.
* @return the identity transform
*/
static ResponseHeaderTransformNameImage identity() {
return IdentityImage.getInstance();
}
}
/**
* Interface that represents a strategy for matching header values in response header transforms.
*/
public interface ResponseHeaderTransformValueMatch extends ResponseHeaderTransformMatch {
/**
* Determines whether a header value is a match according to this instance.
* @param headerName the header name (in case you want it)
* @param headerValue the header value to test
* @return true iff the header value is a match for this instance
*/
boolean isMatchingHeaderValue(String headerName, String headerValue);
/**
* Gets an instance that represents a trivial always-true match.
* @return the matching strategy instance
*/
static ResponseHeaderTransformValueMatch always() {
return AlwaysMatch.getInstance();
}
}
/**
* Interface that represents a strategy for a response header transform to modify a header value.
*/
public interface ResponseHeaderTransformValueImage {
/**
* Transforms a header value.
* @param headerName the header name
* @param valueMatchRegex the regex that determined whether the header was a match
* @param headerValue the original header value
* @return the new header value, or null if the header is to be removed from the response
*/
@Nullable
String transformHeaderValue(String headerName, Pattern valueMatchRegex, String headerValue);
/**
* Gets an instance representing the identity transform. The identity transform does not
* modify the header.
* @return the identity image
*/
static ResponseHeaderTransformValueImage identity() {
return IdentityImage.getInstance();
}
}
private static final class AlwaysMatch implements ResponseHeaderTransformNameMatch, ResponseHeaderTransformValueMatch {
private static final AlwaysMatch INSTANCE = new AlwaysMatch();
private static final Pattern REGEX = Pattern.compile("^(.*)$");
private AlwaysMatch() {}
@Override
public Pattern asRegex() {
return REGEX;
}
@Override
public boolean isMatchingHeaderName(String headerName) {
return true;
}
@Override
public boolean isMatchingHeaderValue(String headerName, String headerValue) {
return true;
}
public static AlwaysMatch getInstance() {
return INSTANCE;
}
public String toString() {
return "ResponseHeaderTransformMatch{ALWAYS}";
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof AlwaysMatch;
}
}
private static final class IdentityImage implements ResponseHeaderTransformNameImage, ResponseHeaderTransformValueImage{
private static final IdentityImage INSTANCE = new IdentityImage();
private IdentityImage() {}
@Override
public String transformHeaderName(String headerName, Pattern nameMatchRegex) {
return headerName;
}
@Override
public String transformHeaderValue(String headerName, Pattern valueMatchRegex, String headerValue) {
return headerValue;
}
public static IdentityImage getInstance() {
return INSTANCE;
}
public String toString() {
return "ResponseHeaderTransformImage{IDENTITY}";
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof IdentityImage;
}
}
@VisibleForTesting
static final class RemoveHeader implements ResponseHeaderTransformNameImage, ResponseHeaderTransformValueImage {
static final String TYPE_FIELD_VALUE = "RemoveHeader";
@SuppressWarnings("unused") // used in deserialization
@SerializedName(CommonDeserializer.TYPE_FIELD_NAME)
private final String kind = TYPE_FIELD_VALUE;
private RemoveHeader() {}
private static final RemoveHeader INSTANCE = new RemoveHeader();
public static RemoveHeader getInstance() {
return INSTANCE;
}
@Nullable
@Override
public String transformHeaderName(String headerName, Pattern nameMatchRegex) {
return null;
}
@Nullable
@Override
public String transformHeaderValue(String headerName, Pattern valueMatchRegex, String headerValue) {
return null;
}
public String toString() {
return "ResponseHeaderTransformImage{REMOVE}";
}
@Override
public int hashCode() {
return RemoveHeader.class.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof RemoveHeader;
}
}
/**
* Mapping from URL to filesystem pathname. Match field is a string or Javascript regex, and path is a string.
* Path can contain $n references to substitute capture groups from match.
*/
@SuppressWarnings("unused")
public final static class Mapping {
/**
* Matching strategy for this mapping.
*/
public final MappingMatch match;
/**
* Pathname of the file to be served if the URL matches this mapping.
*/
public final MappingPath path;
/**
* Constructs a new mapping instance.
* @param match the matching strategy
* @param path pathname of the file to be served
*/
public Mapping(MappingMatch match, MappingPath path) {
this.match = requireNonNull(match);
this.path = requireNonNull(path);
}
/**
* Constructs a new mapping given a matcher and a destination file.
* @param match the matcher
* @param file the file to serve
* @return a mapping instance
*/
public static Mapping toFile(MappingMatch match, File file) {
return new Mapping(match, new StringLiteral(file.getAbsolutePath()));
}
/**
* Constructs a new mapping given an exact match strategy and a pathname
* @param literalMatch the string that must match the URL exactly
* @param file the file
* @return the mapping
*/
public static Mapping literalToFile(String literalMatch, File file) {
return Mapping.toFile(new StringLiteral(literalMatch), file);
}
public static Mapping regexToFile(String regex, File file) {
return Mapping.toFile(new RegexHolder(regex), file);
}
/**
* Constructs a new mapping given a matcher and a destination file.
* @param match the matcher
* @param path the file to serve; can contain $n groups from a match regex
* @return a mapping instance
*/
public static Mapping toPath(MappingMatch match, String path) {
return new Mapping(match, new StringLiteral(path));
}
public static Mapping literalToPath(String literalMatch, String path) {
return Mapping.toPath(new StringLiteral(literalMatch), path);
}
public static Mapping regexToPath(String regex, String path) {
return Mapping.toPath(new RegexHolder(regex), path);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Mapping mapping = (Mapping) o;
return Objects.equals(match, mapping.match) &&
Objects.equals(path, mapping.path);
}
@Override
public int hashCode() {
return Objects.hash(match, path);
}
}
/**
* Class that represents a string value for a field of a {@link Mapping} or {@link Replacement} instance.
*/
@com.google.gson.annotations.JsonAdapter(StringLiteralTypeAdapter.class)
public static class StringLiteral implements MappingMatch, MappingPath,
ReplacementMatch, ReplacementReplace,
ResponseHeaderTransformNameImage, ResponseHeaderTransformNameMatch,
ResponseHeaderTransformValueMatch, ResponseHeaderTransformValueImage {
public final String value;
private transient final Supplier regexSupplier;
private StringLiteral(String value_) {
this.value = requireNonNull(value_);
this.regexSupplier = Suppliers.memoize(() -> Pattern.compile("(" + Pattern.quote(value) + ")"));
}
/**
* Returns an instance representing a string.
* @param value the string value
* @return the instance
*/
public static StringLiteral of(String value) {
return new StringLiteral(value);
}
@Override
public boolean evaluateUrlMatch(String url) {
return Objects.equals(value, url);
}
@Override
public File resolveFile(Path root, MappingMatch match, String url) {
File f = new File(value);
if (f.isAbsolute()) {
return f;
} else {
return root.resolve(f.toPath()).toFile();
}
}
/**
* Type adapter used in serialization.
* @see GsonBuilder#registerTypeAdapter(Type, Object)
*/
public static class StringLiteralTypeAdapter extends TypeAdapter {
@Override
public void write(JsonWriter out, StringLiteral value) throws IOException {
out.value(value.value);
}
@Override
public StringLiteral read(JsonReader in) throws IOException {
String value = in.nextString();
return new StringLiteral(value);
}
}
/**
* Returns the value held by this instance.
* @param dictionary a variable dictionary (unused)
* @return this instance's value, always
*/
@Override
public String interpolate(VariableDictionary dictionary) {
return value;
}
@Override
public boolean isMatchingHeaderName(String headerName) {
return value.equalsIgnoreCase(headerName);
}
@Nullable
@Override
public String transformHeaderName(String headerName, Pattern nameMatchRegex) {
return nameMatchRegex.matcher(headerName).replaceAll(value);
}
/**
* Performs case-sensitive match on the header value.
* @param headerName the header name
* @param headerValue the header value
* @return true iff header value is case sensitive match for this instance's value
*/
@Override
public boolean isMatchingHeaderValue(String headerName, String headerValue) {
return value.equals(headerValue);
}
@Nullable
@Override
public String transformHeaderValue(String headerName, Pattern valueMatchRegex, String headerValue) {
Matcher m = valueMatchRegex.matcher(headerValue);
return m.replaceAll(value);
}
@Override
public Pattern asRegex() {
return regexSupplier.get();
}
@Override
public String toString() {
return "StringLiteral{" +
"value='" + value + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StringLiteral that = (StringLiteral) o;
return Objects.equals(value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
/**
* Class that represents a variable object. These are used in {@link Replacement}s.
* See https://github.com/Stuk/server-replay. In a pure-Java implementation
*/
public static final class VariableHolder implements ReplacementMatch, ReplacementReplace {
/**
* Field name that signals to {@link CommonDeserializer} that a given JSON object
* should be deserialized as an instance of this class.
*/
static final String SIGNAL_FIELD_NAME = "var";
/**
* Variable name.
*/
public final String var;
@SuppressWarnings("unused") // for deserialization
private VariableHolder() {
var = null;
}
private VariableHolder(String var) {
this.var = requireNonNull(var);
}
/**
* Returns an instance representing a variable.
* @param var the variable name
* @return the instance
*/
public static VariableHolder of(String var) {
return new VariableHolder(var);
}
/**
* Substitutes
* @param dictionary the variable lookup dictionary
* @return the substitution text, or empty string if variable not found
*/
@Override
public String interpolate(VariableDictionary dictionary) {
@Nullable
Optional substitution = dictionary.substitute(var);
if (substitution == null) {
return "";
}
return substitution.orElse("");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VariableHolder that = (VariableHolder) o;
return Objects.equals(var, that.var);
}
@Override
public int hashCode() {
return Objects.hash(var);
}
}
/**
* Class that represents a regex object. Instances of this class are used to define {@link Mapping}
* or {@link Replacement} matchers.
*/
public static final class RegexHolder implements MappingMatch, ReplacementMatch,
ResponseHeaderTransformNameMatch, ResponseHeaderTransformValueMatch {
/**
* Field name that signals to {@link CommonDeserializer} that a given JSON object
* should be deserialized as an instance of this class.
*/
static final String SIGNAL_FIELD_NAME = "regex";
/**
* Regex in syntax corresponding to destination engine. If using the node engine,
* then JavaScript syntax is required. If using the VHS engine, then Java syntax
* is required.
*/
public final String regex;
private transient final Supplier caseSensitivePatternSupplier;
private transient final Supplier caseInsensitivePatternSupplier;
@SuppressWarnings("unused") // for deserialization
private RegexHolder() {
this("");
}
private RegexHolder(String regex_) {
this.regex = requireNonNull(regex_);
this.caseSensitivePatternSupplier = Suppliers.memoize(() -> Pattern.compile(regex));
this.caseInsensitivePatternSupplier = Suppliers.memoize(() -> Pattern.compile(regex, Pattern.CASE_INSENSITIVE));
}
/**
* Returns an instance representing a regular expression.
* @param regex the regular expression
* @return the instance
*/
public static RegexHolder of(String regex) {
return new RegexHolder(regex);
}
@Override
public boolean evaluateUrlMatch(String url) {
return url != null && url.matches(regex);
}
@Override
public boolean isMatchingHeaderName(String headerName) {
Matcher matcher = caseInsensitivePatternSupplier.get().matcher(headerName);
return matcher.find();
}
@Override
public boolean isMatchingHeaderValue(String headerName, String headerValue) {
Matcher matcher = caseSensitivePatternSupplier.get().matcher(headerValue);
boolean found = matcher.find();
return found;
}
@Override
public String toString() {
return "RegexHolder{" +
"regex='" + regex + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RegexHolder that = (RegexHolder) o;
return Objects.equals(regex, that.regex);
}
@Override
public int hashCode() {
return Objects.hash(regex);
}
@Override
public Pattern asRegex() {
return caseSensitivePatternSupplier.get();
}
}
/**
* Replacement strategy for textual response content. {@link #match Match} field is a string,
* Javascript regex or variable, and {@link #replace} field is a string or variable.
* The {@code replace} field can contain $n references to substitute capture groups from match.
*/
public static final class Replacement {
/**
* Matching strategy for this replacement.
*/
public final ReplacementMatch match;
/**
* Text substitution strategy for this replacement.
*/
public final ReplacementReplace replace;
@SuppressWarnings("unused") // for deserialization
private Replacement() {
match = null;
replace = null;
}
/**
* Constructs a new replacement instance.
* @param match the matching strategy
* @param replace the text replacement strategy
*/
public Replacement(ReplacementMatch match, ReplacementReplace replace) {
this.match = match;
this.replace = replace;
}
/**
* Constructs a replacement that swaps exact instances of a string literal with another string.
* @param match the string to replace
* @param replace the value to put in the replaced string's place
* @return a new replacement
*/
@SuppressWarnings("SameParameterValue")
public static Replacement literal(String match, String replace) {
return new Replacement(new StringLiteral(match), new StringLiteral(replace));
}
/**
* Constructs a replacement that swaps substrings that match a regex with a replacement string.
* @param matchRegex Javascript regex to match
* @param replace replacement value; can use $n groups
* @return a new replacement instance
*/
public static Replacement regexToString(String matchRegex, String replace) {
return new Replacement(new RegexHolder(matchRegex), new StringLiteral(replace));
}
/**
* Constructs a replacement that swaps substrings that match the value of a variable
* with the value of another variable.
* @param matchVariable variable to match, e.g. {@code entry.request.parsedUrl.query.callback}
* @param replaceVariable variable representing the replacement value, e.g. {@code request.parsedUrl.query.callback}
* @return a new replacement instance
*/
public static Replacement varToVar(String matchVariable, String replaceVariable) {
return new Replacement(new VariableHolder(matchVariable), new VariableHolder(replaceVariable));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Replacement that = (Replacement) o;
return Objects.equals(match, that.match) &&
Objects.equals(replace, that.replace);
}
@Override
public int hashCode() {
return Objects.hash(match, replace);
}
}
/**
* Class that represents a response header transform.
*/
@SuppressWarnings("unused")
public static final class ResponseHeaderTransform {
private final ResponseHeaderTransformNameMatch nameMatch;
private final ResponseHeaderTransformNameImage nameImage;
private final ResponseHeaderTransformValueMatch valueMatch;
private final ResponseHeaderTransformValueImage valueImage;
private ResponseHeaderTransform(ResponseHeaderTransformNameMatch nameMatch, ResponseHeaderTransformValueMatch valueMatch, ResponseHeaderTransformNameImage nameImage, ResponseHeaderTransformValueImage valueImage) {
this.nameMatch = nameMatch;
this.nameImage = nameImage;
this.valueMatch = valueMatch;
this.valueImage = valueImage;
}
private static ResponseHeaderTransformNameMatch orAlwaysMatch(@Nullable ResponseHeaderTransformNameMatch nameMatch) {
//noinspection ConstantConditions
return MoreObjects.firstNonNull(nameMatch, ResponseHeaderTransformNameMatch.always());
}
private static ResponseHeaderTransformValueMatch orAlwaysMatch(@Nullable ResponseHeaderTransformValueMatch valueMatch) {
//noinspection ConstantConditions
return MoreObjects.firstNonNull(valueMatch, ResponseHeaderTransformValueMatch.always());
}
private static ResponseHeaderTransformNameImage orIdentity(@Nullable ResponseHeaderTransformNameImage nameImage) {
//noinspection ConstantConditions
return MoreObjects.firstNonNull(nameImage, ResponseHeaderTransformNameImage.identity());
}
private static ResponseHeaderTransformValueImage orIdentity(@Nullable ResponseHeaderTransformValueImage valueImage) {
//noinspection ConstantConditions
return MoreObjects.firstNonNull(valueImage, ResponseHeaderTransformValueImage.identity());
}
/**
* Gets the header name matching strategy for this instance.
* @return the name matching strategy
*/
public ResponseHeaderTransformNameMatch getNameMatch() {
return orAlwaysMatch(nameMatch);
}
/**
* Gets the header name transform strategy for this instance.
* @return the name transform strategy
*/
public ResponseHeaderTransformNameImage getNameImage() {
return orIdentity(nameImage);
}
/**
* Gets the header value matching strategy for this instance.
* @return the value matching strategy
*/
public ResponseHeaderTransformValueMatch getValueMatch() {
return orAlwaysMatch(valueMatch);
}
/**
* Gets the header value transform strategy for this instance.
* @return the value transform strategy
*/
public ResponseHeaderTransformValueImage getValueImage() {
return orIdentity(valueImage);
}
/**
* Creates a transform that matches based on name and transforms only the header name.
* @param nameMatch the name matching strategy
* @param nameImage the name transform strategy
* @return the transform
*/
public static ResponseHeaderTransform name(ResponseHeaderTransformNameMatch nameMatch,
ResponseHeaderTransformNameImage nameImage) {
return new ResponseHeaderTransform(requireNonNull(nameMatch), null, requireNonNull(nameImage), null);
}
/**
* Creates a transform that matches based on header value and transforms only the header value.
* @param valueMatch the value matching strategy
* @param valueImage the value transform strategy
* @return the transform
*/
public static ResponseHeaderTransform value(ResponseHeaderTransformValueMatch valueMatch,
ResponseHeaderTransformValueImage valueImage) {
return new ResponseHeaderTransform(null, requireNonNull(valueMatch), null, requireNonNull(valueImage));
}
/**
* Creates a transform that matches based on header name and transforms only the header value.
* @param nameMatch the name matching strategy
* @param valueImage the value transform strategy
* @return the transform
*/
public static ResponseHeaderTransform valueByName(ResponseHeaderTransformNameMatch nameMatch,
ResponseHeaderTransformValueImage valueImage) {
return new ResponseHeaderTransform(requireNonNull(nameMatch), null, null, requireNonNull(valueImage));
}
/**
* Creates a transform that matches based on header value and transforms the header name.
* @param valueMatch the value matching strategy
* @param nameImage the name transform strategy
* @return the transform
*/
public static ResponseHeaderTransform nameByValue(ResponseHeaderTransformValueMatch valueMatch,
ResponseHeaderTransformNameImage nameImage) {
return new ResponseHeaderTransform(null, requireNonNull(valueMatch), requireNonNull(nameImage), null);
}
/**
* Creates a transform that matches based on header name and header value and transforms only the
* header value.
* @param nameMatch name matching strategy
* @param valueMatch value matching strategy
* @param valueImage
* @return
*/
public static ResponseHeaderTransform valueByNameAndValue(ResponseHeaderTransformNameMatch nameMatch,
ResponseHeaderTransformValueMatch valueMatch,
ResponseHeaderTransformValueImage valueImage) {
return new ResponseHeaderTransform(requireNonNull(nameMatch), requireNonNull(valueMatch), null, requireNonNull(valueImage));
}
/**
* Creates a transform that matches based on header name and header value and transforms only
* the header name.
* @param nameMatch the name matching strategy
* @param valueMatch the value matching strategy
* @param nameImage the name transform strategy
* @return the transform
*/
public static ResponseHeaderTransform nameByNameAndValue(ResponseHeaderTransformNameMatch nameMatch,
ResponseHeaderTransformValueMatch valueMatch,
ResponseHeaderTransformNameImage nameImage) {
return new ResponseHeaderTransform(requireNonNull(nameMatch), requireNonNull(valueMatch), requireNonNull(nameImage), null);
}
/**
* Creates a transform that matches based on header name and header value and transforms
* both the header name and header value.
* @param nameMatch the name matching strategy
* @param valueMatch the value matching strategy
* @param nameImage the name transform strategy
* @param valueImage the value transform strategy
* @return the transform
*/
public static ResponseHeaderTransform everything(ResponseHeaderTransformNameMatch nameMatch,
ResponseHeaderTransformValueMatch valueMatch,
ResponseHeaderTransformNameImage nameImage,
ResponseHeaderTransformValueImage valueImage) {
return new ResponseHeaderTransform(requireNonNull(nameMatch), requireNonNull(valueMatch), requireNonNull(nameImage), requireNonNull(valueImage));
}
/**
* Produces a transform that removes a header, selecting based on the header name.
* Warning: this is not compatible with the Node engine.
* @param nameMatch the name matching strategy
* @return the header transform
*/
public static ResponseHeaderTransform removeByName(ResponseHeaderTransformNameMatch nameMatch) {
return new ResponseHeaderTransform(nameMatch, null, RemoveHeader.getInstance(), null);
}
/**
* Produces a transform that removes a header, selecting based on the header name and header value.
*
Warning: this is not compatible with the Node engine.
* @param nameMatch the name matching strategy
* @param valueMatch the value matching strategy
* @return the header transform
*/
public static ResponseHeaderTransform removeByNameAndValue(ResponseHeaderTransformNameMatch nameMatch,
ResponseHeaderTransformValueMatch valueMatch) {
return new ResponseHeaderTransform(nameMatch, valueMatch, null, RemoveHeader.getInstance());
}
/**
* Produces a transform that removes a header, selecting based on header value.
*
Warning: this is not compatible with the Node engine.
* @param valueMatch the value matching strategy
* @return the header transform
*/
public static ResponseHeaderTransform removeByValue(ResponseHeaderTransformValueMatch valueMatch) {
return new ResponseHeaderTransform(null, valueMatch, null, RemoveHeader.getInstance());
}
@Override
public String toString() {
return "ResponseHeaderTransform{" +
"nameMatch=" + nameMatch +
", nameImage=" + nameImage +
", valueMatch=" + valueMatch +
", valueImage=" + valueImage +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ResponseHeaderTransform that = (ResponseHeaderTransform) o;
return Objects.equals(nameMatch, that.nameMatch) &&
Objects.equals(nameImage, that.nameImage) &&
Objects.equals(valueMatch, that.valueMatch) &&
Objects.equals(valueImage, that.valueImage);
}
@Override
public int hashCode() {
return Objects.hash(nameMatch, nameImage, valueMatch, valueImage);
}
}
/**
* Builder of {@code ReplayServerConfig} instances.
*/
public static final class Builder {
private int version = 1;
private final List mappings = new ArrayList<>();
private final List replacements = new ArrayList<>();
private final List responseHeaderTransforms = new ArrayList<>();
private Builder() {
}
/**
* Adds a mapping.
* @param mapping the mapping
* @return this builder instance
*/
public Builder map(Mapping mapping) {
mappings.add(requireNonNull(mapping));
return this;
}
/**
* Adds a replacement.
* @param val the replacement
* @return this builder instance
*/
public Builder replace(Replacement val) {
replacements.add(requireNonNull(val));
return this;
}
/**
* Adds a response header transform.
* @param responseHeaderTransform the transform
* @return this builder instance
*/
public Builder transformResponse(ResponseHeaderTransform responseHeaderTransform) {
responseHeaderTransforms.add(requireNonNull(responseHeaderTransform));
return this;
}
/**
* Builds the config instance.
* @return the immutable config instance
*/
public ReplayServerConfig build() {
return new ReplayServerConfig(version, mappings, replacements, responseHeaderTransforms);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ReplayServerConfig that = (ReplayServerConfig) o;
return version == that.version &&
Objects.equals(mappings, that.mappings) &&
Objects.equals(replacements, that.replacements) &&
Objects.equals(responseHeaderTransforms, that.responseHeaderTransforms);
}
@Override
public int hashCode() {
return Objects.hash(version, mappings, replacements, responseHeaderTransforms);
}
/**
* Deserializer for concrete classes that implement the set of interfaces
* contained in {@link #INTERFACES_HANDLED_BY_COMMON_DESERIALIZER}. Currently deserializes
* JSON to instances of {@link StringLiteral}, {@link RegexHolder}, {@link RemoveHeader},
* and {@link VariableHolder}.
*/
@VisibleForTesting
static class CommonDeserializer implements JsonDeserializer