From e29072dfd28193d373269237654276fd65715beb Mon Sep 17 00:00:00 2001 From: Daniel Tang Date: Thu, 20 Apr 2017 13:22:59 -0700 Subject: [PATCH] fix schema generation for transformer corner case When a Transformer affects an array element type, SchemaRepository treated it as the non-transformed type. If the Transformer transformed to a primitive, SchemaRepository tried to add the primitive as an object schema, resulting in an error. This fixes that by getting the schema type when dealing with array types. --- .../spi/config/model/SchemaRepository.java | 7 +- .../spi/discovery/DiscoveryGeneratorTest.java | 1 - .../server/spi/discovery/array_endpoint.json | 504 +++++++++--------- .../server/spi/swagger/array_endpoint.swagger | 27 + .../api/server/spi/testing/ArrayEndpoint.java | 13 +- .../api/server/spi/testing/StringValue.java | 20 + .../spi/testing/StringValueTransformer.java | 30 ++ 7 files changed, 359 insertions(+), 243 deletions(-) create mode 100644 test-utils/src/main/java/com/google/api/server/spi/testing/StringValue.java create mode 100644 test-utils/src/main/java/com/google/api/server/spi/testing/StringValueTransformer.java diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/SchemaRepository.java b/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/SchemaRepository.java index fc14188b..5b4c1d5b 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/SchemaRepository.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/SchemaRepository.java @@ -211,8 +211,11 @@ private void fillInFieldInformation(Field.Builder builder, TypeToken fieldTyp builder.setSchemaReference(SchemaReference.create(this, config, fieldType)); } else if (ft == FieldType.ARRAY) { Field.Builder arrayItemBuilder = Field.builder().setName(ARRAY_UNUSED_MSG); - fillInFieldInformation(arrayItemBuilder, Types.getArrayItemType(fieldType), - typesForConfig, config); + fillInFieldInformation( + arrayItemBuilder, + ApiAnnotationIntrospector.getSchemaType(Types.getArrayItemType(fieldType), config), + typesForConfig, + config); builder.setArrayItemSchema(arrayItemBuilder.build()); } } diff --git a/endpoints-framework/src/test/java/com/google/api/server/spi/discovery/DiscoveryGeneratorTest.java b/endpoints-framework/src/test/java/com/google/api/server/spi/discovery/DiscoveryGeneratorTest.java index 0db0bd2e..bf7f399e 100644 --- a/endpoints-framework/src/test/java/com/google/api/server/spi/discovery/DiscoveryGeneratorTest.java +++ b/endpoints-framework/src/test/java/com/google/api/server/spi/discovery/DiscoveryGeneratorTest.java @@ -183,7 +183,6 @@ public void testDirectoryIsCloneable() throws Exception { private RestDescription getDiscovery(DiscoveryContext context, Class serviceClass) throws Exception { - ImmutableList.Builder builder = ImmutableList.builder(); ApiConfig config = configLoader.loadConfiguration(ServiceContext.create(), serviceClass); // If the clone call fails, the generated discovery is invalid. return Iterables.getFirst( diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/discovery/array_endpoint.json b/endpoints-framework/src/test/resources/com/google/api/server/spi/discovery/array_endpoint.json index 080226f3..42ac3489 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/discovery/array_endpoint.json +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/discovery/array_endpoint.json @@ -1,96 +1,242 @@ { - "kind": "discovery#restDescription", - "discoveryVersion": "v1", - "id": "myapi:v1", - "name": "myapi", - "version": "v1", + "auth": { + "oauth2": { + "scopes": { + "https://www.googleapis.com/auth/userinfo.email": { + "description": "View your email address" + } + } + } + }, + "basePath": "/_ah/api/myapi/v1/", + "baseUrl": "https://myapi.appspot.com/_ah/api/myapi/v1/", + "batchPath": "batch", "description": "This is an API", + "discoveryVersion": "v1", "icons": { "x16": "http://www.google.com/images/icons/product/search-16.gif", "x32": "http://www.google.com/images/icons/product/search-32.gif" }, - "protocol": "rest", - "baseUrl": "https://myapi.appspot.com/_ah/api/myapi/v1/", - "basePath": "/_ah/api/myapi/v1/", - "rootUrl": "https://myapi.appspot.com/_ah/api/", - "servicePath": "myapi/v1/", - "batchPath": "batch", + "id": "myapi:v1", + "kind": "discovery#restDescription", + "name": "myapi", "parameters": { "alt": { - "type": "string", - "description": "Data format for the response.", "default": "json", + "description": "Data format for the response.", "enum": [ "json" ], "enumDescriptions": [ "Responses with Content-Type of application/json" ], - "location": "query" + "location": "query", + "type": "string" }, "fields": { - "type": "string", "description": "Selector specifying which fields to include in a partial response.", - "location": "query" + "location": "query", + "type": "string" }, "key": { - "type": "string", "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.", - "location": "query" + "location": "query", + "type": "string" }, "oauth_token": { - "type": "string", "description": "OAuth 2.0 token for the current user.", - "location": "query" + "location": "query", + "type": "string" }, "prettyPrint": { - "type": "boolean", - "description": "Returns response with indentations and line breaks.", "default": "true", - "location": "query" + "description": "Returns response with indentations and line breaks.", + "location": "query", + "type": "boolean" }, "quotaUser": { - "type": "string", "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.", - "location": "query" + "location": "query", + "type": "string" }, "userIp": { - "type": "string", "description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.", - "location": "query" + "location": "query", + "type": "string" } }, - "auth": { - "oauth2": { - "scopes": { - "https://www.googleapis.com/auth/userinfo.email": { - "description": "View your email address" + "protocol": "rest", + "resources": { + "arrayEndpoint": { + "methods": { + "getAllArrayedFoos": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getAllArrayedFoos", + "path": "foocollectioncollection", + "response": { + "$ref": "FooCollectionCollection" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "getAllFoos": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getAllFoos", + "path": "getAllFoos", + "response": { + "$ref": "FooCollectionCollection" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "getAllFoosResponse": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getAllFoosResponse", + "path": "getAllFoosResponse", + "response": { + "$ref": "CollectionResponse_FooCollection" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "getAllNestedFoosResponse": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getAllNestedFoosResponse", + "path": "getAllNestedFoosResponse", + "response": { + "$ref": "CollectionResponse_FooCollectionCollection" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "getArrayService": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getArrayService", + "path": "arrayendpoint", + "response": { + "$ref": "ArrayEndpoint" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "getArrayedFoos": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getArrayedFoos", + "path": "getArrayedFoos", + "response": { + "$ref": "FooCollection" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "getBaz": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getBaz", + "path": "baz", + "response": { + "$ref": "Baz" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "getFoos": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getFoos", + "path": "foocollection", + "response": { + "$ref": "FooCollection" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "getFoosResponse": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getFoosResponse", + "path": "collectionresponse_foo", + "response": { + "$ref": "CollectionResponse_Foo" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "getIntegers": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getIntegers", + "path": "integercollection", + "response": { + "$ref": "IntegerCollection" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "getIntegersResponse": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getIntegersResponse", + "path": "getIntegersResponse", + "response": { + "$ref": "CollectionResponse_Integer" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "getListOfString": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getListOfString", + "path": "getListOfString", + "response": { + "$ref": "ListContainer" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "getObjectIntegers": { + "httpMethod": "GET", + "id": "myapi.arrayEndpoint.getObjectIntegers", + "path": "getObjectIntegers", + "response": { + "$ref": "IntegerCollection" + }, + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } } } }, + "rootUrl": "https://myapi.appspot.com/_ah/api/", "schemas": { "ArrayEndpoint": { "id": "ArrayEndpoint", - "type": "object", "properties": { "allArrayedFoos": { - "type": "array", "items": { - "type": "array", "items": { "$ref": "Foo" - } - } + }, + "type": "array" + }, + "type": "array" }, "allFoos": { - "type": "array", "items": { - "type": "array", "items": { "$ref": "Foo" - } - } + }, + "type": "array" + }, + "type": "array" }, "allFoosResponse": { "$ref": "CollectionResponse_FooCollection" @@ -102,317 +248,197 @@ "$ref": "ArrayEndpoint" }, "arrayedFoos": { - "type": "array", "items": { "$ref": "Foo" - } + }, + "type": "array" }, "baz": { "$ref": "Baz" }, "foos": { - "type": "array", "items": { "$ref": "Foo" - } + }, + "type": "array" }, "foosResponse": { "$ref": "CollectionResponse_Foo" }, "integers": { - "type": "array", "items": { - "type": "integer", - "format": "int32" - } + "format": "int32", + "type": "integer" + }, + "type": "array" }, "integersResponse": { "$ref": "CollectionResponse_Integer" }, + "listOfString": { + "$ref": "ListContainer" + }, "objectIntegers": { - "type": "array", "items": { - "type": "integer", - "format": "int32" - } + "format": "int32", + "type": "integer" + }, + "type": "array" } - } + }, + "type": "object" }, "Baz": { "id": "Baz", - "type": "object", "properties": { "foo": { "$ref": "Foo" }, "foos": { - "type": "array", "items": { "$ref": "Foo" - } + }, + "type": "array" } - } + }, + "type": "object" }, "CollectionResponse_Foo": { "id": "CollectionResponse_Foo", - "type": "object", "properties": { "items": { - "type": "array", "items": { "$ref": "Foo" - } + }, + "type": "array" }, "nextPageToken": { "type": "string" } - } + }, + "type": "object" }, "CollectionResponse_FooCollection": { "id": "CollectionResponse_FooCollection", - "type": "object", "properties": { "items": { - "type": "array", "items": { - "type": "array", "items": { "$ref": "Foo" - } - } + }, + "type": "array" + }, + "type": "array" }, "nextPageToken": { "type": "string" } - } + }, + "type": "object" }, "CollectionResponse_FooCollectionCollection": { "id": "CollectionResponse_FooCollectionCollection", - "type": "object", "properties": { "items": { - "type": "array", "items": { - "type": "array", "items": { - "type": "array", "items": { "$ref": "Foo" - } - } - } + }, + "type": "array" + }, + "type": "array" + }, + "type": "array" }, "nextPageToken": { "type": "string" } - } + }, + "type": "object" }, "CollectionResponse_Integer": { "id": "CollectionResponse_Integer", - "type": "object", "properties": { "items": { - "type": "array", "items": { - "type": "integer", - "format": "int32" - } + "format": "int32", + "type": "integer" + }, + "type": "array" }, "nextPageToken": { "type": "string" } - } + }, + "type": "object" }, "Foo": { "id": "Foo", - "type": "object", "properties": { "name": { "type": "string" }, "value": { - "type": "integer", - "format": "int32" + "format": "int32", + "type": "integer" } - } + }, + "type": "object" }, "FooCollection": { "id": "FooCollection", - "type": "object", "properties": { "items": { - "type": "array", "items": { "$ref": "Foo" - } + }, + "type": "array" } - } + }, + "type": "object" }, "FooCollectionCollection": { "id": "FooCollectionCollection", - "type": "object", "properties": { "items": { - "type": "array", "items": { - "type": "array", "items": { "$ref": "Foo" - } - } + }, + "type": "array" + }, + "type": "array" } - } + }, + "type": "object" }, "IntegerCollection": { "id": "IntegerCollection", - "type": "object", "properties": { "items": { - "type": "array", "items": { - "type": "integer", - "format": "int32" - } - } - } - } - }, - "resources": { - "arrayEndpoint": { - "methods": { - "getAllArrayedFoos": { - "id": "myapi.arrayEndpoint.getAllArrayedFoos", - "path": "foocollectioncollection", - "httpMethod": "GET", - "response": { - "$ref": "FooCollectionCollection" - }, - "scopes": [ - "https://www.googleapis.com/auth/userinfo.email" - ] - }, - "getAllFoos": { - "id": "myapi.arrayEndpoint.getAllFoos", - "path": "getAllFoos", - "httpMethod": "GET", - "response": { - "$ref": "FooCollectionCollection" - }, - "scopes": [ - "https://www.googleapis.com/auth/userinfo.email" - ] - }, - "getAllFoosResponse": { - "id": "myapi.arrayEndpoint.getAllFoosResponse", - "path": "getAllFoosResponse", - "httpMethod": "GET", - "response": { - "$ref": "CollectionResponse_FooCollection" - }, - "scopes": [ - "https://www.googleapis.com/auth/userinfo.email" - ] - }, - "getAllNestedFoosResponse": { - "id": "myapi.arrayEndpoint.getAllNestedFoosResponse", - "path": "getAllNestedFoosResponse", - "httpMethod": "GET", - "response": { - "$ref": "CollectionResponse_FooCollectionCollection" - }, - "scopes": [ - "https://www.googleapis.com/auth/userinfo.email" - ] - }, - "getArrayService": { - "id": "myapi.arrayEndpoint.getArrayService", - "path": "arrayendpoint", - "httpMethod": "GET", - "response": { - "$ref": "ArrayEndpoint" + "format": "int32", + "type": "integer" }, - "scopes": [ - "https://www.googleapis.com/auth/userinfo.email" - ] - }, - "getArrayedFoos": { - "id": "myapi.arrayEndpoint.getArrayedFoos", - "path": "getArrayedFoos", - "httpMethod": "GET", - "response": { - "$ref": "FooCollection" - }, - "scopes": [ - "https://www.googleapis.com/auth/userinfo.email" - ] - }, - "getBaz": { - "id": "myapi.arrayEndpoint.getBaz", - "path": "baz", - "httpMethod": "GET", - "response": { - "$ref": "Baz" - }, - "scopes": [ - "https://www.googleapis.com/auth/userinfo.email" - ] - }, - "getFoos": { - "id": "myapi.arrayEndpoint.getFoos", - "path": "foocollection", - "httpMethod": "GET", - "response": { - "$ref": "FooCollection" - }, - "scopes": [ - "https://www.googleapis.com/auth/userinfo.email" - ] - }, - "getFoosResponse": { - "id": "myapi.arrayEndpoint.getFoosResponse", - "path": "collectionresponse_foo", - "httpMethod": "GET", - "response": { - "$ref": "CollectionResponse_Foo" - }, - "scopes": [ - "https://www.googleapis.com/auth/userinfo.email" - ] - }, - "getIntegers": { - "id": "myapi.arrayEndpoint.getIntegers", - "path": "integercollection", - "httpMethod": "GET", - "response": { - "$ref": "IntegerCollection" - }, - "scopes": [ - "https://www.googleapis.com/auth/userinfo.email" - ] - }, - "getIntegersResponse": { - "id": "myapi.arrayEndpoint.getIntegersResponse", - "path": "getIntegersResponse", - "httpMethod": "GET", - "response": { - "$ref": "CollectionResponse_Integer" - }, - "scopes": [ - "https://www.googleapis.com/auth/userinfo.email" - ] - }, - "getObjectIntegers": { - "id": "myapi.arrayEndpoint.getObjectIntegers", - "path": "getObjectIntegers", - "httpMethod": "GET", - "response": { - "$ref": "IntegerCollection" + "type": "array" + } + }, + "type": "object" + }, + "ListContainer": { + "id": "ListContainer", + "properties": { + "strings": { + "items": { + "type": "string" }, - "scopes": [ - "https://www.googleapis.com/auth/userinfo.email" - ] + "type": "array" } - } + }, + "type": "object" } - } + }, + "servicePath": "myapi/v1/", + "version": "v1" } diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/array_endpoint.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/array_endpoint.swagger index 949b6994..67263b9f 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/array_endpoint.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/array_endpoint.swagger @@ -156,6 +156,20 @@ } } }, + "/myapi/v1/getListOfString": { + "get": { + "operationId": "MyapiGetListOfString", + "parameters": [], + "responses": { + "200": { + "description": "A successful response", + "schema": { + "$ref": "#/definitions/ListContainer" + } + } + } + } + }, "/myapi/v1/getObjectIntegers": { "get": { "operationId": "MyapiGetObjectIntegers", @@ -250,6 +264,16 @@ } } }, + "ListContainer": { + "properties": { + "strings": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "CollectionResponse_FooCollection": { "properties": { "items": { @@ -323,6 +347,9 @@ "integersResponse": { "$ref": "#/definitions/CollectionResponse_Integer" }, + "listOfString": { + "$ref": "#/definitions/ListContainer" + }, "objectIntegers": { "type": "array", "items": { diff --git a/test-utils/src/main/java/com/google/api/server/spi/testing/ArrayEndpoint.java b/test-utils/src/main/java/com/google/api/server/spi/testing/ArrayEndpoint.java index ab8a5e65..d681e637 100644 --- a/test-utils/src/main/java/com/google/api/server/spi/testing/ArrayEndpoint.java +++ b/test-utils/src/main/java/com/google/api/server/spi/testing/ArrayEndpoint.java @@ -20,11 +20,12 @@ import com.google.api.server.spi.response.CollectionResponse; import java.util.Collection; +import java.util.List; /** * Test service used for testing array schemas. */ -@Api +@Api(transformers = StringValueTransformer.class) public class ArrayEndpoint { public ArrayEndpoint getArrayService() { @@ -80,4 +81,14 @@ public Integer[] getObjectIntegers() { public CollectionResponse getIntegersResponse() { return null; } + + @ApiMethod(path = "getListOfString") + public ListContainer getListOfString() { + return null; + } + + public static class ListContainer { + public List strings; + } + } diff --git a/test-utils/src/main/java/com/google/api/server/spi/testing/StringValue.java b/test-utils/src/main/java/com/google/api/server/spi/testing/StringValue.java new file mode 100644 index 00000000..1b534cb7 --- /dev/null +++ b/test-utils/src/main/java/com/google/api/server/spi/testing/StringValue.java @@ -0,0 +1,20 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * 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 com.google.api.server.spi.testing; + +public class StringValue { + public String value; +} diff --git a/test-utils/src/main/java/com/google/api/server/spi/testing/StringValueTransformer.java b/test-utils/src/main/java/com/google/api/server/spi/testing/StringValueTransformer.java new file mode 100644 index 00000000..b45300a2 --- /dev/null +++ b/test-utils/src/main/java/com/google/api/server/spi/testing/StringValueTransformer.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * 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 com.google.api.server.spi.testing; + +import com.google.api.server.spi.config.Transformer; + +public class StringValueTransformer implements Transformer { + @Override + public String transformTo(StringValue in) { + return null; + } + + @Override + public StringValue transformFrom(String in) { + return null; + } +}