Skip to content

Commit

Permalink
Issue #27. Validate path params with regex
Browse files Browse the repository at this point in the history
If @PathParam defines a regex, the latter will be used to
validate the given argument at substitution time.
  • Loading branch information
mgazanayi authored and fbiville committed Mar 3, 2015
1 parent 8d71c72 commit b2edef1
Show file tree
Hide file tree
Showing 15 changed files with 303 additions and 31 deletions.
Empty file added git
Empty file.
@@ -0,0 +1,27 @@
package fr.vidal.oss.jax_rs_linker.it;

import fr.vidal.oss.jax_rs_linker.Linkers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class BrandResourceLinkerTest {

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void no_exception_is_thrown_when_argument_matches_regex() {
Linkers.brandResourceLinker()
.relatedBrandResourceByZipCode()
.replace(BrandResourcePathParameters.CODE, "aBc");
}

@Test
public void illegal_argument_exception_is_thrown_when_regex_is_not_matched() {
thrown.expect(IllegalArgumentException.class);
Linkers.brandResourceLinker()
.relatedBrandResourceByZipCode()
.replace(BrandResourcePathParameters.CODE, "aBD2");
}
}
5 changes: 5 additions & 0 deletions jax-rs-linker/pom.xml
Expand Up @@ -84,6 +84,11 @@
<relocation>
<pattern>com.google</pattern>
<shadedPattern>hidden.com.google</shadedPattern>
<excludes>
<exclude>com.google.common.base.Optional</exclude>
<exclude>com.google.common.base.Present</exclude>
<exclude>com.google.common.base.Absent</exclude>
</excludes>
</relocation>
</relocations>
<minimizeJar>true</minimizeJar>
Expand Down
@@ -1,10 +1,19 @@
package fr.vidal.oss.jax_rs_linker.api;

import com.google.common.base.Optional;

import java.util.regex.Pattern;

public enum NoPathParameters implements PathParameters {
;

@Override
public String placeholder() {
throw new UnsupportedOperationException();
}

@Override
public Optional<Pattern> regex() {
throw new UnsupportedOperationException();
}
}
@@ -1,5 +1,10 @@
package fr.vidal.oss.jax_rs_linker.api;

import com.google.common.base.Optional;

import java.util.regex.Pattern;

public interface PathParameters {
String placeholder();
Optional<Pattern> regex();
}
Expand Up @@ -6,6 +6,7 @@

import java.util.Collection;

import static fr.vidal.oss.jax_rs_linker.parser.ApiPaths.decorate;
import static fr.vidal.oss.jax_rs_linker.parser.ApiPaths.sanitize;

public class ApiPath {
Expand All @@ -15,7 +16,7 @@ public class ApiPath {

public ApiPath(String path, Collection<PathParameter> pathParameters) {
this.path = sanitize(path);
this.pathParameters = pathParameters;
this.pathParameters = decorate(pathParameters, path);
}

public String getPath() {
Expand Down
@@ -1,19 +1,38 @@
package fr.vidal.oss.jax_rs_linker.model;

import com.google.common.base.Objects;
import com.google.common.base.*;

import java.util.regex.Pattern;

import static com.google.common.base.Optional.absent;
import static com.google.common.base.Optional.fromNullable;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;

public class PathParameter {

private final ClassName type;
private final String name;
private final Optional<Pattern> regex;

public PathParameter(ClassName type, String name) {
this(type, name, Optional.<Pattern>absent());
}

public PathParameter(ClassName type, String name, String regex) {
this.type = type;
this.name = name;
this.regex = Optional.of(Pattern.compile(regex));
}

private PathParameter(ClassName type, String name, Optional<Pattern> regex) {
this.type = type;
this.name = name;
this.regex = regex;
}



public ClassName getType() {
return type;
}
Expand All @@ -22,6 +41,11 @@ public String getName() {
return name;
}

public Optional<Pattern> getRegex() {
return regex;
}


@Override
public int hashCode() {
return Objects.hashCode(type, name);
Expand Down
@@ -1,6 +1,7 @@
package fr.vidal.oss.jax_rs_linker.model;

import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
Expand All @@ -12,6 +13,7 @@
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.not;
Expand Down Expand Up @@ -41,6 +43,7 @@ private TemplatedUrl(String path, Collection<PathParameter> pathParameters, Map<

public TemplatedUrl<T,U> replace(T parameter, String value) {
checkState(!pathParameters.isEmpty(), "No more path parameters to replace");
validateParamValue(parameter.regex(), value);

return new TemplatedUrl<>(
path.replace(placeholder(parameter.placeholder()), value),
Expand All @@ -61,6 +64,14 @@ public String value() {
return path + TO_QUERY_STRING.apply(queryParameters);
}

private void validateParamValue(Optional<Pattern> regex, String value) {
if (regex.isPresent()) {
if (!regex.get().matcher(value).matches()) {
throw new IllegalArgumentException(String.format("The given value doesn't match the parameter regex: %s", regex.get()));
}
}
}

private String parameterNames() {
return FluentIterable.from(pathParameters)
.transform(PathParameterToName.TO_NAME)
Expand Down
@@ -1,5 +1,20 @@
package fr.vidal.oss.jax_rs_linker.parser;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import fr.vidal.oss.jax_rs_linker.model.PathParameter;

import javax.annotation.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import static com.google.common.base.Optional.absent;
import static com.google.common.base.Optional.of;
import static com.google.common.collect.Maps.newHashMap;

public final class ApiPaths {

public static String sanitize(String path) {
Expand All @@ -12,4 +27,41 @@ public static String sanitize(String path) {
return path.substring(0, colonPosition).trim()
+ sanitize(path.substring(closingBracePosition));
}

public static Collection<PathParameter> decorate(Collection<PathParameter> pathParameters, String path) {
final Map<String, String> parametersRegex = extractParametersRegex(path);

return FluentIterable.from(pathParameters).transform(new Function<PathParameter, PathParameter>() {
@Nullable
@Override
public PathParameter apply(PathParameter pathParameter) {
if (parametersRegex.containsKey(pathParameter.getName())) {
return new PathParameter(pathParameter.getType(),
pathParameter.getName(),
parametersRegex.get(pathParameter.getName())
);
}
return pathParameter;
}
}).toList();
}

private static Map<String, String> extractParametersRegex(String path) {
Map<String, String> parameterAndRegex = newHashMap();

StringTokenizer st = new StringTokenizer(path, "/");
while (st.hasMoreTokens()) {
String pathParam = st.nextToken();
if (pathParam.contains("{")) {
int colonPosition = pathParam.indexOf(':');
if (colonPosition != -1) {
String name = pathParam.substring(1, colonPosition).trim();
String regex = pathParam.substring(colonPosition + 1, pathParam.length() - 1).trim();
parameterAndRegex.put(name, regex);
}
}

}
return parameterAndRegex;
}
}
@@ -1,10 +1,8 @@
package fr.vidal.oss.jax_rs_linker.writer;

import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.*;
import fr.vidal.oss.jax_rs_linker.LinkerAnnotationProcessor;
import fr.vidal.oss.jax_rs_linker.api.PathParameters;
import fr.vidal.oss.jax_rs_linker.functions.MappingToPathParameters;
Expand All @@ -17,6 +15,7 @@
import java.io.IOException;
import java.util.Collection;
import java.util.Comparator;
import java.util.regex.Pattern;

import static javax.lang.model.element.Modifier.*;

Expand All @@ -40,17 +39,33 @@ public void write(ClassName generatedClass, Collection<Mapping> mappings) throws

writeEnumeration(mappings, typeBuilder);

TypeName optionalOfString =
ParameterizedTypeName.get(
com.squareup.javapoet.ClassName.get(Optional.class),
com.squareup.javapoet.ClassName.get(Pattern.class)
);

typeBuilder.addField(String.class, "placeholder", PRIVATE, FINAL)
.addMethod(MethodSpec.constructorBuilder()
.addParameter(String.class, "placeholder")
.addCode("this.$L = $L;\n", "placeholder", "placeholder")
.build())
.addMethod(MethodSpec.methodBuilder("placeholder")
.addAnnotation(Override.class)
.addModifiers(PUBLIC, FINAL)
.returns(String.class)
.addCode("return this.$L;\n", "placeholder")
.build());
.addField(optionalOfString, "regex", PRIVATE, FINAL)
.addMethod(MethodSpec.constructorBuilder()
.addParameter(String.class, "placeholder")
.addParameter(optionalOfString, "regex")
.addCode("this.$L = $L;\n", "placeholder", "placeholder")
.addCode("this.$L = $L;\n", "regex", "regex")
.build())
.addMethod(MethodSpec.methodBuilder("placeholder")
.addAnnotation(Override.class)
.addModifiers(PUBLIC, FINAL)
.returns(String.class)
.addCode("return this.$L;\n", "placeholder")
.build())
.addMethod(MethodSpec.methodBuilder("regex")
.addAnnotation(Override.class)
.addModifiers(PUBLIC, FINAL)
.returns(optionalOfString)
.addCode("return this.$L;\n", "regex")
.build());
;


JavaFile.builder(generatedClass.packageName(), typeBuilder.build())
Expand All @@ -62,10 +77,21 @@ public void write(ClassName generatedClass, Collection<Mapping> mappings) throws

private void writeEnumeration(Collection<Mapping> mappings, TypeSpec.Builder typeBuilder) throws IOException {
for (PathParameter parameter : enumConstants(mappings)) {
typeBuilder.addEnumConstant(
EnumConstants.constantName(parameter.getName()),
TypeSpec.anonymousClassBuilder("$S", parameter.getName()).build()
);
Optional<Pattern> regex = parameter.getRegex();
String name = parameter.getName();
if (regex.isPresent()) {
typeBuilder.addEnumConstant(
EnumConstants.constantName(name),
TypeSpec.anonymousClassBuilder("$S, Optional.<Pattern>of(Pattern.compile($S))", name, regex.get())
.build()
);
} else {
typeBuilder.addEnumConstant(
EnumConstants.constantName(name),
TypeSpec.anonymousClassBuilder("$S, Optional.<Pattern>absent()", name)
.build()
);
}
}
}

Expand Down
@@ -1,5 +1,6 @@
package fr.vidal.oss.jax_rs_linker.model;

import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import fr.vidal.oss.jax_rs_linker.api.NoQueryParameters;
import fr.vidal.oss.jax_rs_linker.api.PathParameters;
Expand All @@ -8,6 +9,7 @@
import org.junit.rules.ExpectedException;

import java.util.Collection;
import java.util.regex.Pattern;

import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -67,12 +69,16 @@ private ClassName className(String type) {
}
}

enum ProductParameters implements PathParameters
{
enum ProductParameters implements PathParameters {
ID;

@Override
public String placeholder() {
return "id";
}

@Override
public Optional<Pattern> regex() {
return Optional.absent();
}
}

0 comments on commit b2edef1

Please sign in to comment.