package restx.specs;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.io.CharSource;
import com.google.common.io.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
import restx.factory.Component;
import restx.http.HttpStatus;
import restx.common.MoreResources;
import restx.factory.Factory;
import restx.factory.NamedComponent;
import javax.inject.Inject;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static restx.common.MorePreconditions.checkInstanceOf;
/**
* User: xavierhanin
* Date: 3/30/13
* Time: 6:19 PM
*/
@Component
public class RestxSpecLoader {
private static final Logger logger = LoggerFactory.getLogger(RestxSpecLoader.class);
private final Set<NamedComponent<GivenLoader>> givenLoaders;
private final Set<NamedComponent<WhenHeaderLoader>> whenHeaderLoaders;
private final String names;
@Inject
public RestxSpecLoader(Factory factory) {
this(factory.queryByClass(GivenLoader.class).find(), factory.queryByClass(WhenHeaderLoader.class).find());
}
public RestxSpecLoader(Set<NamedComponent<GivenLoader>> givenLoaders,
Set<NamedComponent<WhenHeaderLoader>> whenHeaderLoaders) {
this.givenLoaders = givenLoaders;
this.whenHeaderLoaders = whenHeaderLoaders;
List<String> names = Lists.newArrayList();
for (NamedComponent<GivenLoader> givenLoader : givenLoaders) {
names.add(givenLoader.getName().getName());
}
this.names = Joiner.on(", ").join(names);
}
public RestxSpec load(String resource) throws IOException {
return load(resource, Resources.asCharSource(
MoreResources.getResource(resource, true),
Charsets.UTF_8));
}
public RestxSpec load(String path, CharSource charSource) throws IOException {
Yaml yaml = new Yaml();
Map spec = (Map) yaml.load(charSource.read());
List<Given> givens = loadGivens(spec);
List<WhenHttpRequest> whens = Lists.newArrayList();
Iterable wts = checkInstanceOf("wts", spec.get("wts"), Iterable.class);
for (Object wt : wts) {
Map whenThen = checkInstanceOf("when/then", wt, Map.class);
Object w = whenThen.get("when");
if (w instanceof String) {
WhenHttpRequest.Builder whenHttpBuilder = WhenHttpRequest.builder();
String ws = (String) w;
String definition;
String body;
int nlIndex = ws.indexOf("\n");
if (nlIndex != -1) {
definition = ws.substring(0, nlIndex);
body = ws.substring(nlIndex + 1).trim();
Optional<WhenHeaderLoader> whenHeader = resolveFromBody(body);
while (whenHeader.isPresent()) {
nlIndex = body.indexOf("\n");
String headerValue;
if (nlIndex == -1) {
headerValue = body.substring(whenHeader.get().detectionPattern().length(), body.length());
body = "";
} else {
headerValue = body.substring(whenHeader.get().detectionPattern().length(), nlIndex);
body = body.substring(nlIndex + 1).trim();
}
whenHeader.get().loadHeader(headerValue, whenHttpBuilder);
whenHeader = resolveFromBody(body);
}
} else {
definition = ws;
body = "";
}
Matcher methodAndPathMatcher = Pattern.compile("(GET|POST|PUT|DELETE|HEAD|OPTIONS) (.+)").matcher(definition);
if (methodAndPathMatcher.matches()) {
String then = checkInstanceOf("then", whenThen.get("then"), String.class).trim();
HttpStatus code = HttpStatus.OK;
int endLineIndex = then.indexOf("\n");
if (endLineIndex == -1) {
endLineIndex = then.length();
}
String firstLine = then.substring(0, endLineIndex);
Matcher respMatcher = Pattern.compile("^(\\d{3}).*$").matcher(firstLine);
if (respMatcher.matches()) {
code = HttpStatus.havingCode(Integer.parseInt(respMatcher.group(1)));
then = then.substring(endLineIndex).trim();
}
whens.add(whenHttpBuilder
.withMethod(methodAndPathMatcher.group(1))
.withPath(methodAndPathMatcher.group(2))
.withBody(body)
.withThen(new ThenHttpResponse(code.getCode(), then))
.build());
} else {
throw new IllegalArgumentException("unrecognized 'when' format: it must begin with " +
"a HTTP declaration of the form 'VERB resource/path'\nEg: GET users/johndoe\n. Was: '" + ws + "'\n");
}
}
}
return new RestxSpec(path, checkInstanceOf("title", spec.get("title"), String.class),
ImmutableList.copyOf(givens),
ImmutableList.copyOf(whens));
}
private Optional<WhenHeaderLoader> resolveFromBody(String body) {
for(NamedComponent<WhenHeaderLoader> whenHeaderLoader : whenHeaderLoaders){
if(body.startsWith(whenHeaderLoader.getComponent().detectionPattern())){
return Optional.of(whenHeaderLoader.getComponent());
}
}
return Optional.absent();
}
@SuppressWarnings("unchecked")
private List<Given> loadGivens(Map testCase) throws IOException {
List<Given> givens = Lists.newArrayList();
Iterable given = checkInstanceOf("given", testCase.get("given"), Iterable.class);
for (Object g : given) {
Map<String, ?> given1 = (Map<String, ?>) checkInstanceOf("given", g, Map.class);
if (given1.isEmpty()) {
throw new IllegalArgumentException(
String.format("can't load %s: a given has no properties at all", testCase));
}
String firstKey = checkInstanceOf("key", given1.keySet().iterator().next(), String.class);
givens.add(findLoader(given1, firstKey).load(given1));
}
return givens;
}
private GivenLoader findLoader(Map given, String type) {
for (NamedComponent<GivenLoader> givenLoader : givenLoaders) {
if (givenLoader.getName().getName().equalsIgnoreCase(type)) {
return givenLoader.getComponent();
}
}
throw new IllegalArgumentException("invalid given " + given + ": unrecognized type " + type + "." +
" Was expecting one of [" + names + "] as either first field or 'type' property");
}
public static interface GivenLoader {
Given load(Map<String, ?> m);
}
public static interface WhenHeaderLoader {
String detectionPattern();
void loadHeader(String headerValue, WhenHttpRequest.Builder whenHttpRequestBuilder);
}
}