Skip to content

Commit

Permalink
Merge pull request #26 from leandreck/9-feature/path-params
Browse files Browse the repository at this point in the history
[RFR] 9-feature/path-params
  • Loading branch information
Mathias Kowalzik authored Oct 25, 2016
2 parents e0f6955 + 93abe81 commit 5da4b62
Show file tree
Hide file tree
Showing 15 changed files with 622 additions and 156 deletions.
58 changes: 38 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,45 +38,63 @@ Just specify the dependency in your maven based build.
```

# Example
The following snippet will produce a TestTypeScriptEndpoint.ts and a RootType.model.ts file.
The following snippet will produce a TestTypeScriptEndpoint.ts and a ISubType.model.ts file.
```java
import org.leandreck.endpoints.annotations.TypeScriptEndpoint;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
//...
import static org.springframework.web.bind.annotation.RequestMethod.*;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.List;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.web.bind.annotation.RequestMethod.POST;

@TypeScriptEndpoint
@RestController
@RequestMapping("/api")
public class TestTypeScriptEndpoint {

@RequestMapping(value = "/persons", method = GET, produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody List<RootType> getPersons() {
final List<RootType> rootTypes = new ArrayList<>();
rootTypes.add(new RootType());
return rootTypes;
@RequestMapping(value = "/type/{id}/{typeRef}", method = POST,
consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public List<SubType> setId(@PathVariable Long id, @RequestBody SubType body) {
// do something
return Collections.singletonList(body);
}
}
```
and the produced TypeScript files from the default templates look like:

```typescript
import { IRootType } from './IRootType.model';
import { ISubType } from './ISubType.model';

import { Http, Response, RequestOptions, Headers, RequestOptionsArgs } from "@angular/http";
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';

import { Observable } from "rxjs/Observable";
import { ErrorObservable } from "rxjs/observable/ErrorObservable";
import "rxjs/add/operator/do";
import "rxjs/add/operator/catch";
import "rxjs/add/observable/throw";

@Injectable()
export class TestTypeScriptEndpoint {

private serviceBaseURL = '/api'

constructor(private _http: Http) { }

get_getPersons(): IRootType[] {
return this._http.get(this.serviceBaseURL + '/persons')
.map((res: Response) => res.json())
.catch(this.handleError);
constructor(private http: Http) { }
/* POST */
public setIdPost(id: number, body: ISubType): Observable<ISubType[]> {
let url = this.serviceBaseURL + '/type/' + id + '/' + typeRef + '';
return this.httpPost(url, body)
.map((response: Response) => <ISubType[]>response.json())
.catch((error: Response) => this.handleError(error));
}
private httpPost(url: string, body: any): Observable<Response> {
console.info('httpPost: ' + url);
return this.http.post(url, body);
}
}
```

[freemarker]: http://freemarker.org/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ public class RequestMapping {
private final String[] value;

public RequestMapping(def method, def produces, def value) {
this.method = method;
this.produces = produces;
this.value = value;
this.method = method == null ? [] : method;
this.produces = produces == null ? [] : produces;
this.value = value == null ? [] : value;
}

public RequestMethod[] method() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ private static Collection<TypeNode> flatten(TypeNode root) {
.flatMap(Collection::stream)
.filter(c -> !c.isMappedType())
.collect(toSet());
typeSet.add(root);
if (root.isDeclaredComplexType()) {
typeSet.add(root);
}
return typeSet;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public class MethodNode {
private final String url;
private final boolean ignored;
private final TypeNode returnType;
private final TypeNode paramType;
private final TypeNode requestBodyType;
private final List<TypeNode> pathVariableTypes;
private final List<String> httpMethods;
private final Set<TypeNode> types;

Expand All @@ -36,17 +37,20 @@ public MethodNode(final String name, final String url, final boolean ignored, fi
this.ignored = ignored;
this.returnType = returnType;
this.httpMethods = httpMethods;
this.paramType = null;
requestBodyType = null;
pathVariableTypes = Collections.emptyList();
this.types = collectTypes();
}

public MethodNode(final String name, final String url, final boolean ignored, final List<String> httpMethods, final TypeNode returnType, final TypeNode paramType) {
public MethodNode(final String name, final String url, final boolean ignored, final List<String> httpMethods,
final TypeNode returnType, final TypeNode requestBodyType, final List<TypeNode> pathVariableTypes) {
this.name = name;
this.url = url;
this.ignored = ignored;
this.url = url;
this.returnType = returnType;
this.httpMethods = httpMethods;
this.paramType = paramType;
this.requestBodyType = requestBodyType;
this.pathVariableTypes = pathVariableTypes;
this.types = collectTypes();
}

Expand All @@ -55,8 +59,8 @@ private Set<TypeNode> collectTypes() {
if (returnType != null) {
typeMap.put(returnType.getTypeName(), returnType);
}
if (paramType != null) {
typeMap.put(paramType.getTypeName(), paramType);
if (requestBodyType != null) {
typeMap.put(requestBodyType.getTypeName(), requestBodyType);
}
return new HashSet<>(typeMap.values());
}
Expand All @@ -81,11 +85,15 @@ public String getUrl() {
return url;
}

public TypeNode getParamType() {
return paramType;
public TypeNode getRequestBodyType() {
return requestBodyType;
}

public Set<TypeNode> getTypes() {
return types;
return Collections.unmodifiableSet(types);
}

public List<TypeNode> getPathVariableTypes() {
return Collections.unmodifiableList(pathVariableTypes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import org.leandreck.endpoints.annotations.TypeScriptIgnore;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
Expand All @@ -27,7 +29,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Optional;

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

/**
* Created by Mathias Kowalzik ([email protected]) on 28.08.2016.
Expand All @@ -51,21 +55,41 @@ public MethodNode createMethodNode(final ExecutableElement methodElement) {
return new MethodNode(name, "", true, null, null);
}
final String url = defineUrl(requestMapping);

final List<String> httpMethods = defineHttpMethods(requestMapping);
final TypeNode returnType = defineReturnType(methodElement);

final List<? extends VariableElement> parameters = methodElement.getParameters();
final TypeNode requestBodyType = defineRequestBodyType(parameters);
final List<TypeNode> pathVariables = definePathVariableTypes(parameters);

return new MethodNode(name, url, false, httpMethods, returnType, requestBodyType, pathVariables);
}

private TypeNode defineReturnType(final ExecutableElement methodElement) {
final TypeMirror returnMirror = methodElement.getReturnType();
final TypeNode returnType = typeNodeFactory.createTypeNode(returnMirror);
return typeNodeFactory.createTypeNode(returnMirror);
}

private List<TypeNode> definePathVariableTypes(final List<? extends VariableElement> parameters) {
return parameters.stream()
.filter(p -> p.getAnnotation(PathVariable.class) != null)
.map(p -> typeNodeFactory.createTypeNode(p))
.collect(toList());
}

final TypeNode paramType;
if (methodElement.getParameters().isEmpty()) {
paramType = null;
private TypeNode defineRequestBodyType(final List<? extends VariableElement> parameters) {
final Optional<? extends VariableElement> optional = parameters.stream()
.filter(p -> p.getAnnotation(RequestBody.class) != null)
.findFirst();
final TypeNode requestBodyType;
if (optional.isPresent()) {
final VariableElement paramElement = optional.get();
requestBodyType = typeNodeFactory.createTypeNode(paramElement);
} else {
final VariableElement paramElement = methodElement.getParameters().get(0);
paramType = typeNodeFactory.createTypeNode(paramElement);
requestBodyType = null;
}

return new MethodNode(name, url, false, httpMethods, returnType, paramType);
return requestBodyType;
}

private static List<String> defineHttpMethods(final RequestMapping requestMapping) {
Expand All @@ -74,7 +98,7 @@ private static List<String> defineHttpMethods(final RequestMapping requestMappin
if (requestMapping != null) {
return Arrays.stream(requestMapping.method())
.map(requestMethod -> requestMethod.toString().toLowerCase())
.collect(Collectors.toList());
.collect(toList());
}

return methods;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class TypeNode {
private final List<TypeNode> typeParameters;
private final List<TypeNode> children;
private final Set<TypeNode> types;
private final boolean isDeclaredComplexType;

public TypeNode(final String fieldName, final String typeName, final TypeNodeKind kind) {
this.fieldName = fieldName;
Expand All @@ -43,6 +44,7 @@ public TypeNode(final String fieldName, final String typeName, final TypeNodeKin
mappedType = true;
type = defineType();
types = collectTypes();
isDeclaredComplexType = false;
}

public TypeNode(final String fieldName, final String typeName, final List<TypeNode> typeParameters, final String template, final TypeNodeKind kind, final List<TypeNode> children) {
Expand All @@ -55,6 +57,18 @@ public TypeNode(final String fieldName, final String typeName, final List<TypeNo
mappedType = false;
type = defineType();
types = collectTypes();
isDeclaredComplexType = defineIsDeclaredComplexType();
}

private boolean defineIsDeclaredComplexType() {
final boolean isDeclared;
if (this.isMappedType()
|| TypeNodeKind.MAP.equals(this.getKind())) {
isDeclared = false;
} else {
isDeclared = true;
}
return isDeclared;
}

public String getFieldName() {
Expand All @@ -76,10 +90,7 @@ private String defineType() {
name = typeName + "[]";
break;
case MAP:
final String[] types = typeName.split("/");
final String keyName = mappedType ? "I" + types[0] : types[0];
final String valueName = mappedType ? "I" + types[1] : types[1];
name = "{ [index: " + keyName + "]: " + valueName + " }";
name = "{ [index: " + typeParameters.get(0).type + "]: " + typeParameters.get(1).type + " }";
break;
default:
name = typeName;
Expand Down Expand Up @@ -142,4 +153,9 @@ public List<TypeNode> getTypeParameters() {
public Set<TypeNode> getTypes() {
return types;
}


public boolean isDeclaredComplexType() {
return isDeclaredComplexType;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ class TypeNodeFactory {
private static final String NUMBER_TYPE = "number";
private static final String STRING_TYPE = "string";
private static final String BOOLEAN_TYPE = "boolean";
public static final String UNDEFINED = "UNDEFINED";
public static final String JAVA_LANG_OBJECT = "java.lang.Object";
private static final String UNDEFINED = "UNDEFINED";
private static final String JAVA_LANG_OBJECT = "java.lang.Object";
private final TypeMirror objectMirror;

static {
//Void
Expand Down Expand Up @@ -94,6 +95,7 @@ class TypeNodeFactory {
public TypeNodeFactory(final Types typeUtils, final Elements elementUtils) {
this.typeUtils = typeUtils;
this.elementUtils = elementUtils;
objectMirror = elementUtils.getTypeElement(JAVA_LANG_OBJECT).asType();
}

/**
Expand Down Expand Up @@ -176,10 +178,18 @@ private List<TypeNode> defineTypeParameters(final TypeNodeKind typeNodeKind, fin
if (TypeNodeKind.COLLECTION.equals(typeNodeKind) || TypeNodeKind.MAP.equals(typeNodeKind)) {
final DeclaredType declaredType = (DeclaredType) typeMirror;
final List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();

typeParameters = typeArguments.stream()
.filter(t -> !t.getKind().equals(TypeKind.WILDCARD))
.map(t -> t.getKind().equals(TypeKind.WILDCARD) ? objectMirror : t)
.map(this::createTypeNode)
.collect(toList());

if (typeParameters.isEmpty()) {
typeParameters.add(createTypeNode(objectMirror));
if (TypeNodeKind.MAP.equals(typeNodeKind)) {
typeParameters.add(createTypeNode(objectMirror));
}
}
} else {
typeParameters = Collections.emptyList();
}
Expand Down
Loading

0 comments on commit 5da4b62

Please sign in to comment.