From c9963d067582d7ddee308a074ee0ba66563a90bd Mon Sep 17 00:00:00 2001 From: Mathias Kowalzik Date: Wed, 19 Oct 2016 00:05:05 +0200 Subject: [PATCH 1/6] fixed generating of erroneous Type-Interfaces for MAP-TypeNodeKinds --- .../processor/model/RequestMapping.groovy | 6 +- .../processor/model/EndpointNode.java | 4 +- .../endpoints/processor/model/MethodNode.java | 34 +++-- .../processor/model/MethodNodeFactory.java | 5 +- .../endpoints/processor/model/TypeNode.java | 32 +++-- .../processor/model/TypeNodeFactory.java | 16 ++- .../templates/typescript/service.ftl | 18 +-- .../TypeScriptEndpointProcessorSpec.groovy | 120 ++++++++++-------- .../endpoints/templates/testing/service.ftl | 21 +++ .../endpoints/httpmethods/Endpoint.gstring | 8 +- .../endpoints/pathvariable/Endpoint.gstring | 36 ++++++ 11 files changed, 205 insertions(+), 95 deletions(-) create mode 100644 annotations/src/test/testcases/org/leandreck/endpoints/pathvariable/Endpoint.gstring diff --git a/annotations/src/main/groovy/org/leandreck/endpoints/processor/model/RequestMapping.groovy b/annotations/src/main/groovy/org/leandreck/endpoints/processor/model/RequestMapping.groovy index a57374f..cc9abf9 100644 --- a/annotations/src/main/groovy/org/leandreck/endpoints/processor/model/RequestMapping.groovy +++ b/annotations/src/main/groovy/org/leandreck/endpoints/processor/model/RequestMapping.groovy @@ -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() { diff --git a/annotations/src/main/java/org/leandreck/endpoints/processor/model/EndpointNode.java b/annotations/src/main/java/org/leandreck/endpoints/processor/model/EndpointNode.java index 0679cc0..a02b48f 100644 --- a/annotations/src/main/java/org/leandreck/endpoints/processor/model/EndpointNode.java +++ b/annotations/src/main/java/org/leandreck/endpoints/processor/model/EndpointNode.java @@ -96,7 +96,9 @@ private static Collection flatten(TypeNode root) { .flatMap(Collection::stream) .filter(c -> !c.isMappedType()) .collect(toSet()); - typeSet.add(root); + if (root.isDeclaredComplexType()) { + typeSet.add(root); + } return typeSet; } diff --git a/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNode.java b/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNode.java index 5d8c140..79a4374 100644 --- a/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNode.java +++ b/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNode.java @@ -1,12 +1,12 @@ /** * Copyright © 2016 Mathias Kowalzik (Mathias.Kowalzik@leandreck.org) - * + *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -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 pathVariableTypes; private final List httpMethods; private final Set types; @@ -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 httpMethods, final TypeNode returnType, final TypeNode paramType) { + public MethodNode(final String name, final String url, final boolean ignored, final List httpMethods, + final TypeNode returnType, final TypeNode paramType, final List pathVariableTypes) { this.name = name; this.url = url; this.ignored = ignored; this.returnType = returnType; this.httpMethods = httpMethods; - this.paramType = paramType; + this.requestBodyType = paramType; + this.pathVariableTypes = pathVariableTypes; this.types = collectTypes(); } @@ -55,8 +59,8 @@ private Set 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()); } @@ -81,11 +85,15 @@ public String getUrl() { return url; } - public TypeNode getParamType() { - return paramType; + public TypeNode getRequestBodyType() { + return requestBodyType; } public Set getTypes() { - return types; + return Collections.unmodifiableSet(types); + } + + public List getPathVariableTypes() { + return Collections.unmodifiableList(pathVariableTypes); } } diff --git a/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNodeFactory.java b/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNodeFactory.java index f3c1178..74b87d2 100644 --- a/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNodeFactory.java +++ b/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNodeFactory.java @@ -26,6 +26,7 @@ import javax.lang.model.util.Types; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -57,7 +58,7 @@ public MethodNode createMethodNode(final ExecutableElement methodElement) { final TypeMirror returnMirror = methodElement.getReturnType(); final TypeNode returnType = typeNodeFactory.createTypeNode(returnMirror); - final TypeNode paramType; + final TypeNode paramType; //FIXME define real requestBodyParam if (methodElement.getParameters().isEmpty()) { paramType = null; } else { @@ -65,7 +66,7 @@ public MethodNode createMethodNode(final ExecutableElement methodElement) { paramType = typeNodeFactory.createTypeNode(paramElement); } - return new MethodNode(name, url, false, httpMethods, returnType, paramType); + return new MethodNode(name, url, false, httpMethods, returnType, paramType, Collections.emptyList()); //FIXME define real PathVariables } private static List defineHttpMethods(final RequestMapping requestMapping) { diff --git a/annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNode.java b/annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNode.java index e79b619..47db0ff 100644 --- a/annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNode.java +++ b/annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNode.java @@ -1,12 +1,12 @@ /** * Copyright © 2016 Mathias Kowalzik (Mathias.Kowalzik@leandreck.org) - * + *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -32,6 +32,7 @@ public class TypeNode { private final List typeParameters; private final List children; private final Set types; + private final boolean isDeclaredComplexType; public TypeNode(final String fieldName, final String typeName, final TypeNodeKind kind) { this.fieldName = fieldName; @@ -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 typeParameters, final String template, final TypeNodeKind kind, final List children) { @@ -55,6 +57,18 @@ public TypeNode(final String fieldName, final String typeName, final List getTypeParameters() { public Set getTypes() { return types; } + + + public boolean isDeclaredComplexType() { + return isDeclaredComplexType; + } } diff --git a/annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNodeFactory.java b/annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNodeFactory.java index 603d0d3..597f7ae 100644 --- a/annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNodeFactory.java +++ b/annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNodeFactory.java @@ -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 @@ -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(); } /** @@ -176,10 +178,18 @@ private List defineTypeParameters(final TypeNodeKind typeNodeKind, fin if (TypeNodeKind.COLLECTION.equals(typeNodeKind) || TypeNodeKind.MAP.equals(typeNodeKind)) { final DeclaredType declaredType = (DeclaredType) typeMirror; final List 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(); } diff --git a/annotations/src/main/resources/org/leandreck/endpoints/templates/typescript/service.ftl b/annotations/src/main/resources/org/leandreck/endpoints/templates/typescript/service.ftl index 34c4be7..e2fda7a 100644 --- a/annotations/src/main/resources/org/leandreck/endpoints/templates/typescript/service.ftl +++ b/annotations/src/main/resources/org/leandreck/endpoints/templates/typescript/service.ftl @@ -71,9 +71,9 @@ export class ${serviceName} { /* POST */ <#list getPostMethods() as method> - public ${method.name}Post(${method.paramType.fieldName}: ${method.paramType.type}): Observable<${method.returnType.type}> { + public ${method.name}Post(${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable<${method.returnType.type}> { let url = this.serviceBaseURL + '${method.url}'; - return this.httpPost(url, ${method.paramType.fieldName}) + return this.httpPost(url, ${method.requestBodyType.fieldName}) .map((response: Response) => <${method.returnType.type}>response.json()) .catch((error: Response) => this.handleError(error)); } @@ -88,9 +88,9 @@ export class ${serviceName} { /* PUT */ <#list getPutMethods() as method> - public ${method.name}Put(${method.paramType.fieldName}: ${method.paramType.type}): Observable<${method.returnType.type}> { + public ${method.name}Put(${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable<${method.returnType.type}> { let url = this.serviceBaseURL + '${method.url}'; - return this.httpPut(url, ${method.paramType.fieldName}) + return this.httpPut(url, ${method.requestBodyType.fieldName}) .map((response: Response) => <${method.returnType.type}>response.json()) .catch((error: Response) => this.handleError(error)); } @@ -105,9 +105,9 @@ export class ${serviceName} { /* PATCH */ <#list getPatchMethods() as method> - public ${method.name}Patch(${method.paramType.fieldName}: ${method.paramType.type}): Observable<${method.returnType.type}> { + public ${method.name}Patch(${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable<${method.returnType.type}> { let url = this.serviceBaseURL + '${method.url}'; - return this.httpPatch(url, ${method.paramType.fieldName}) + return this.httpPatch(url, ${method.requestBodyType.fieldName}) .map((response: Response) => <${method.returnType.type}>response.json()) .catch((error: Response) => this.handleError(error)); } @@ -138,7 +138,7 @@ export class ${serviceName} { /* OPTIONS */ <#list getOptionsMethods() as method> - public ${method.name}Options(${method.paramType.fieldName}: ${method.paramType.type}): Observable { + public ${method.name}Options(${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable { let url = this.serviceBaseURL + '${method.url}'; return this.httpOptions(url) .catch((error: Response) => this.handleError(error)); @@ -154,9 +154,9 @@ export class ${serviceName} { /* TRACE */ <#list getTraceMethods() as method> - public ${method.name}Trace(${method.paramType.fieldName}: ${method.paramType.type}): Observable<${method.paramType.type}> { + public ${method.name}Trace(${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable<${method.requestBodyType.type}> { let url = this.serviceBaseURL + '${method.url}'; - return this.httpTrace(url, ${method.paramType.fieldName}) + return this.httpTrace(url, ${method.requestBodyType.fieldName}) .map((response: Response) => <${method.returnType.type}>response.json()) .catch((error: Response) => this.handleError(error)); } diff --git a/annotations/src/test/groovy/org/leandreck/endpoints/processor/TypeScriptEndpointProcessorSpec.groovy b/annotations/src/test/groovy/org/leandreck/endpoints/processor/TypeScriptEndpointProcessorSpec.groovy index a2b1d64..1088323 100644 --- a/annotations/src/test/groovy/org/leandreck/endpoints/processor/TypeScriptEndpointProcessorSpec.groovy +++ b/annotations/src/test/groovy/org/leandreck/endpoints/processor/TypeScriptEndpointProcessorSpec.groovy @@ -27,6 +27,8 @@ import javax.tools.JavaFileObject import java.nio.file.Files import java.util.stream.Collectors +import static groovy.io.FileType.FILES + /** * Created by Mathias Kowalzik (Mathias.Kowalzik@leandreck.org) on 31.08.2016. */ @@ -61,18 +63,20 @@ class TypeScriptEndpointProcessorSpec extends Specification { given: "a simple Endpoint" def folder = "/case1" def sourceFile = getSourceFile("$folder/Endpoint.gstring", "$folder/Endpoint.java", [returnType: returnType, returnValue: returnValue]) - - Files.createDirectories(new File("$annotationsTarget/$folder").toPath()) + def destinationFolder = initFolder folder when: "a simple Endpoint is compiled" - List> diagnostics = - CompilerTestHelper.compileTestCase(Arrays. asList(new TypeScriptEndpointProcessor()), folder, sourceFile) - def model = jsonSlurper.parse(new File("$annotationsTarget/$folder/Endpoint.ts")) + def diagnostics = CompilerTestHelper.compileTestCase([new TypeScriptEndpointProcessor()], folder, sourceFile) then: "there should be no errors" diagnostics.every { d -> (Diagnostic.Kind.ERROR != d.kind) } + and: "there must be no declared typescript interface file" + def allTSFiles = getInterfaceFiles(destinationFolder) + allTSFiles.size() == 0 + and: "the scanned model should be correct" + def model = jsonSlurper.parse(new File("$annotationsTarget/$folder/Endpoint.ts")) with(model) { serviceName == "Endpoint" serviceUrl == "/api" @@ -85,6 +89,7 @@ class TypeScriptEndpointProcessorSpec extends Specification { cleanup: "remove test java source file" sourceFile.delete() + destinationFolder.deleteDir() where: "possible simple values for case1 are" returnType | returnValue || mappedType @@ -219,25 +224,32 @@ class TypeScriptEndpointProcessorSpec extends Specification { @Unroll def "if a Method is annotated with TypeScriptIgnore it should be ignored"() { given: "an Endpoint with a TypeScriptIgnore annotated Method" - def classFile = new File("$defaultPathBase/src/test/testcases/org/leandreck/endpoints/ignored/${ignoreClass}.java") def folder = "/ignored" - Files.createDirectories(new File("$annotationsTarget/$folder").toPath()) + def sourceFile = new File("$endpointsPath$folder/${ignoreClass}.java") + def destinationFolder = initFolder folder when: "a simple Endpoint is compiled" - List> diagnostics = - CompilerTestHelper.compileTestCase(Arrays. asList(new TypeScriptEndpointProcessor()), folder, classFile) - def model = jsonSlurper.parse(new File("$annotationsTarget/$folder/${ignoreClass}.ts")) + def diagnostics = CompilerTestHelper.compileTestCase([new TypeScriptEndpointProcessor()], folder, sourceFile) then: "there should be no errors" diagnostics.every { d -> (Diagnostic.Kind.ERROR != d.kind) } + and: "there must be no declared typescript interface file" + def allTSFiles = getInterfaceFiles(destinationFolder) + allTSFiles.size() == 0 + and: "the scanned model should contain no method" + def model = jsonSlurper.parse(new File("$annotationsTarget/$folder/${ignoreClass}.ts")) with(model) { serviceName == ignoreClass serviceUrl == "/api" methodCount == 0 } + cleanup: "remove test java source file" + // Do not delete the source files: sourceFile.delete(), because they are not generated in this testcase! + destinationFolder.deleteDir() + where: "possible class files with ignored Methods are" ignoreClass || bogus "Annotated" || "" @@ -252,19 +264,20 @@ class TypeScriptEndpointProcessorSpec extends Specification { @Unroll def "each RequestMethod #httpMethod results in a specific httpMethod-Entry"() { given: "an Endpoint with a HttpMethod" - def gstring = Eval.me("httpMethod", httpMethod, new File("$defaultPathBase/src/test/testcases/org/leandreck/endpoints/httpmethods/Endpoint.gstring").text) - def classFile = new File("$defaultPathBase/src/test/testcases/org/leandreck/endpoints/httpmethods/${httpMethod}.java") - Files.createDirectories(new File("$annotationsTarget/httpmethods").toPath()) - classFile.text = gstring.toString() def folder = "/httpmethods" + def sourceFile = getSourceFile("$folder/Endpoint.gstring", "$folder/${httpMethod}.java", [httpMethod: httpMethod]) + def destinationFolder = initFolder folder when: "the Endpoint is compiled" - List> diagnostics = - CompilerTestHelper.compileTestCase(Arrays. asList(new TypeScriptEndpointProcessor()), folder, classFile) + def diagnostics = CompilerTestHelper.compileTestCase([new TypeScriptEndpointProcessor()], folder, sourceFile) then: "there should be no errors" diagnostics.every { d -> (Diagnostic.Kind.ERROR != d.kind) } + and: "there must be no declared typescript interface file" + def allTSFiles = getInterfaceFiles(destinationFolder) + allTSFiles.size() == 0 + and: "the scanned model should contain the httpmethod" def model = jsonSlurper.parse(new File("$annotationsTarget${folder}/${httpMethod}.ts")) with(model) { @@ -274,8 +287,9 @@ class TypeScriptEndpointProcessorSpec extends Specification { getProperty("${httpMethod.toString().toLowerCase()}MethodCount") == 1 } - cleanup: "remove classfile" - classFile.delete() + cleanup: "remove test java source file" + sourceFile.delete() + destinationFolder.deleteDir() where: "possible http-Methods are all possible values from RequestMethod" httpMethod << Arrays.stream(RequestMethod.values()).map({ m -> m.toString() }).collect(Collectors.toList()) @@ -287,19 +301,17 @@ class TypeScriptEndpointProcessorSpec extends Specification { def folder = "/complex" def endpointSourceFile = new File(endpointsPath + folder + "/Endpoint.java") def simpleRootTypeSourceFile = getSourceFile("$folder/SimpleRootType.gstring", "$folder/SimpleRootType.java", [type: type]) - def destinationFolder = new File("$annotationsTarget/$folder") - Files.createDirectories(destinationFolder.toPath()) + def destinationFolder = initFolder folder when: "the Endpoint is compiled" - List> diagnostics = - CompilerTestHelper.compileTestCase(Arrays. asList(new TypeScriptEndpointProcessor()), folder, endpointSourceFile, simpleRootTypeSourceFile) + def diagnostics = CompilerTestHelper.compileTestCase([new TypeScriptEndpointProcessor()], folder, endpointSourceFile, simpleRootTypeSourceFile) then: "there should be no errors" diagnostics.every { d -> (Diagnostic.Kind.ERROR != d.kind) } and: "there should only be one declared typescript interface file" def allTSFiles = new ArrayList() - destinationFolder.eachFileMatch FileType.FILES, ~/.*\.model\.ts/, { file -> allTSFiles << file } + destinationFolder.eachFileMatch FILES, ~/.*\.model\.ts/, { file -> allTSFiles << file } allTSFiles.size() == 1 allTSFiles[0].name == "ISimpleRootType.model.ts" @@ -314,7 +326,7 @@ class TypeScriptEndpointProcessorSpec extends Specification { cleanup: "remove test java source file" simpleRootTypeSourceFile.delete() - destinationFolder.eachFile(FileType.FILES, { file -> file.delete() }) + destinationFolder.eachFile(FILES, { file -> file.delete() }) where: "possible simple values for type in SimpleRootType are" type || mappedType @@ -451,20 +463,17 @@ class TypeScriptEndpointProcessorSpec extends Specification { def folder = "/returnref" def endpointSourceFile = getSourceFile("$folder/Endpoint.gstring", "$folder/Endpoint.java", [returnType: returnType, returnValue: returnValue]) def simpleRootTypeSourceFile = new File(endpointsPath + folder + "/SimpleRootType.java") - - def destinationFolder = new File("$annotationsTarget/$folder") - Files.createDirectories(destinationFolder.toPath()) + def destinationFolder = initFolder folder when: "the Endpoint is compiled" - List> diagnostics = - CompilerTestHelper.compileTestCase(Arrays. asList(new TypeScriptEndpointProcessor()), folder, endpointSourceFile, simpleRootTypeSourceFile) + def diagnostics = CompilerTestHelper.compileTestCase([new TypeScriptEndpointProcessor()], folder, endpointSourceFile, simpleRootTypeSourceFile) then: "there should be no errors" diagnostics.every { d -> (Diagnostic.Kind.ERROR != d.kind) } and: "there should only be one declared typescript interface file" def allTSFiles = new ArrayList() - destinationFolder.eachFileMatch FileType.FILES, ~/.*\.model\.ts/, { file -> allTSFiles << file } + destinationFolder.eachFileMatch FILES, ~/.*\.model\.ts/, { file -> allTSFiles << file } allTSFiles.size() == 1 allTSFiles[0].name == "ISimpleRootType.model.ts" @@ -481,7 +490,7 @@ class TypeScriptEndpointProcessorSpec extends Specification { cleanup: "remove test java source file" endpointSourceFile.delete() - destinationFolder.eachFile(FileType.FILES, { file -> file.delete() }) + destinationFolder.eachFile(FILES, { file -> file.delete() }) where: "possible return values for type in List are" returnType | returnValue || targetType @@ -524,20 +533,17 @@ class TypeScriptEndpointProcessorSpec extends Specification { def folder = "/returnvoid" def endpointSourceFile = getSourceFile("$folder/Endpoint.gstring", "$folder/Endpoint.java", [paramType: paramType]) def simpleRootTypeSourceFile = new File(endpointsPath + folder + "/SimpleRootType.java") - - def destinationFolder = new File("$annotationsTarget/$folder") - Files.createDirectories(destinationFolder.toPath()) + def destinationFolder = initFolder folder when: "the Endpoint is compiled" - List> diagnostics = - CompilerTestHelper.compileTestCase(Arrays. asList(new TypeScriptEndpointProcessor()), folder, endpointSourceFile, simpleRootTypeSourceFile) + def diagnostics = CompilerTestHelper.compileTestCase([new TypeScriptEndpointProcessor()], folder, endpointSourceFile, simpleRootTypeSourceFile) then: "there should be no errors" diagnostics.every { d -> (Diagnostic.Kind.ERROR != d.kind) } and: "there should only be one declared typescript interface file" def allTSFiles = new ArrayList() - destinationFolder.eachFileMatch FileType.FILES, ~/.*\.model\.ts/, { file -> allTSFiles << file } + destinationFolder.eachFileMatch FILES, ~/.*\.model\.ts/, { file -> allTSFiles << file } allTSFiles.size() == 1 allTSFiles[0].name == "ISimpleRootType.model.ts" @@ -554,14 +560,14 @@ class TypeScriptEndpointProcessorSpec extends Specification { cleanup: "remove test java source file" endpointSourceFile.delete() - destinationFolder.eachFile(FileType.FILES, { file -> file.delete() }) + destinationFolder.eachFile(FILES, { file -> file.delete() }) - where: "possible paramType values are" + where: "possible requestBodyType values are" paramType || targetType "SimpleRootType" || "SimpleRootType" "SimpleRootType[]" || "SimpleRootType[]" - and: "possible paramType values for type in List are" + and: "possible requestBodyType values for type in List are" "java.util.List" || "SimpleRootType[]" "java.util.LinkedList" || "SimpleRootType[]" "java.util.ArrayList" || "SimpleRootType[]" @@ -572,12 +578,12 @@ class TypeScriptEndpointProcessorSpec extends Specification { "java.util.concurrent.BlockingQueue" || "SimpleRootType[]" "java.util.concurrent.BlockingDeque" || "SimpleRootType[]" - and: "possible paramType values for type in Set are" + and: "possible requestBodyType values for type in Set are" "java.util.Set" || "SimpleRootType[]" "java.util.TreeSet" || "SimpleRootType[]" "java.util.HashSet" || "SimpleRootType[]" - and: "possible paramType values for type in Map are" + and: "possible requestBodyType values for type in Map are" "java.util.Map" || "{ [index: SimpleRootType]: any }" "java.util.Map" || "{ [index: any]: SimpleRootType }" "java.util.HashMap" || "{ [index: SimpleRootType]: any }" @@ -597,20 +603,16 @@ class TypeScriptEndpointProcessorSpec extends Specification { given: "an Endpoint with a composed Annotation" def folder = "/composed" def endpointSourceFile = getSourceFile("$folder/Endpoint.gstring", "$folder/Endpoint.java", [annotation: annotation]) - - def destinationFolder = new File("$annotationsTarget/$folder") - Files.createDirectories(destinationFolder.toPath()) + def destinationFolder = initFolder folder when: "the Endpoint is compiled" - List> diagnostics = - CompilerTestHelper.compileTestCase(Arrays. asList(new TypeScriptEndpointProcessor()), folder, endpointSourceFile) + def diagnostics = CompilerTestHelper.compileTestCase([new TypeScriptEndpointProcessor()], folder, endpointSourceFile) then: "there should be no errors" diagnostics.every { d -> (Diagnostic.Kind.ERROR != d.kind) } and: "there must be no declared typescript interface file" - def allTSFiles = new ArrayList() - destinationFolder.eachFileMatch FileType.FILES, ~/.*\.model\.ts/, { file -> allTSFiles << file } + def allTSFiles = getInterfaceFiles(destinationFolder) allTSFiles.size() == 0 and: "the scanned model should contain only the httpmethod" @@ -625,9 +627,9 @@ class TypeScriptEndpointProcessorSpec extends Specification { cleanup: "remove test java source file" endpointSourceFile.delete() - destinationFolder.eachFile(FileType.FILES, { file -> file.delete() }) + destinationFolder.eachFile(FILES, { file -> file.delete() }) - where: "possible paramType values are" + where: "possible requestBodyType values are" annotation || targetType "@GetMapping" || "SimpleRootType" "@PutMapping" || "SimpleRootType" @@ -644,4 +646,20 @@ class TypeScriptEndpointProcessorSpec extends Specification { sourceFile.write(new SimpleTemplateEngine().createTemplate(text).make(variables).toString(), 'UTF8') return sourceFile } + + def getInterfaceFiles(File destinationFolder) { + def allTSFiles = new ArrayList() + destinationFolder.eachFileRecurse(FILES) { + if(it.name.endsWith('.model.ts')) { + allTSFiles << it + } + } + return allTSFiles + } + + def initFolder(String folder) { + def destinationFolder = new File("$annotationsTarget/$folder") + Files.createDirectories(destinationFolder.toPath()) + return destinationFolder; + } } \ No newline at end of file diff --git a/annotations/src/test/resources/org/leandreck/endpoints/templates/testing/service.ftl b/annotations/src/test/resources/org/leandreck/endpoints/templates/testing/service.ftl index 576646a..72c4ecf 100644 --- a/annotations/src/test/resources/org/leandreck/endpoints/templates/testing/service.ftl +++ b/annotations/src/test/resources/org/leandreck/endpoints/templates/testing/service.ftl @@ -51,6 +51,9 @@ "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], "returnType": "${method.returnType.type}" + <#if method.requestBodyType??>, + "requestBodyType": "${method.requestBodyType.type}" + }<#sep>, ], @@ -81,6 +84,9 @@ "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], "returnType": "${method.returnType.type}" + <#if method.requestBodyType??>, + "requestBodyType": "${method.requestBodyType.type}" + }<#sep>, ], @@ -91,6 +97,9 @@ "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], "returnType": "${method.returnType.type}" + <#if method.requestBodyType??>, + "requestBodyType": "${method.requestBodyType.type}" + }<#sep>, ], @@ -101,6 +110,9 @@ "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], "returnType": "${method.returnType.type}" + <#if method.requestBodyType??>, + "requestBodyType": "${method.requestBodyType.type}" + }<#sep>, ], @@ -111,6 +123,9 @@ "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], "returnType": "${method.returnType.type}" + <#if method.requestBodyType??>, + "requestBodyType": "${method.requestBodyType.type}" + }<#sep>, ], @@ -121,6 +136,9 @@ "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], "returnType": "${method.returnType.type}" + <#if method.requestBodyType??>, + "requestBodyType": "${method.requestBodyType.type}" + }<#sep>, ], @@ -131,6 +149,9 @@ "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], "returnType": "${method.returnType.type}" + <#if method.requestBodyType??>, + "requestBodyType": "${method.requestBodyType.type}" + }<#sep>, ] diff --git a/annotations/src/test/testcases/org/leandreck/endpoints/httpmethods/Endpoint.gstring b/annotations/src/test/testcases/org/leandreck/endpoints/httpmethods/Endpoint.gstring index 5968fe2..9934f2b 100644 --- a/annotations/src/test/testcases/org/leandreck/endpoints/httpmethods/Endpoint.gstring +++ b/annotations/src/test/testcases/org/leandreck/endpoints/httpmethods/Endpoint.gstring @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -""" + package org.leandreck.endpoints.httpmethods; import org.leandreck.endpoints.annotations.TypeScriptEndpoint; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import static org.springframework.web.bind.annotation.RequestMethod.*; @@ -30,7 +29,6 @@ import static org.springframework.web.bind.annotation.RequestMethod.*; public class $httpMethod { @RequestMapping(value = "/int", method = $httpMethod, produces = MediaType.APPLICATION_JSON_VALUE) - public @ResponseBody - void getInt() { + public void getInt() { } -}""" \ No newline at end of file +} \ No newline at end of file diff --git a/annotations/src/test/testcases/org/leandreck/endpoints/pathvariable/Endpoint.gstring b/annotations/src/test/testcases/org/leandreck/endpoints/pathvariable/Endpoint.gstring new file mode 100644 index 0000000..7f57821 --- /dev/null +++ b/annotations/src/test/testcases/org/leandreck/endpoints/pathvariable/Endpoint.gstring @@ -0,0 +1,36 @@ +/** + * Copyright © 2016 Mathias Kowalzik (Mathias.Kowalzik@leandreck.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.leandreck.endpoints.pathvariable; + +import static org.springframework.web.bind.annotation.RequestMethod.*; + +import org.leandreck.endpoints.annotations.TypeScriptEndpoint; +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@TypeScriptEndpoint(template = "/org/leandreck/endpoints/templates/testing/service.ftl") +@RestController +@RequestMapping("/api") +public class $httpMethod { + + @RequestMapping(value = "/int/{value}", method = $httpMethod, produces = MediaType.APPLICATION_JSON_VALUE) + public int getInt(@PathVariable int value) { + return value; + } +} \ No newline at end of file From a7b2c079a7f028c9747c3a683b81ca460a05bb48 Mon Sep 17 00:00:00 2001 From: Mathias Kowalzik Date: Wed, 19 Oct 2016 00:10:21 +0200 Subject: [PATCH 2/6] fixed license header --- .../leandreck/endpoints/processor/model/MethodNode.java | 8 ++++---- .../org/leandreck/endpoints/processor/model/TypeNode.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNode.java b/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNode.java index 79a4374..b8a2125 100644 --- a/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNode.java +++ b/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNode.java @@ -1,12 +1,12 @@ /** * Copyright © 2016 Mathias Kowalzik (Mathias.Kowalzik@leandreck.org) - *

+ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

+ * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNode.java b/annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNode.java index 47db0ff..6fad6c5 100644 --- a/annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNode.java +++ b/annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNode.java @@ -1,12 +1,12 @@ /** * Copyright © 2016 Mathias Kowalzik (Mathias.Kowalzik@leandreck.org) - *

+ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

+ * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. From 52284530deaafb834efe298f6fd393adf194c9dc Mon Sep 17 00:00:00 2001 From: Mathias Kowalzik Date: Thu, 20 Oct 2016 23:09:50 +0200 Subject: [PATCH 3/6] added pathvariables to model an ftl --- README.md | 58 +++++--- .../endpoints/processor/model/MethodNode.java | 6 +- .../processor/model/MethodNodeFactory.java | 45 +++++-- .../templates/typescript/service.ftl | 67 ++++++---- .../TypeScriptEndpointProcessorSpec.groovy | 88 ++++++++++++- .../endpoints/templates/testing/service.ftl | 124 ++++++++++++++---- .../endpoints/pathvariable/Endpoint.gstring | 6 +- .../examples/TestTypeScriptEndpoint.java | 27 ++-- 8 files changed, 324 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 6091572..f18d0e2 100644 --- a/README.md +++ b/README.md @@ -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 getPersons() { - final List 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 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 { + let url = this.serviceBaseURL + '/type/' + id + '/' + typeRef + ''; + return this.httpPost(url, body) + .map((response: Response) => response.json()) + .catch((error: Response) => this.handleError(error)); + } + private httpPost(url: string, body: any): Observable { + console.info('httpPost: ' + url); + return this.http.post(url, body); } +} ``` [freemarker]: http://freemarker.org/ diff --git a/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNode.java b/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNode.java index b8a2125..99ee3dc 100644 --- a/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNode.java +++ b/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNode.java @@ -43,13 +43,13 @@ public MethodNode(final String name, final String url, final boolean ignored, fi } public MethodNode(final String name, final String url, final boolean ignored, final List httpMethods, - final TypeNode returnType, final TypeNode paramType, final List pathVariableTypes) { + final TypeNode returnType, final TypeNode requestBodyType, final List pathVariableTypes) { this.name = name; - this.url = url; this.ignored = ignored; + this.url = url; this.returnType = returnType; this.httpMethods = httpMethods; - this.requestBodyType = paramType; + this.requestBodyType = requestBodyType; this.pathVariableTypes = pathVariableTypes; this.types = collectTypes(); } diff --git a/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNodeFactory.java b/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNodeFactory.java index 74b87d2..07cacdb 100644 --- a/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNodeFactory.java +++ b/annotations/src/main/java/org/leandreck/endpoints/processor/model/MethodNodeFactory.java @@ -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; @@ -26,9 +28,10 @@ import javax.lang.model.util.Types; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; +import java.util.Optional; + +import static java.util.stream.Collectors.toList; /** * Created by Mathias Kowalzik (Mathias.Kowalzik@leandreck.org) on 28.08.2016. @@ -52,21 +55,41 @@ public MethodNode createMethodNode(final ExecutableElement methodElement) { return new MethodNode(name, "", true, null, null); } final String url = defineUrl(requestMapping); - final List httpMethods = defineHttpMethods(requestMapping); + final TypeNode returnType = defineReturnType(methodElement); + + final List parameters = methodElement.getParameters(); + final TypeNode requestBodyType = defineRequestBodyType(parameters); + final List 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 definePathVariableTypes(final List parameters) { + return parameters.stream() + .filter(p -> p.getAnnotation(PathVariable.class) != null) + .map(p -> typeNodeFactory.createTypeNode(p)) + .collect(toList()); + } - final TypeNode paramType; //FIXME define real requestBodyParam - if (methodElement.getParameters().isEmpty()) { - paramType = null; + private TypeNode defineRequestBodyType(final List parameters) { + final Optional 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, Collections.emptyList()); //FIXME define real PathVariables + return requestBodyType; } private static List defineHttpMethods(final RequestMapping requestMapping) { @@ -75,7 +98,7 @@ private static List defineHttpMethods(final RequestMapping requestMappin if (requestMapping != null) { return Arrays.stream(requestMapping.method()) .map(requestMethod -> requestMethod.toString().toLowerCase()) - .collect(Collectors.toList()); + .collect(toList()); } return methods; diff --git a/annotations/src/main/resources/org/leandreck/endpoints/templates/typescript/service.ftl b/annotations/src/main/resources/org/leandreck/endpoints/templates/typescript/service.ftl index e2fda7a..88ad0c1 100644 --- a/annotations/src/main/resources/org/leandreck/endpoints/templates/typescript/service.ftl +++ b/annotations/src/main/resources/org/leandreck/endpoints/templates/typescript/service.ftl @@ -16,30 +16,39 @@ --> <#-- @ftlvariable name="" type="org.leandreck.endpoints.processor.model.EndpointNode" --> +<#function buildUrl variables url> + <#assign result = url> + <#list variables as item> + '/api/{value}/{some}' + '/api/' + value + '/' + some + '' + <#assign result = result?replace('{', '')> + + <#return result> + <#list types as type> import { ${type.typeName} } from './${type.typeName}.model'; -import {Http, Response, RequestOptions, Headers, RequestOptionsArgs} from "@angular/http"; +import { Http, Response, RequestOptions, Headers, RequestOptionsArgs } from "@angular/http"; import { Injectable } from '@angular/core'; -import {Observable} from "rxjs/Observable"; -import {ErrorObservable} from "rxjs/observable/ErrorObservable"; +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 ${serviceName} { - private serviceBaseURL = '${serviceURL}' - constructor(private http: Http) { } - /* GET */ <#list getGetMethods() as method> - public ${method.name}Get(): Observable<${method.returnType.type}> { - let url = this.serviceBaseURL + '${method.url}'; + <#assign expandedURL = method.url?replace('{', '\' + ')> + <#assign expandedURL = expandedURL?replace('}', ' + \'')> + public ${method.name}Get(<#list method.pathVariableTypes as variable>${variable.fieldName}: ${variable.type}<#sep>, ): Observable<${method.returnType.type}> { + let url = this.serviceBaseURL + '${expandedURL}'; + return this.httpGet(url) .map((response: Response) => <${method.returnType.type}>response.json()) .catch((error: Response) => this.handleError(error)); @@ -55,8 +64,10 @@ export class ${serviceName} { /* HEAD */ <#list getHeadMethods() as method> - public ${method.name}Head(): Observable { - let url = this.serviceBaseURL + '${method.url}'; + <#assign expandedURL = method.url?replace('{', '\' + ')> + <#assign expandedURL = expandedURL?replace('}', ' + \'')> + public ${method.name}Head(<#list method.pathVariableTypes as variable>${variable.fieldName}: ${variable.type}<#sep>, ): Observable { + let url = this.serviceBaseURL + '${expandedURL}'; return this.httpHead(url) .catch((error: Response) => this.handleError(error)); } @@ -71,8 +82,10 @@ export class ${serviceName} { /* POST */ <#list getPostMethods() as method> - public ${method.name}Post(${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable<${method.returnType.type}> { - let url = this.serviceBaseURL + '${method.url}'; + <#assign expandedURL = method.url?replace('{', '\' + ')> + <#assign expandedURL = expandedURL?replace('}', ' + \'')> + public ${method.name}Post(<#list method.pathVariableTypes as variable>${variable.fieldName}: ${variable.type}<#sep>, <#if method.pathVariableTypes?size gt 0>, ${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable<${method.returnType.type}> { + let url = this.serviceBaseURL + '${expandedURL}'; return this.httpPost(url, ${method.requestBodyType.fieldName}) .map((response: Response) => <${method.returnType.type}>response.json()) .catch((error: Response) => this.handleError(error)); @@ -88,8 +101,10 @@ export class ${serviceName} { /* PUT */ <#list getPutMethods() as method> - public ${method.name}Put(${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable<${method.returnType.type}> { - let url = this.serviceBaseURL + '${method.url}'; + <#assign expandedURL = method.url?replace('{', '\' + ')> + <#assign expandedURL = expandedURL?replace('}', ' + \'')> + public ${method.name}Put(<#list method.pathVariableTypes as variable>${variable.fieldName}: ${variable.type}<#sep>, <#if method.pathVariableTypes?size gt 0>, ${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable<${method.returnType.type}> { + let url = this.serviceBaseURL + '${expandedURL}'; return this.httpPut(url, ${method.requestBodyType.fieldName}) .map((response: Response) => <${method.returnType.type}>response.json()) .catch((error: Response) => this.handleError(error)); @@ -105,8 +120,10 @@ export class ${serviceName} { /* PATCH */ <#list getPatchMethods() as method> - public ${method.name}Patch(${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable<${method.returnType.type}> { - let url = this.serviceBaseURL + '${method.url}'; + <#assign expandedURL = method.url?replace('{', '\' + ')> + <#assign expandedURL = expandedURL?replace('}', ' + \'')> + public ${method.name}Patch(<#list method.pathVariableTypes as variable>${variable.fieldName}: ${variable.type}<#sep>, <#if method.pathVariableTypes?size gt 0>, ${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable<${method.returnType.type}> { + let url = this.serviceBaseURL + '${expandedURL}'; return this.httpPatch(url, ${method.requestBodyType.fieldName}) .map((response: Response) => <${method.returnType.type}>response.json()) .catch((error: Response) => this.handleError(error)); @@ -122,8 +139,10 @@ export class ${serviceName} { /* DELETE */ <#list getDeleteMethods() as method> - public ${method.name}Delete(): Observable { - let url = this.serviceBaseURL + '${method.url}'; + <#assign expandedURL = method.url?replace('{', '\' + ')> + <#assign expandedURL = expandedURL?replace('}', ' + \'')> + public ${method.name}Delete(<#list method.pathVariableTypes as variable>${variable.fieldName}: ${variable.type}<#sep>, ): Observable { + let url = this.serviceBaseURL + '${expandedURL}'; return this.httpDelete(url) .catch((error: Response) => this.handleError(error)); } @@ -138,8 +157,10 @@ export class ${serviceName} { /* OPTIONS */ <#list getOptionsMethods() as method> - public ${method.name}Options(${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable { - let url = this.serviceBaseURL + '${method.url}'; + <#assign expandedURL = method.url?replace('{', '\' + ')> + <#assign expandedURL = expandedURL?replace('}', ' + \'')> + public ${method.name}Options(<#list method.pathVariableTypes as variable>${variable.fieldName}: ${variable.type}<#sep>, <#if method.pathVariableTypes?size gt 0>, ${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable { + let url = this.serviceBaseURL + '${expandedURL}'; return this.httpOptions(url) .catch((error: Response) => this.handleError(error)); } @@ -154,8 +175,10 @@ export class ${serviceName} { /* TRACE */ <#list getTraceMethods() as method> - public ${method.name}Trace(${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable<${method.requestBodyType.type}> { - let url = this.serviceBaseURL + '${method.url}'; + <#assign expandedURL = method.url?replace('{', '\' + ')> + <#assign expandedURL = expandedURL?replace('}', ' + \'')> + public ${method.name}Trace(<#list method.pathVariableTypes as variable>${variable.fieldName}: ${variable.type}<#sep>, <#if method.pathVariableTypes?size gt 0>, ${method.requestBodyType.fieldName}: ${method.requestBodyType.type}): Observable<${method.requestBodyType.type}> { + let url = this.serviceBaseURL + '${expandedURL}'; return this.httpTrace(url, ${method.requestBodyType.fieldName}) .map((response: Response) => <${method.returnType.type}>response.json()) .catch((error: Response) => this.handleError(error)); diff --git a/annotations/src/test/groovy/org/leandreck/endpoints/processor/TypeScriptEndpointProcessorSpec.groovy b/annotations/src/test/groovy/org/leandreck/endpoints/processor/TypeScriptEndpointProcessorSpec.groovy index 1088323..7f4a4cb 100644 --- a/annotations/src/test/groovy/org/leandreck/endpoints/processor/TypeScriptEndpointProcessorSpec.groovy +++ b/annotations/src/test/groovy/org/leandreck/endpoints/processor/TypeScriptEndpointProcessorSpec.groovy @@ -15,15 +15,12 @@ */ package org.leandreck.endpoints.processor -import groovy.io.FileType import groovy.json.JsonSlurper import groovy.text.SimpleTemplateEngine import org.springframework.web.bind.annotation.RequestMethod import spock.lang.* -import javax.annotation.processing.Processor import javax.tools.Diagnostic -import javax.tools.JavaFileObject import java.nio.file.Files import java.util.stream.Collectors @@ -598,6 +595,89 @@ class TypeScriptEndpointProcessorSpec extends Specification { "java.util.WeakHashMap" || "{ [index: any]: SimpleRootType }" } + @Unroll + def "each PathVariable type #type should create an input parameter for each #httpMethod-Entry"() { + given: "an Endpoint with a Method with @PatVariable" + def folder = "/pathvariable" + def endpointSourceFile = getSourceFile("$folder/Endpoint.gstring", "$folder/${httpMethod}.java", [type: type, httpMethod: httpMethod, mappedType: mappedType]) + def destinationFolder = initFolder folder + + when: "the Endpoint is compiled" + def diagnostics = CompilerTestHelper.compileTestCase([new TypeScriptEndpointProcessor()], folder, endpointSourceFile) + + then: "there should be no errors" + diagnostics.every { d -> (Diagnostic.Kind.ERROR != d.kind) } + + and: "there must be no declared typescript interface file" + def allTSFiles = getInterfaceFiles(destinationFolder) + allTSFiles.size() == 0 + + and: "the scanned model should contain only the httpmethod with pathvariable" + def model = jsonSlurper.parse(new File("$annotationsTarget${folder}/${httpMethod}.ts")) + def method = httpMethod.toLowerCase() + with(model) { + serviceName == "${httpMethod}" + serviceUrl == "/api" + methodCount == 1 + getProperty("${method}MethodCount") == 1 + with(getProperty("${method}Methods")[0]) { + name == "getInt" + url == "/{value}" + httpMethods == ["$method"] + returnType == "$mappedType" + pathVariableTypes[0].fieldName == "value" + pathVariableTypes[0].type == "$mappedType" + } + } + + cleanup: "remove test java source file" + endpointSourceFile.delete() + destinationFolder.eachFile(FILES, { file -> file.delete() }) + + where: "possible values for type and httpmethod are" + type | httpMethod || mappedType + "int" | "GET" || "number" + "String" | "GET" || "string" + "java.util.Date" | "GET" || "Date" + "java.time.LocalDate" | "GET" || "Date" + + "int" | "HEAD" || "number" + "String" | "HEAD" || "string" + "java.util.Date" | "HEAD" || "Date" + "java.time.LocalDate" | "HEAD" || "Date" + + "int" | "POST" || "number" + "String" | "POST" || "string" + "java.util.Date" | "POST" || "Date" + "java.time.LocalDate" | "POST" || "Date" + + "int" | "PUT" || "number" + "String" | "PUT" || "string" + "java.util.Date" | "PUT" || "Date" + "java.time.LocalDate" | "PUT" || "Date" + + "int" | "PATCH" || "number" + "String" | "PATCH" || "string" + "java.util.Date" | "PATCH" || "Date" + "java.time.LocalDate" | "PATCH" || "Date" + + "int" | "DELETE" || "number" + "String" | "DELETE" || "string" + "java.util.Date" | "DELETE" || "Date" + "java.time.LocalDate" | "DELETE" || "Date" + + "int" | "OPTIONS" || "number" + "String" | "OPTIONS" || "string" + "java.util.Date" | "OPTIONS" || "Date" + "java.time.LocalDate" | "OPTIONS" || "Date" + + "int" | "TRACE" || "number" + "String" | "TRACE" || "string" + "java.util.Date" | "TRACE" || "Date" + "java.time.LocalDate" | "TRACE" || "Date" + + } + @Unroll def "each composed #annotation results in a specific httpMethod-Entry"() { given: "an Endpoint with a composed Annotation" @@ -650,7 +730,7 @@ class TypeScriptEndpointProcessorSpec extends Specification { def getInterfaceFiles(File destinationFolder) { def allTSFiles = new ArrayList() destinationFolder.eachFileRecurse(FILES) { - if(it.name.endsWith('.model.ts')) { + if (it.name.endsWith('.model.ts')) { allTSFiles << it } } diff --git a/annotations/src/test/resources/org/leandreck/endpoints/templates/testing/service.ftl b/annotations/src/test/resources/org/leandreck/endpoints/templates/testing/service.ftl index 72c4ecf..345fc02 100644 --- a/annotations/src/test/resources/org/leandreck/endpoints/templates/testing/service.ftl +++ b/annotations/src/test/resources/org/leandreck/endpoints/templates/testing/service.ftl @@ -50,10 +50,18 @@ "name": "${method.name}", "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], - "returnType": "${method.returnType.type}" - <#if method.requestBodyType??>, - "requestBodyType": "${method.requestBodyType.type}" + "returnType": "${method.returnType.type}", + <#if method.requestBodyType??> + "requestBodyType": "${method.requestBodyType.type}", + "pathVariableTypes": [ + <#list method.pathVariableTypes as pathVariable> + { + "fieldName": "${pathVariable.fieldName}", + "type": "${pathVariable.type}" + }<#sep>, + + ] }<#sep>, ], @@ -63,7 +71,18 @@ "name": "${method.name}", "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], - "returnType": "${method.returnType.type}" + "returnType": "${method.returnType.type}", + <#if method.requestBodyType??> + "requestBodyType": "${method.requestBodyType.type}", + + "pathVariableTypes": [ + <#list method.pathVariableTypes as pathVariable> + { + "fieldName": "${pathVariable.fieldName}", + "type": "${pathVariable.type}" + }<#sep>, + + ] }<#sep>, ], @@ -73,7 +92,18 @@ "name": "${method.name}", "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], - "returnType": "${method.returnType.type}" + "returnType": "${method.returnType.type}", + <#if method.requestBodyType??> + "requestBodyType": "${method.requestBodyType.type}", + + "pathVariableTypes": [ + <#list method.pathVariableTypes as pathVariable> + { + "fieldName": "${pathVariable.fieldName}", + "type": "${pathVariable.type}" + }<#sep>, + + ] }<#sep>, ], @@ -83,10 +113,18 @@ "name": "${method.name}", "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], - "returnType": "${method.returnType.type}" - <#if method.requestBodyType??>, - "requestBodyType": "${method.requestBodyType.type}" + "returnType": "${method.returnType.type}", + <#if method.requestBodyType??> + "requestBodyType": "${method.requestBodyType.type}", + "pathVariableTypes": [ + <#list method.pathVariableTypes as pathVariable> + { + "fieldName": "${pathVariable.fieldName}", + "type": "${pathVariable.type}" + }<#sep>, + + ] }<#sep>, ], @@ -96,10 +134,18 @@ "name": "${method.name}", "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], - "returnType": "${method.returnType.type}" - <#if method.requestBodyType??>, - "requestBodyType": "${method.requestBodyType.type}" + "returnType": "${method.returnType.type}", + <#if method.requestBodyType??> + "requestBodyType": "${method.requestBodyType.type}", + "pathVariableTypes": [ + <#list method.pathVariableTypes as pathVariable> + { + "fieldName": "${pathVariable.fieldName}", + "type": "${pathVariable.type}" + }<#sep>, + + ] }<#sep>, ], @@ -109,10 +155,18 @@ "name": "${method.name}", "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], - "returnType": "${method.returnType.type}" - <#if method.requestBodyType??>, - "requestBodyType": "${method.requestBodyType.type}" + "returnType": "${method.returnType.type}", + <#if method.requestBodyType??> + "requestBodyType": "${method.requestBodyType.type}", + "pathVariableTypes": [ + <#list method.pathVariableTypes as pathVariable> + { + "fieldName": "${pathVariable.fieldName}", + "type": "${pathVariable.type}" + }<#sep>, + + ] }<#sep>, ], @@ -122,10 +176,18 @@ "name": "${method.name}", "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], - "returnType": "${method.returnType.type}" - <#if method.requestBodyType??>, - "requestBodyType": "${method.requestBodyType.type}" + "returnType": "${method.returnType.type}", + <#if method.requestBodyType??> + "requestBodyType": "${method.requestBodyType.type}", + "pathVariableTypes": [ + <#list method.pathVariableTypes as pathVariable> + { + "fieldName": "${pathVariable.fieldName}", + "type": "${pathVariable.type}" + }<#sep>, + + ] }<#sep>, ], @@ -135,10 +197,18 @@ "name": "${method.name}", "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], - "returnType": "${method.returnType.type}" - <#if method.requestBodyType??>, - "requestBodyType": "${method.requestBodyType.type}" + "returnType": "${method.returnType.type}", + <#if method.requestBodyType??> + "requestBodyType": "${method.requestBodyType.type}", + "pathVariableTypes": [ + <#list method.pathVariableTypes as pathVariable> + { + "fieldName": "${pathVariable.fieldName}", + "type": "${pathVariable.type}" + }<#sep>, + + ] }<#sep>, ], @@ -148,10 +218,18 @@ "name": "${method.name}", "url": "${method.url}", "httpMethods": ["${method.httpMethods?join("\", \"")}"], - "returnType": "${method.returnType.type}" - <#if method.requestBodyType??>, - "requestBodyType": "${method.requestBodyType.type}" + "returnType": "${method.returnType.type}", + <#if method.requestBodyType??> + "requestBodyType": "${method.requestBodyType.type}", + "pathVariableTypes": [ + <#list method.pathVariableTypes as pathVariable> + { + "fieldName": "${pathVariable.fieldName}", + "type": "${pathVariable.type}" + }<#sep>, + + ] }<#sep>, ] diff --git a/annotations/src/test/testcases/org/leandreck/endpoints/pathvariable/Endpoint.gstring b/annotations/src/test/testcases/org/leandreck/endpoints/pathvariable/Endpoint.gstring index 7f57821..7898699 100644 --- a/annotations/src/test/testcases/org/leandreck/endpoints/pathvariable/Endpoint.gstring +++ b/annotations/src/test/testcases/org/leandreck/endpoints/pathvariable/Endpoint.gstring @@ -19,7 +19,7 @@ package org.leandreck.endpoints.pathvariable; import static org.springframework.web.bind.annotation.RequestMethod.*; import org.leandreck.endpoints.annotations.TypeScriptEndpoint; -import org.springframework.http.MediaType +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -29,8 +29,8 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/api") public class $httpMethod { - @RequestMapping(value = "/int/{value}", method = $httpMethod, produces = MediaType.APPLICATION_JSON_VALUE) - public int getInt(@PathVariable int value) { + @RequestMapping(value = "/{value}", method = $httpMethod, produces = MediaType.APPLICATION_JSON_VALUE) + public $type getInt(@PathVariable $type value) { return value; } } \ No newline at end of file diff --git a/examples/src/main/java/org/leandreck/endpoints/examples/TestTypeScriptEndpoint.java b/examples/src/main/java/org/leandreck/endpoints/examples/TestTypeScriptEndpoint.java index 099f43a..c218ba5 100644 --- a/examples/src/main/java/org/leandreck/endpoints/examples/TestTypeScriptEndpoint.java +++ b/examples/src/main/java/org/leandreck/endpoints/examples/TestTypeScriptEndpoint.java @@ -26,11 +26,10 @@ import org.springframework.web.bind.annotation.RestController; import java.io.ByteArrayInputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.IMAGE_PNG; import static org.springframework.web.bind.annotation.RequestMethod.*; /** @@ -41,25 +40,31 @@ @RequestMapping("/api") public class TestTypeScriptEndpoint { - @RequestMapping(value = "/persons", method = GET, produces = MediaType.APPLICATION_JSON_VALUE) + @RequestMapping(value = "/type/{id}/{typeRef}", method = POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) + public List setId(@PathVariable Long id, @RequestBody SubType body) { + // do something + return Collections.singletonList(body); + } + + @RequestMapping(value = "/persons", method = GET, produces = APPLICATION_JSON_VALUE) public List getPersons() { final List rootTypes = new ArrayList<>(); rootTypes.add(new RootType()); return rootTypes; } - @RequestMapping(value = "/person/{id}", method = GET, produces = MediaType.APPLICATION_JSON_VALUE) - public RootType getPerson(@PathVariable Long id) { + @RequestMapping(value = "/person/{id}/{typeRef}", method = GET, produces = APPLICATION_JSON_VALUE) + public RootType getPerson(@PathVariable Long id, @PathVariable String typeRef) { return new RootType(id); } - @RequestMapping(value = "/person/{id}", method = POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @RequestMapping(value = "/person/{id}", method = POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) public RootType updatePerson(@PathVariable Long id, @RequestBody RootType rootType) { rootType.setId(id); return rootType; } - @RequestMapping(value = "/maps", method = GET, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @RequestMapping(value = "/maps", method = GET, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) public Map maps() { final Map map = new HashMap<>(); map.put("one", new RootType()); @@ -72,7 +77,7 @@ public ResponseEntity getPhoto(@PathVariable Long id) { return ResponseEntity .ok() .contentLength(0) - .contentType(MediaType.IMAGE_PNG) + .contentType(IMAGE_PNG) .body(new InputStreamResource(new ByteArrayInputStream("No Content".getBytes()))); } @@ -88,7 +93,7 @@ List ignoredAlso() { return null; } - @RequestMapping(value = "/subType", method = {HEAD, PUT, PATCH, DELETE, OPTIONS, TRACE}, produces = MediaType.APPLICATION_JSON_VALUE) + @RequestMapping(value = "/subType", method = {HEAD, PUT, PATCH, DELETE, OPTIONS, TRACE}, produces = APPLICATION_JSON_VALUE) public SubType handleSubType(@RequestBody final SubType param) { return param; } From 18771a83507d3ef02fb830206de6a5e9ff448d8b Mon Sep 17 00:00:00 2001 From: Mathias Kowalzik Date: Fri, 21 Oct 2016 00:07:27 +0200 Subject: [PATCH 4/6] added Test for custom Endpoint name --- .../TypeScriptEndpointProcessorSpec.groovy | 25 ++++++++++++++ .../leandreck/endpoints/epname/Endpoint.java | 33 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 annotations/src/test/testcases/org/leandreck/endpoints/epname/Endpoint.java diff --git a/annotations/src/test/groovy/org/leandreck/endpoints/processor/TypeScriptEndpointProcessorSpec.groovy b/annotations/src/test/groovy/org/leandreck/endpoints/processor/TypeScriptEndpointProcessorSpec.groovy index 7f4a4cb..b51f08e 100644 --- a/annotations/src/test/groovy/org/leandreck/endpoints/processor/TypeScriptEndpointProcessorSpec.groovy +++ b/annotations/src/test/groovy/org/leandreck/endpoints/processor/TypeScriptEndpointProcessorSpec.groovy @@ -718,6 +718,31 @@ class TypeScriptEndpointProcessorSpec extends Specification { "@PatchMapping" || "SimpleRootType" } + def "the custom endpoint name is independet of the Classname "() { + given: "an Endpoint with a custom name in @TypeScriptEndpoint" + def folder = "/epname" + def sourceFile = new File("$endpointsPath/$folder/Endpoint.java") + def destinationFolder = initFolder folder + + when: "the Endpoint is compiled" + def diagnostics = CompilerTestHelper.compileTestCase([new TypeScriptEndpointProcessor()], folder, sourceFile) + + then: "there should be no errors" + diagnostics.every { d -> (Diagnostic.Kind.ERROR != d.kind) } + + and: "there must be no declared typescript interface file" + def allTSFiles = getInterfaceFiles(destinationFolder) + allTSFiles.size() == 0 + + and: "there must be a ts file with the custom name" + destinationFolder.listFiles().length == 1 + destinationFolder.eachFile { f -> f.name == "CustomName.ts" } + + cleanup: "remove test java source file" + // Do not delete the source files: sourceFile.delete(), because they are not generated in this testcase! + destinationFolder.deleteDir() + } + def getSourceFile(inputFilePath, outputFilePath, Map variables) { def text = new File("$endpointsPath/$inputFilePath").getText("utf-8") def sourceFile = new File("$endpointsPath/$outputFilePath") diff --git a/annotations/src/test/testcases/org/leandreck/endpoints/epname/Endpoint.java b/annotations/src/test/testcases/org/leandreck/endpoints/epname/Endpoint.java new file mode 100644 index 0000000..aece518 --- /dev/null +++ b/annotations/src/test/testcases/org/leandreck/endpoints/epname/Endpoint.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016 Mathias Kowalzik (Mathias.Kowalzik@leandreck.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.leandreck.endpoints.epname; + +import org.leandreck.endpoints.annotations.TypeScriptEndpoint; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +@TypeScriptEndpoint(template = "/org/leandreck/endpoints/templates/testing/service.ftl", value = "CustomName") +@RestController +public class Endpoint { + + @RequestMapping(value = "/int", method = GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public String getInt() { + return "Some"; + } +} \ No newline at end of file From a520b4273c33dfcd5c78ed536ab4a46b2489f59c Mon Sep 17 00:00:00 2001 From: Mathias Kowalzik Date: Fri, 21 Oct 2016 00:24:56 +0200 Subject: [PATCH 5/6] added Test for RequestMapping.groovy --- .../processor/model/RequestMappingTest.groovy | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 annotations/src/test/groovy/org/leandreck/endpoints/processor/model/RequestMappingTest.groovy diff --git a/annotations/src/test/groovy/org/leandreck/endpoints/processor/model/RequestMappingTest.groovy b/annotations/src/test/groovy/org/leandreck/endpoints/processor/model/RequestMappingTest.groovy new file mode 100644 index 0000000..18b65f1 --- /dev/null +++ b/annotations/src/test/groovy/org/leandreck/endpoints/processor/model/RequestMappingTest.groovy @@ -0,0 +1,56 @@ +package org.leandreck.endpoints.processor.model + +import org.springframework.web.bind.annotation.RequestMethod +import spock.lang.Specification +import spock.lang.Unroll + +/** + * Created by kowalzik on 21.10.2016. + */ +class RequestMappingTest extends Specification { + + @Unroll + def "Method is always an Array #methods"() { + given: "a RequestMapping" + def reqMapping = new RequestMapping(methods, null, null) + + when: "method is called" + def retVal = reqMapping.method() + + then: "retVal should be a Collection" + retVal instanceof RequestMethod[] + + where: "possible values for methods are" + methods << [[RequestMethod.GET], null] + } + + @Unroll + def "Produces is always an Array #produces"() { + given: "a RequestMapping" + def reqMapping = new RequestMapping(null, produces, null) + + when: "method is called" + def retVal = reqMapping.produces() + + then: "retVal should be a Collection" + retVal instanceof String[] + + where: "possible values for produces are" + produces << [["some"], null] + } + + @Unroll + def "Value is always an Array #value"() { + given: "a RequestMapping" + def reqMapping = new RequestMapping(null, null, value) + + when: "method is called" + def retVal = reqMapping.value() + + then: "retVal should be a Collection" + retVal instanceof String[] + + where: "possible values for produces are" + value << [["some"], null] + } +} From 93abe81d8b48eb5fe8cd12555a7091cd27ce4f93 Mon Sep 17 00:00:00 2001 From: Mathias Kowalzik Date: Fri, 21 Oct 2016 00:29:58 +0200 Subject: [PATCH 6/6] fixed missing license header --- .../processor/model/RequestMappingTest.groovy | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/annotations/src/test/groovy/org/leandreck/endpoints/processor/model/RequestMappingTest.groovy b/annotations/src/test/groovy/org/leandreck/endpoints/processor/model/RequestMappingTest.groovy index 18b65f1..1af1e2e 100644 --- a/annotations/src/test/groovy/org/leandreck/endpoints/processor/model/RequestMappingTest.groovy +++ b/annotations/src/test/groovy/org/leandreck/endpoints/processor/model/RequestMappingTest.groovy @@ -1,3 +1,18 @@ +/** + * Copyright © 2016 Mathias Kowalzik (Mathias.Kowalzik@leandreck.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.leandreck.endpoints.processor.model import org.springframework.web.bind.annotation.RequestMethod