Skip to content

Commit

Permalink
Merge pull request #323 from SentryMan/dynamic-routes
Browse files Browse the repository at this point in the history
[http-client] Allow paths to be loaded from System Properties/Avaje Config
  • Loading branch information
SentryMan authored Oct 18, 2023
2 parents ef603ad + 014af6d commit 347522b
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 15 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Use source code generation to adapt annotated REST controllers `@Path, @Get, @Po

- Lightweight (65Kb library + generated source code)
- Full use of Javalin or Helidon SE/Nima as desired
- Bean Validation of request bodies supported
- Bean Validation of request bodies supported (validation groups supported as well)

## Add dependencies
```xml
Expand Down Expand Up @@ -58,7 +58,7 @@ In JDK 22+, annotation processors are disabled by default, you will need to add
</plugin>
```

## Define a Controller (These APT processors work with Java and Kotlin.)
## Define a Controller (These APT processors work with both Java and Kotlin)
```java
package org.example.hello;

Expand Down Expand Up @@ -106,7 +106,7 @@ To force the AP to generate with `@javax.inject.Singleton`(in the case where you

### Usage with Javalin

The annotation processor will generate controller classes implementing the Javalin `Plugin` interface, which means we can register them with Javalin using:
The annotation processor will generate controller classes implementing the Javalin `Plugin` interface, which we can register using:

```java
List<Plugin> routes = ...; //retrieve using a DI framework
Expand Down Expand Up @@ -268,6 +268,7 @@ public class WidgetController$Route implements HttpFeature {
var id = asInt(pathParams.first("id").get());
var result = controller.getById(id);
res.headers().contentType(MediaTypes.APPLICATION_JSON);
//jsonb has a special accommodation for helidon to improve performance
widgetController$WidgetJsonType.toJson(result, JsonOutput.of(res));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@

import static io.avaje.http.generator.core.ProcessingContext.*;
import io.avaje.http.generator.core.*;
import io.avaje.http.generator.core.PathSegments.Segment;

import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;

import static java.util.stream.Collectors.toMap;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
Expand All @@ -29,6 +35,8 @@ class ClientMethodWriter {
private String methodGenericParams = "";
private final boolean useJsonb;
private final Optional<RequestTimeoutPrism> timeout;
private final boolean useConfig;
private final Map<String, String> segmentPropertyMap;

ClientMethodWriter(MethodReader method, Append writer, boolean useJsonb) {
this.method = method;
Expand All @@ -37,6 +45,12 @@ class ClientMethodWriter {
this.returnType = Util.parseType(method.returnType());
this.useJsonb = useJsonb;
this.timeout = method.timeout();
this.useConfig = ProcessingContext.typeElement("io.avaje.config.Config") != null;

this.segmentPropertyMap =
method.pathSegments().segments().stream()
.filter(Segment::isProperty)
.collect(toMap(Segment::name, s -> Util.sanitizeName(s.name()).toUpperCase()));
}

void addImportTypes(ControllerReader reader) {
Expand All @@ -45,16 +59,29 @@ void addImportTypes(ControllerReader reader) {
.map(UType::parse)
.map(UType::importTypes)
.forEach(reader::addImportTypes);

for (final MethodParam param : method.params()) {
reader.addImportTypes(param.utype().importTypes());
}

if (useConfig && !segmentPropertyMap.isEmpty()) {
reader.addImportType("io.avaje.config.Config");
}
}

private void methodStart(Append writer) {
for (MethodParam param : method.params()) {
checkBodyHandler(param);
}
method.checkArgumentNames();

segmentPropertyMap.forEach(
(k, v) -> {
writer.append(" private static final String %s = ", v);
final String getProperty = useConfig ? "Config.get(" : "System.getProperty(";
writer.append(getProperty).append("\"%s\");", k).eol();
});

writer.append(" // %s %s", webMethod, method.webMethodPath()).eol();
writer.append(" @Override").eol();
AnnotationUtil.writeAnnotations(writer, method.element());
Expand Down Expand Up @@ -348,9 +375,14 @@ private void writePaths(Set<PathSegments.Segment> segments) {
for (PathSegments.Segment segment : segments) {
if (segment.isLiteral()) {
writer.append(".path(\"").append(segment.literalSection()).append("\")");
} else if (segment.isProperty()) {

writer.append(".path(").append(segmentPropertyMap.get(segment.name())).append(")");

} else {

writer.append(".path(").append(segment.name()).append(")");
//TODO: matrix params
// TODO: matrix params
}
}
if (!segments.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ public interface UserClient {

@Get("/users/{userId}")
String getUserById(String userId);

@Get("${property.path}/users/{userId}")
String property(String userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ static PathSegments parse(String fullPath) {
segments.add(segment);
chunks.named(segment.path(name), section.charAt(0));

} else if (isProperty(section)) {
String name = section.substring(2, section.length() - 1);
Segment segment = createPropertySegment(name);
segments.add(segment);
chunks.named(segment.path(name), section.charAt(0));

} else {
Segment segment = createLiteralSegment(section);
segments.add(segment);
Expand All @@ -52,8 +58,12 @@ private static boolean isPathParameter(String section) {
|| section.startsWith("<") && section.endsWith(">");
}

private static boolean isProperty(String section) {
return section.startsWith("${") && section.endsWith("}");
}

private static Segment createLiteralSegment(String section) {
return new Segment(section, true);
return new Segment(section, "literal");
}

private static Segment createSegment(String val) {
Expand All @@ -65,6 +75,11 @@ private static Segment createSegment(String val) {
return new Segment(matrixSplit[0], matrixKeys);
}

private static Segment createPropertySegment(String val) {

return new Segment(val, true);
}

private final Chunks chunks;
private final Set<Segment> segments;
private final List<Segment> withMatrixs = new ArrayList<>();
Expand Down Expand Up @@ -129,7 +144,6 @@ public boolean isEmpty() {

public static class Segment {

private static final Pattern PATTERN = Pattern.compile("[^a-zA-Z0-9_]|\\s");
private final String name;
private final String sanitizedName;
private final String literalSection;
Expand All @@ -144,26 +158,40 @@ public static class Segment {
*/
private final Set<String> matrixVarNames;

private final boolean property;

/**
* Create a normal segment.
*/
Segment(String name) {
this.name = name;
this.sanitizedName = PATTERN.matcher(name).replaceAll("_");
this.sanitizedName = Util.sanitizeName(name);
this.literalSection = null;
this.matrixKeys = null;
this.matrixVarNames = null;
this.property = false;
}

/** Create a normal segment. */
Segment(String name, boolean isProperty) {
this.name = name;
this.sanitizedName = null;
this.literalSection = null;
this.matrixKeys = null;
this.matrixVarNames = null;
this.property = isProperty;
}

/**
* Create a segment with matrix keys.
*/
Segment(String name, Set<String> matrixKeys) {
this.name = name;
this.sanitizedName = PATTERN.matcher(name).replaceAll("_");
this.sanitizedName = Util.sanitizeName(name);
this.literalSection = null;
this.matrixKeys = matrixKeys;
this.matrixVarNames = new HashSet<>();
this.property = false;
for (String key : matrixKeys) {
matrixVarNames.add(combine(name, key));
}
Expand All @@ -172,12 +200,13 @@ public static class Segment {
/**
* Create a literal path segment.
*/
public Segment(String section, boolean literalDummy) {
public Segment(String section, String literalDummy) {
this.literalSection = section;
this.name = null;
this.sanitizedName = null;
this.matrixKeys = null;
this.matrixVarNames = null;
this.property = false;
}

void addNames(Set<String> allNames) {
Expand Down Expand Up @@ -210,6 +239,10 @@ public boolean isLiteral() {
return literalSection != null;
}

public boolean isProperty() {
return property;
}

boolean isPathParameter(String varName) {
return (name != null && name.equals(varName)) || (matrixKeys != null && (matrixVarNames.contains(varName) || matrixKeys.contains(varName)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class Util {
Pattern.compile(", (?=(?:[^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*$)");
private static final Pattern PARENTHESIS_CONTENT = Pattern.compile("\\((.*?)\\)");

private static final Pattern SANITIZE_PATTERN = Pattern.compile("[^a-zA-Z0-9_]|\\s");

/**
* Parse the raw type potentially handling generic parameters.
*/
Expand All @@ -48,6 +50,11 @@ public static String typeDef(TypeMirror typeMirror) {
}
}

/** Sanitize the given string such that it can be used as a method/class/field name */
public static String sanitizeName(String name) {
return SANITIZE_PATTERN.matcher(name).replaceAll("_");
}

/** Trim off annotations from the raw type if present. */
public static String trimAnnotations(String input) {
input = COMMA_PATTERN.matcher(input).replaceAll(",");
Expand Down Expand Up @@ -173,13 +180,11 @@ public static String initcapSnake(String input) {
if (ch == '-') {
sb.append(ch);
upper = true;
} else if (upper) {
sb.append(Character.toUpperCase(ch));
upper = false;
} else {
if (upper) {
sb.append(Character.toUpperCase(ch));
upper = false;
} else {
sb.append(ch);
}
sb.append(ch);
}
}
return sb.toString();
Expand Down

0 comments on commit 347522b

Please sign in to comment.