Skip to content

Commit

Permalink
Add ability to use multiple dialects (#3)
Browse files Browse the repository at this point in the history
* feat: add dialect functionality and basic spring implementation for it

* feat: add request parameters

* fix: correct imports, allow spring controllers to use annotations

* fix: move generation of converters to generated services

* fix: allow services to be loaded properly

* fix: correct code smells

* feat: add javadocs

* feat: add docs for dialects

* feat: add docs for dialects

* fix: correct javadoc
  • Loading branch information
zskamljic authored Feb 14, 2022
1 parent 0316e98 commit 2f1b1a6
Show file tree
Hide file tree
Showing 125 changed files with 1,305 additions and 456 deletions.
11 changes: 11 additions & 0 deletions Dialects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Custom dialects

Defining a new dialect is possible by depending on rest-ahead-processor. Required steps are as following:

1. Create a class implementing `io.github.zskamljic.restahead.polyglot.Dialect`.
2. Create a file in `main/resources/META-INF/services` named `io.github.zskamljic.restahead.polyglot.Dialect` (note that
this is a file name, not a Java package)
3. In created file add the line with FQCN of the class created in #1.
4. In project where you want the dialect to be used add the dependency with at least `provided` scope.

For sample implementation see Spring Dialect.
35 changes: 35 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ approach.
- [Query](#queries)
- [Responses](#response-types)
- [Spring Boot](#spring-boot)
- [Dialects](#dialects)

## Introduction

Expand Down Expand Up @@ -357,6 +358,40 @@ public interface DemoService {
injection. URL property needs to be provided to have a baseUrl configured, converter property is optional and is
required only if the service requires one, see [response types](#response-types).

### Dialects

The code generator allows for usage of multiple dialects (Default being RestAhead). For example, Spring dialect can be
used, by adding the dependency:

```xml

<dependency>
<groupId>io.github.zskamljic</groupId>
<artifactId>rest-ahead-spring-dialect</artifactId>
<version>${rest.ahead.version}</version>
</dependency>
```

It can then be used as following:

```java
interface SpringService {
@GetMappin("/get")
Response performGet(@RequestHeader String header, @RequestParam String query);

@RequestMapping(method = RequestMethod.GET, value = "/{param}")
HttpBinResponse get2(@PathVariable String param);

@PostMapping("/multipart")
HttpBinResponse postFile(@RequestPart MultiPartFile file);

@PostMapping("/customBody")
HttpBinResponse postFormData(@RequestPart Map<String, String> body);
}
```

Info on how to declare a new dialect can be seen in [Dialects](Dialects.md)

## Adding to project

Add the dependencies as following:
Expand Down
6 changes: 6 additions & 0 deletions demo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
<version>0.3.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.github.zskamljic</groupId>
<artifactId>rest-ahead-spring-dialect</artifactId>
<version>0.3.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.github.zskamljic</groupId>
<artifactId>rest-ahead-jackson-converter</artifactId>
Expand Down
9 changes: 7 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@
<module>rest-ahead-jackson-converter</module>
<module>test-report-aggregator</module>
<module>rest-ahead-spring</module>
<module>rest-ahead-spring-dialect</module>
</modules>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<junit.version>5.8.2</junit.version>
<mockito.version>4.2.0</mockito.version>
<compiler.plugin.version>3.8.1</compiler.plugin.version>
<mockito.version>4.3.1</mockito.version>
<compiler.plugin.version>3.9.0</compiler.plugin.version>
<surefire.version>2.22.2</surefire.version>
<jacoco.version>0.8.7</jacoco.version>

Expand All @@ -42,6 +43,10 @@
<sonar.language>java</sonar.language>
<sonar.organization>zskamljic-github</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<truth.version>1.1.3</truth.version>
<testing.compile.version>0.19</testing.compile.version>

<spring.version>5.3.15</spring.version>
</properties>

<url>https://github.com/zskamljic/rest-ahead</url>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.zskamljic.restahead.conversion;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.stream.Collectors;

/**
* The default converter for Map classes.
*/
public final class MapFormConverter {
private MapFormConverter() {
}

/**
* Encodes the given value in format [key]=[URL encoded value] separated by &amp;
* @param value the values to encode
* @param <K> type of the key
* @param <V> type of the value
* @return the {@link InputStream} with encoded data
*/
public static <K, V> InputStream formEncode(Map<K, V> value) {
var stringValue = value.entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + URLEncoder.encode(String.valueOf(entry.getValue()), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
return new ByteArrayInputStream(stringValue.getBytes(StandardCharsets.UTF_8));
}
}
25 changes: 0 additions & 25 deletions rest-ahead-processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,6 @@
</dependency>

<!-- Testing dependencies -->
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
<version>1.1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.testing.compile</groupId>
<artifactId>compile-testing</artifactId>
<version>0.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
Expand Down Expand Up @@ -87,17 +75,4 @@
</plugin>
</plugins>
</build>

<profiles>
<profile>
<id>testing only</id>
<activation>
<jdk>[9,)</jdk>
</activation>
<properties>
<argLine>--add-opens jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</argLine>
</properties>
</profile>
</profiles>

</project>
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package io.github.zskamljic.restahead.encoding;

import io.github.zskamljic.restahead.encoding.generation.GenerationStrategy;
import io.github.zskamljic.restahead.encoding.generation.FormConversionStrategy;

/**
* Specifies that the type should use form encoding.
*
* @param parameterName the name of parameter to encode
* @param strategy the strategy to use when generating the conversion code
*/
public record FormBodyEncoding(String parameterName, GenerationStrategy strategy) implements BodyEncoding {
public record FormBodyEncoding(String parameterName, FormConversionStrategy strategy) implements BodyEncoding {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
/**
* A parameter that specifies a part of multipart request.
*
* @param httpName the name to use in http transport
* @param name the name of function parameter
* @param type which type to use when generating the code, empty if it's already an appropriate type
* @param httpName the name to use in http transport
* @param name the name of function parameter
* @param type which type to use when generating the code, empty if it's already an appropriate type
* @param extraParameters extra parameters that should be sent to the constructor
*/
public record MultiPartParameter(
String httpName,
String name,
Optional<Class<? extends MultiPart>> type
Optional<Class<? extends MultiPart>> type,
Optional<String> extraParameters
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
/**
* Used to generate class to form encoded string conversion.
*/
public record ClassGenerationStrategy(TypeMirror type) implements GenerationStrategy {
@Override
public MethodSpec generateMethod() {
public record ClassGenerationStrategy(TypeMirror type) implements FormConversionStrategy {
public MethodSpec generate() {
var builder = MethodSpec.methodBuilder(Variables.FORM_ENCODE)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(TypeName.get(type), "value")
Expand All @@ -44,7 +43,7 @@ public MethodSpec generateMethod() {
* @param mirror the type for which to find a strategy
* @return generation strategy if no issues were discovered, empty otherwise
*/
public static Optional<GenerationStrategy> getIfSupported(Messager messager, Elements elements, Types types, TypeMirror mirror) {
public static Optional<FormConversionStrategy> getIfSupported(Messager messager, Elements elements, Types types, TypeMirror mirror) {
if (!(mirror instanceof DeclaredType declaredType)) return Optional.empty();

var getters = findGetters(declaredType);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.github.zskamljic.restahead.encoding.generation;

import com.squareup.javapoet.MethodSpec;

import javax.annotation.processing.Messager;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
Expand All @@ -12,7 +10,7 @@
/**
* Outlines the generation strategy for some type.
*/
public sealed interface GenerationStrategy permits ClassGenerationStrategy, MapGenerationStrategy, RecordGenerationStrategy {
public sealed interface FormConversionStrategy permits ClassGenerationStrategy, MapConversionStrategy, RecordGenerationStrategy {
/**
* The type that this strategy applies to. Return from this value will be used to ensure that only one converter
* will be generated for each type.
Expand All @@ -21,13 +19,6 @@ public sealed interface GenerationStrategy permits ClassGenerationStrategy, MapG
*/
TypeMirror type();

/**
* Generate the method for this type and strategy.
*
* @return the generated convert method.
*/
MethodSpec generateMethod();

/**
* Selects an appropriate generation strategy for given type.
*
Expand All @@ -37,9 +28,9 @@ public sealed interface GenerationStrategy permits ClassGenerationStrategy, MapG
* @param mirror the type for which to find a strategy
* @return the strategy or empty if none was found
*/
static Optional<GenerationStrategy> select(Messager messager, Elements elements, Types types, TypeMirror mirror) {
static Optional<FormConversionStrategy> select(Messager messager, Elements elements, Types types, TypeMirror mirror) {
Stream<OptionalStrategyProvider> providers = Stream.of(
MapGenerationStrategy::getIfSupported,
MapConversionStrategy::getIfSupported,
RecordGenerationStrategy::getIfSupported,
ClassGenerationStrategy::getIfSupported
);
Expand All @@ -53,6 +44,6 @@ static Optional<GenerationStrategy> select(Messager messager, Elements elements,
*/
@FunctionalInterface
interface OptionalStrategyProvider {
Optional<GenerationStrategy> getIfSupported(Messager messager, Elements elements, Types types, TypeMirror mirror);
Optional<FormConversionStrategy> getIfSupported(Messager messager, Elements elements, Types types, TypeMirror mirror);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.github.zskamljic.restahead.encoding.generation;

import io.github.zskamljic.restahead.modeling.TypeValidator;

import javax.annotation.processing.Messager;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.util.Map;
import java.util.Optional;

/**
* Generates a form converter for maps.
*/
public record MapConversionStrategy(TypeMirror type) implements FormConversionStrategy {

/**
* Checks if provided type is a Map or one of the subclasses that has string representable keys and values.
*
* @param elements the elements to fetch type information from
* @param types the types utility to use for typing info
* @param mirror the type for which to find a strategy
* @return a strategy if data is valid, empty otherwise
*/
public static Optional<FormConversionStrategy> getIfSupported(Messager messager, Elements elements, Types types, TypeMirror mirror) {
var type = elements.getTypeElement(Map.class.getCanonicalName())
.asType();
if (!types.isAssignable(types.erasure(mirror), type)) {
return Optional.empty();
}
var mapType = (DeclaredType) mirror;
var genericArguments = mapType.getTypeArguments();
if (genericArguments.size() != 2) return Optional.empty();

var stringValidator = new TypeValidator(elements, types);
var key = genericArguments.get(0);
var value = genericArguments.get(1);

if (stringValidator.isUnsupportedType(key) || stringValidator.isUnsupportedType(value)) {
messager.printMessage(Diagnostic.Kind.ERROR, "Maps must consist of string representable values to be formEncoded", mapType.asElement());
return Optional.empty();
}

return Optional.of(new MapConversionStrategy(types.erasure(type)));
}
}
Loading

0 comments on commit 2f1b1a6

Please sign in to comment.