package io.github.azagniotov.stubby4j.stubs;
import io.github.azagniotov.stubby4j.annotations.CoberturaIgnore;
import io.github.azagniotov.stubby4j.annotations.VisibleForTesting;
import io.github.azagniotov.stubby4j.common.Common;
import io.github.azagniotov.stubby4j.utils.CollectionUtils;
import io.github.azagniotov.stubby4j.utils.FileUtils;
import io.github.azagniotov.stubby4j.utils.ObjectUtils;
import io.github.azagniotov.stubby4j.utils.StringUtils;
import org.eclipse.jetty.http.HttpMethod;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import static io.github.azagniotov.generics.TypeSafeConverter.asCheckedArrayList;
import static io.github.azagniotov.generics.TypeSafeConverter.asCheckedLinkedHashMap;
import static io.github.azagniotov.stubby4j.stubs.StubbableAuthorizationType.BASIC;
import static io.github.azagniotov.stubby4j.stubs.StubbableAuthorizationType.BEARER;
import static io.github.azagniotov.stubby4j.stubs.StubbableAuthorizationType.CUSTOM;
import static io.github.azagniotov.stubby4j.utils.ObjectUtils.isNotNull;
import static io.github.azagniotov.stubby4j.utils.StringUtils.isSet;
import static io.github.azagniotov.stubby4j.utils.StringUtils.newStringUtf8;
import static io.github.azagniotov.stubby4j.utils.StringUtils.toLower;
import static io.github.azagniotov.stubby4j.yaml.ConfigurableYAMLProperty.FILE;
import static io.github.azagniotov.stubby4j.yaml.ConfigurableYAMLProperty.HEADERS;
import static io.github.azagniotov.stubby4j.yaml.ConfigurableYAMLProperty.METHOD;
import static io.github.azagniotov.stubby4j.yaml.ConfigurableYAMLProperty.POST;
import static io.github.azagniotov.stubby4j.yaml.ConfigurableYAMLProperty.QUERY;
import static io.github.azagniotov.stubby4j.yaml.ConfigurableYAMLProperty.URL;
import static java.lang.String.valueOf;
import static java.util.stream.Collectors.toCollection;
public class StubRequest implements ReflectableStub {
static final String HTTP_HEADER_AUTHORIZATION = "authorization";
private final String url;
private final String post;
private final File file;
private final byte[] fileBytes;
private final List<String> method;
private final Map<String, String> headers;
private final Map<String, String> query;
private final Map<String, String> regexGroups;
private StubRequest(final String url,
final String post,
final File file,
final List<String> method,
final Map<String, String> headers,
final Map<String, String> query) {
this.url = url;
this.post = post;
this.file = file;
this.fileBytes = ObjectUtils.isNull(file) ? new byte[]{} : getFileBytes();
this.method = method;
this.headers = headers;
this.query = query;
this.regexGroups = new TreeMap<>();
}
public final ArrayList<String> getMethod() {
return method.stream().map(StringUtils::toUpper).collect(toCollection(ArrayList::new));
}
public String getUri() {
return url;
}
/**
* Returns url and query string parameters (if present), separated by a question mark.
*
* @return {@link String url}
*/
public String getUrl() {
if (getQuery().isEmpty()) {
return url;
}
final String queryString = CollectionUtils.constructQueryString(query);
return String.format("%s?%s", url, queryString);
}
private byte[] getFileBytes() {
try {
return FileUtils.fileToBytes(file);
} catch (Exception e) {
return new byte[]{};
}
}
public String getPostBody() {
if (fileBytes.length == 0) {
return FileUtils.enforceSystemLineSeparator(post);
}
final String utf8FileContent = newStringUtf8(fileBytes);
return FileUtils.enforceSystemLineSeparator(utf8FileContent);
}
//Used by reflection when populating stubby admin page with stubbed information
public String getPost() {
return post;
}
public final Map<String, String> getHeaders() {
final Map<String, String> headersCopy = new LinkedHashMap<>(headers);
final Set<Map.Entry<String, String>> entrySet = headersCopy.entrySet();
this.headers.clear();
for (final Map.Entry<String, String> entry : entrySet) {
this.headers.put(toLower(entry.getKey()), entry.getValue());
}
return headers;
}
public Map<String, String> getQuery() {
return query;
}
public byte[] getFile() {
return fileBytes;
}
// Just a shallow copy that protects collection from modification, the points themselves are not copied
public Map<String, String> getRegexGroups() {
return new TreeMap<>(regexGroups);
}
public File getRawFile() {
return file;
}
public boolean hasHeaders() {
return !getHeaders().isEmpty();
}
public boolean hasQuery() {
return !getQuery().isEmpty();
}
public boolean hasPostBody() {
return isSet(getPostBody());
}
boolean isSecured() {
return getHeaders().containsKey(BASIC.asYAMLProp()) ||
getHeaders().containsKey(BEARER.asYAMLProp()) ||
getHeaders().containsKey(CUSTOM.asYAMLProp());
}
@VisibleForTesting
StubbableAuthorizationType getStubbedAuthorizationType() {
if (getHeaders().containsKey(BASIC.asYAMLProp())) {
return BASIC;
} else if (getHeaders().containsKey(BEARER.asYAMLProp())) {
return BEARER;
} else {
return CUSTOM;
}
}
String getStubbedHeaderAuthorization(final StubbableAuthorizationType stubbableAuthorizationType) {
return getHeaders().get(stubbableAuthorizationType.asYAMLProp());
}
public String getRawHeaderAuthorization() {
return getHeaders().get(HTTP_HEADER_AUTHORIZATION);
}
@VisibleForTesting
boolean isPostStubbed() {
return isSet(this.getPostBody()) && (getMethod().contains("POST") || getMethod().contains("PUT"));
}
public void compileRegexPatternsAndCache() {
if (isSet(this.url)) {
RegexParser.INSTANCE.compilePatternAndCache(this.url);
}
if (isPostStubbed()) {
RegexParser.INSTANCE.compilePatternAndCache(getPostBody());
}
this.getQuery().values().forEach(RegexParser.INSTANCE::compilePatternAndCache);
this.getHeaders().values().forEach(RegexParser.INSTANCE::compilePatternAndCache);
}
@Override
public boolean equals(final Object that) {
// The 'this' is actually the incoming asserting StubRequest, the 'that' is the stubbed one
if (this == that) {
return true;
} else if (that instanceof StubRequest) {
final StubRequest stubbedRequest = (StubRequest) that;
if (new StubMatcher(regexGroups).matches(stubbedRequest, this)) {
return true;
}
}
return false;
}
@Override
@CoberturaIgnore
public int hashCode() {
int result = (isNotNull(url) ? url.hashCode() : 0);
result = 31 * result + method.hashCode();
result = 31 * result + (isNotNull(post) ? post.hashCode() : 0);
result = 31 * result + (isNotNull(fileBytes) && fileBytes.length != 0 ? Arrays.hashCode(fileBytes) : 0);
result = 31 * result + headers.hashCode();
result = 31 * result + query.hashCode();
return result;
}
@Override
@CoberturaIgnore
public final String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("StubRequest");
sb.append("{url=").append(url);
sb.append(", method=").append(method);
if (!ObjectUtils.isNull(post)) {
sb.append(", post=").append(post);
}
sb.append(", query=").append(query);
sb.append(", headers=").append(getHeaders());
sb.append('}');
return sb.toString();
}
public static final class Builder extends AbstractBuilder<StubRequest> {
private String url;
private List<String> method;
private String post;
private File file;
private Map<String, String> headers;
private Map<String, String> query;
public Builder() {
super();
this.url = null;
this.method = new ArrayList<>();
this.post = null;
this.file = null;
this.headers = new LinkedHashMap<>();
this.query = new LinkedHashMap<>();
}
public Builder withMethod(final String value) {
if (isSet(value)) {
method.add(value);
}
return this;
}
public Builder withMethodGet() {
this.method.add(HttpMethod.GET.asString());
return this;
}
public Builder withMethodPut() {
this.method.add(HttpMethod.PUT.asString());
return this;
}
public Builder withMethodPost() {
this.method.add(HttpMethod.POST.asString());
return this;
}
public Builder withMethodHead() {
this.method.add(HttpMethod.HEAD.asString());
return this;
}
public Builder withUrl(final String value) {
this.url = value;
return this;
}
public Builder withHeader(final String key, final String value) {
this.headers.put(valueOf(key), valueOf(value));
return this;
}
public Builder withHeaderContentType(final String value) {
this.headers.put("content-type", value);
return this;
}
public Builder withApplicationJsonContentType() {
this.headers.put("content-type", Common.HEADER_APPLICATION_JSON);
return this;
}
public Builder withApplicationXmlContentType() {
this.headers.put("content-type", Common.HEADER_APPLICATION_XML);
return this;
}
public Builder withHeaderContentLength(final String value) {
this.headers.put("content-length", value);
return this;
}
public Builder withHeaderContentLanguage(final String value) {
this.headers.put("content-language", value);
return this;
}
public Builder withHeaderContentEncoding(final String value) {
this.headers.put("content-encoding", value);
return this;
}
public Builder withHeaderPragma(final String value) {
this.headers.put("pragma", value);
return this;
}
public Builder withYAMLHeaderAuthorizationBasic(final String value) {
this.headers.put(StubbableAuthorizationType.BASIC.asYAMLProp(), value);
return this;
}
public Builder withYAMLHeaderAuthorizationBearer(final String value) {
this.headers.put(StubbableAuthorizationType.BEARER.asYAMLProp(), value);
return this;
}
public Builder withPost(final String post) {
this.post = post;
return this;
}
public Builder withFile(final File file) {
this.file = file;
return this;
}
public Builder withQuery(final String key, final String value) {
this.query.put(key, value);
return this;
}
public Builder withQuery(final Map<String, String> query) {
this.query.putAll(query);
return this;
}
@Override
public StubRequest build() {
this.url = getStaged(String.class, URL, url);
this.post = getStaged(String.class, POST, post);
this.file = getStaged(File.class, FILE, file);
this.method = asCheckedArrayList(getStaged(List.class, METHOD, method), String.class);
this.headers = asCheckedLinkedHashMap(getStaged(Map.class, HEADERS, headers), String.class, String.class);
this.query = asCheckedLinkedHashMap(getStaged(Map.class, QUERY, query), String.class, String.class);
final StubRequest stubRequest = new StubRequest(url, post, file, method, headers, query);
this.url = null;
this.method = new ArrayList<>();
this.post = null;
this.file = null;
this.headers = new LinkedHashMap<>();
this.query = new LinkedHashMap<>();
this.fieldNameAndValues.clear();
return stubRequest;
}
}
}