From 5da75d8789c92bd4adb205f8783274cba5a649f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Denis?= Date: Fri, 26 Jul 2019 23:06:29 +0200 Subject: [PATCH] Improve generated Swagger spec compatibility with Endpoints Portal * Support Endpoints Portal authorization flow - Add authorizationUrl and useScopesInAuthFlow issuer config params - Define default authorizationUrl for Google id token issuers - Add scopes in operation's security (required for Try it! in Portal) - Add authorizationUrl in OAuth2 flow (required for Try it! in Portal) - Reorder Google id token issuers to have https:// one first * New supported Swagger features - Support deprecation on Swagger operations with @Deprecated - Add tags to Swagger operations to group like API Explorer in Portal - Inline Map types (including JsonMap) to avoid useless definitions - Set default values for parameters - Add proper collection format (multi instead of csv) - List unsupported features of the spec in UNSUPPORTED_FEATURES.md * Order consistency - Remove ordering attempt in reflection-based introspection - Add ordering in Swagger generation (sort paths by string order) * Testing - Improve coverage of SwaggerGenerator (array tests) - Extract Swagger assertion logic in a truth.dev subject - Update truth.dev to 1.0 * Cleanup - Remove writeInternal boolean in SwaggerGenerator (not used) --- .../server/spi/tools/GetOpenApiDocAction.java | 2 +- .../com/google/api/server/spi/Constant.java | 17 +- .../api/server/spi/MethodHierarchyReader.java | 45 +- .../api/server/spi/config/ApiIssuer.java | 11 + .../ApiConfigAnnotationReader.java | 136 ++--- .../config/annotationreader/IssuerUtil.java | 3 +- .../spi/config/model/ApiClassConfig.java | 2 +- .../server/spi/config/model/ApiConfig.java | 2 +- .../config/model/ApiIssuerAudienceConfig.java | 7 +- .../spi/config/model/ApiIssuerConfigs.java | 37 +- .../spi/config/model/ApiMethodConfig.java | 11 +- .../spi/config/model/SchemaRepository.java | 5 + .../server/spi/swagger/SwaggerGenerator.java | 128 ++++- .../spi/swagger/UNSUPPORTED_FEATURES.md | 18 + .../server/spi/MethodHierarchyReaderTest.java | 11 +- .../ApiConfigAnnotationReaderTest.java | 1 + .../config/model/ApiIssuerConfigsTest.java | 8 +- .../spi/swagger/SwaggerGeneratorTest.java | 107 +--- .../server/spi/swagger/SwaggerSubject.java | 111 ++++ .../server/spi/discovery/array_endpoint.json | 230 +++++++++ .../absolute_common_path_endpoint.swagger | 11 + .../swagger/absolute_path_endpoint.swagger | 14 + .../api/server/spi/swagger/api_keys.swagger | 24 +- .../server/spi/swagger/array_endpoint.swagger | 488 +++++++++++++++--- .../server/spi/swagger/enum_endpoint.swagger | 8 + .../server/spi/swagger/foo_endpoint.swagger | 109 ++-- .../foo_endpoint_default_context.swagger | 85 ++- .../spi/swagger/foo_endpoint_internal.swagger | 251 --------- .../swagger/foo_endpoint_localhost.swagger | 109 ++-- .../foo_with_description_endpoint.swagger | 110 ++-- .../server/spi/swagger/google_auth.swagger | 34 +- .../swagger/limit_metrics_endpoint.swagger | 11 + .../server/spi/swagger/map_endpoint.swagger | 336 +++++++----- .../spi/swagger/map_endpoint_legacy.swagger | 225 ++++++-- .../swagger/map_endpoint_with_array.swagger | 356 ++++++++----- .../swagger/multi_resource_endpoint.swagger | 20 + .../swagger/multi_version_endpoint.swagger | 14 + .../spi/swagger/third_party_auth.swagger | 11 + gradle.properties | 2 +- .../api/server/spi/testing/ArrayEndpoint.java | 25 + 40 files changed, 2169 insertions(+), 966 deletions(-) create mode 100644 endpoints-framework/src/main/java/com/google/api/server/spi/swagger/UNSUPPORTED_FEATURES.md create mode 100644 endpoints-framework/src/test/java/com/google/api/server/spi/swagger/SwaggerSubject.java delete mode 100644 endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint_internal.swagger diff --git a/endpoints-framework-tools/src/main/java/com/google/api/server/spi/tools/GetOpenApiDocAction.java b/endpoints-framework-tools/src/main/java/com/google/api/server/spi/tools/GetOpenApiDocAction.java index dfcf47ac..186169f9 100644 --- a/endpoints-framework-tools/src/main/java/com/google/api/server/spi/tools/GetOpenApiDocAction.java +++ b/endpoints-framework-tools/src/main/java/com/google/api/server/spi/tools/GetOpenApiDocAction.java @@ -121,7 +121,7 @@ public String genOpenApiDoc( SwaggerContext swaggerContext = new SwaggerContext() .setHostname(hostname) .setBasePath(basePath); - Swagger swagger = generator.writeSwagger(apiConfigs, true, swaggerContext); + Swagger swagger = generator.writeSwagger(apiConfigs, swaggerContext); String swaggerStr = Json.mapper().writer(new EndpointsPrettyPrinter()) .writeValueAsString(swagger); if (outputToDisk) { diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/Constant.java b/endpoints-framework/src/main/java/com/google/api/server/spi/Constant.java index 75c9d076..3c77cbb9 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/Constant.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/Constant.java @@ -55,18 +55,23 @@ private Constant() {} public static final String SKIP_CLIENT_ID_CHECK = "*"; /** - * Root URL of discovery doc generation API. This is on a host that Endpoints project owns so - * that even if producer has not picked an App Engine app host, this call can still succeed. + * Friendly name to refer to Google ID token authentication with accounts.google.com issuer. */ - public static final String DISCOVERY_GEN_ROOT = "https://webapis-discovery.appspot.com/_ah/api"; + public static final String GOOGLE_ID_TOKEN_ALT = "google_id_token_legacy"; /** - * Friendly name to refer to Google ID token authentication with accounts.google.com issuer. + * Google ID token authentication variant with https://accounts.google.com issuer. */ public static final String GOOGLE_ID_TOKEN_NAME = "google_id_token"; /** - * Google ID token authentication variant with https://accounts.google.com issuer. + * Google JWKS URI + */ + public static final String GOOGLE_JWKS_URI = "https://www.googleapis.com/oauth2/v1/certs"; + + /** + * Google OAuth2 authentication URL */ - public static final String GOOGLE_ID_TOKEN_NAME_HTTPS = "google_id_token_https"; + public static final String GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"; + } diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/MethodHierarchyReader.java b/endpoints-framework/src/main/java/com/google/api/server/spi/MethodHierarchyReader.java index 4f909e0d..1f551157 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/MethodHierarchyReader.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/MethodHierarchyReader.java @@ -15,16 +15,20 @@ */ package com.google.api.server.spi; +import com.google.api.server.spi.EndpointMethod.ResolvedSignature; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimap; import com.google.common.reflect.TypeToken; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -42,7 +46,7 @@ public class MethodHierarchyReader { private final Class endpointClass; // Map from method signatures to a list of method overrides for that signature (ordered // subclass -> superclass). - private ListMultimap endpointMethods; + private Multimap endpointMethods; /** * Constructs a {@code MethodHierarchyReader} for the given class type. @@ -60,8 +64,8 @@ public MethodHierarchyReader(Class endpointClass) { private void readMethodHierarchyIfNecessary() { if (endpointMethods == null) { - ImmutableListMultimap.Builder builder = - ImmutableListMultimap.builder(); + ImmutableMultimap.Builder builder = + ImmutableMultimap.builder(); buildServiceMethods(builder, TypeToken.of(endpointClass)); endpointMethods = builder.build(); } @@ -72,24 +76,10 @@ private void readMethodHierarchyIfNecessary() { * * @param overrides A list of method overrides ordered subclass -> superclass. */ - private EndpointMethod getLeafMethod(List overrides) { + private EndpointMethod getLeafMethod(Collection overrides) { // Because the list is ordered subclass -> superclass, index 0 will always contain the leaf // subclass implementation. - return overrides.get(0); - } - - /** - * Returns {@link ListMultimap#asMap multimap.asMap()}, with its type - * corrected from {@code Map>} to {@code Map>}. - */ - // Copied from com.google.common.collect.Multimaps. We can't use the actual method from - // that class as appengine build magic gives us an older version of guava that doesn't yet have - // this method. - // TODO: Switch to Multimaps.asMap() once it becomes available in appengine. - @SuppressWarnings("unchecked") - // safe by specification of ListMultimap.asMap() - private static Map> asMap(ListMultimap multimap) { - return (Map>) (Map) multimap.asMap(); + return overrides.iterator().next(); } /** @@ -99,7 +89,7 @@ private static Map> asMap(ListMultimap multimap) { public Iterable getLeafMethods() { readMethodHierarchyIfNecessary(); ImmutableList.Builder builder = ImmutableList.builder(); - for (List overrides : asMap(endpointMethods).values()) { + for (Collection overrides : endpointMethods.asMap().values()) { builder.add(getLeafMethod(overrides).getMethod()); } return builder.build(); @@ -113,7 +103,7 @@ public Iterable getLeafMethods() { public Iterable getLeafEndpointMethods() { readMethodHierarchyIfNecessary(); ImmutableList.Builder builder = ImmutableList.builder(); - for (List overrides : asMap(endpointMethods).values()) { + for (Collection overrides : endpointMethods.asMap().values()) { builder.add(getLeafMethod(overrides)); } return builder.build(); @@ -127,7 +117,7 @@ public Iterable getLeafEndpointMethods() { public Iterable> getMethodOverrides() { readMethodHierarchyIfNecessary(); ImmutableList.Builder> builder = ImmutableList.builder(); - for (List overrides : asMap(endpointMethods).values()) { + for (Collection overrides : endpointMethods.asMap().values()) { ImmutableList.Builder methodBuilder = ImmutableList.builder(); for (EndpointMethod method : overrides) { methodBuilder.add(method.getMethod()); @@ -142,9 +132,9 @@ public Iterable> getMethodOverrides() { * Bridge methods are ignored. For each method, all valid method implementations are included, * ordered subclass to superclass. Methods are stored in the EndpointMethod container. */ - public Iterable> getEndpointOverrides() { + public Iterable> getEndpointOverrides() { readMethodHierarchyIfNecessary(); - return asMap(endpointMethods).values(); + return endpointMethods.asMap().values(); } /** @@ -155,7 +145,7 @@ public Iterable> getEndpointOverrides() { public Map getNameToLeafMethodMap() { readMethodHierarchyIfNecessary(); ImmutableMap.Builder builder = ImmutableMap.builder(); - for (List overrides : asMap(endpointMethods).values()) { + for (Collection overrides : endpointMethods.asMap().values()) { Method leafMethod = getLeafMethod(overrides).getMethod(); builder.put(leafMethod.getName(), leafMethod); } @@ -170,7 +160,7 @@ public Map getNameToLeafMethodMap() { public ListMultimap getNameToEndpointOverridesMap() { readMethodHierarchyIfNecessary(); ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); - for (List overrides : asMap(endpointMethods).values()) { + for (Collection overrides : endpointMethods.asMap().values()) { builder.putAll(getLeafMethod(overrides).getMethod().getName(), overrides); } return builder.build(); @@ -183,13 +173,14 @@ public ListMultimap getNameToEndpointOverridesMap() { * @param serviceType is the class object being inspected for service methods */ private void buildServiceMethods( - ImmutableListMultimap.Builder builder, + ImmutableMultimap.Builder builder, TypeToken serviceType) { for (TypeToken typeToken : serviceType.getTypes().classes()) { Class serviceClass = typeToken.getRawType(); if (Object.class.equals(serviceClass)) { return; } + //getDeclaredMethods returns methods in random order, so must not assume any specific order for (Method method : serviceClass.getDeclaredMethods()) { if (!isServiceMethod(method)) { continue; diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/config/ApiIssuer.java b/endpoints-framework/src/main/java/com/google/api/server/spi/config/ApiIssuer.java index bc42ba91..c14899bf 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/config/ApiIssuer.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/config/ApiIssuer.java @@ -33,4 +33,15 @@ * The location of the JSON web key set used to verify tokens generated by this issuer. */ String jwksUri() default ""; + + /** + * The authorization URL to use for the authorization flow. + */ + String authorizationUrl() default ""; + + /** + * When true, scopes will be used in authorization flow. + */ + boolean useScopesInAuthFlow() default false; + } diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/config/annotationreader/ApiConfigAnnotationReader.java b/endpoints-framework/src/main/java/com/google/api/server/spi/config/annotationreader/ApiConfigAnnotationReader.java index 27c6541f..c9faaff4 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/config/annotationreader/ApiConfigAnnotationReader.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/config/annotationreader/ApiConfigAnnotationReader.java @@ -31,19 +31,21 @@ import com.google.api.server.spi.config.PeerAuthenticator; import com.google.api.server.spi.config.Transformer; import com.google.api.server.spi.config.model.ApiClassConfig; +import com.google.api.server.spi.config.model.ApiClassConfig.MethodConfigMap; import com.google.api.server.spi.config.model.ApiConfig; import com.google.api.server.spi.config.model.ApiIssuerAudienceConfig; import com.google.api.server.spi.config.model.ApiIssuerConfigs; import com.google.api.server.spi.config.model.ApiMethodConfig; import com.google.api.server.spi.config.model.ApiParameterConfig; import com.google.api.server.spi.config.model.ApiSerializationConfig; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.reflect.TypeToken; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.List; +import java.util.Collection; import java.util.Map; import javax.annotation.Nullable; @@ -158,15 +160,15 @@ private boolean readEndpointClass(ApiConfig config, Class endpointClass, Anno if (api != null) { readApi(new ApiAnnotationConfig(config), api); readApiAuth(new ApiAuthAnnotationConfig(config.getAuthConfig()), - (Annotation) getAnnotationProperty(api, "auth")); + getAnnotationProperty(api, "auth")); readApiFrontendLimits(new ApiFrontendLimitsAnnotationConfig(config.getFrontendLimitsConfig()), - (Annotation) getAnnotationProperty(api, "frontendLimits")); + getAnnotationProperty(api, "frontendLimits")); readApiCacheControl(new ApiCacheControlAnnotationConfig(config.getCacheControlConfig()), - (Annotation) getAnnotationProperty(api, "cacheControl")); + getAnnotationProperty(api, "cacheControl")); readApiNamespace(new ApiNamespaceAnnotationConfig(config.getNamespaceConfig()), - (Annotation) getAnnotationProperty(api, "namespace")); + getAnnotationProperty(api, "namespace")); readSerializers(config.getSerializationConfig(), - (Class>[]) getAnnotationProperty(api, "transformers")); + getAnnotationProperty(api, "transformers")); } if (apiClass != null) { @@ -178,40 +180,40 @@ private boolean readEndpointClass(ApiConfig config, Class endpointClass, Anno private void readApi(ApiAnnotationConfig config, Annotation api) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - config.setIsAbstractIfSpecified((AnnotationBoolean) getAnnotationProperty(api, "isAbstract")); - config.setRootIfNotEmpty((String) getAnnotationProperty(api, "root")); + config.setIsAbstractIfSpecified(getAnnotationProperty(api, "isAbstract")); + config.setRootIfNotEmpty(getAnnotationProperty(api, "root")); - config.setNameIfNotEmpty((String) getAnnotationProperty(api, "name")); - config.setCanonicalNameIfNotEmpty((String) getAnnotationProperty(api, "canonicalName")); + config.setNameIfNotEmpty(getAnnotationProperty(api, "name")); + config.setCanonicalNameIfNotEmpty(getAnnotationProperty(api, "canonicalName")); - config.setVersionIfNotEmpty((String) getAnnotationProperty(api, "version")); - config.setTitleIfNotEmpty((String) getAnnotationProperty(api, "title")); - config.setDescriptionIfNotEmpty((String) getAnnotationProperty(api, "description")); - config.setDocumentationLinkIfNotEmpty((String) getAnnotationProperty(api, "documentationLink")); + config.setVersionIfNotEmpty(getAnnotationProperty(api, "version")); + config.setTitleIfNotEmpty(getAnnotationProperty(api, "title")); + config.setDescriptionIfNotEmpty(getAnnotationProperty(api, "description")); + config.setDocumentationLinkIfNotEmpty(getAnnotationProperty(api, "documentationLink")); config.setIsDefaultVersionIfSpecified( - (AnnotationBoolean) getAnnotationProperty(api, "defaultVersion")); + getAnnotationProperty(api, "defaultVersion")); config.setIsDiscoverableIfSpecified( - (AnnotationBoolean) getAnnotationProperty(api, "discoverable")); + getAnnotationProperty(api, "discoverable")); config.setUseDatastoreIfSpecified( - (AnnotationBoolean) getAnnotationProperty(api, "useDatastoreForAdditionalConfig")); + getAnnotationProperty(api, "useDatastoreForAdditionalConfig")); - config.setBackendRootIfNotEmpty((String) getAnnotationProperty(api, "backendRoot")); + config.setBackendRootIfNotEmpty(getAnnotationProperty(api, "backendRoot")); - config.setResourceIfNotEmpty((String) getAnnotationProperty(api, "resource")); - config.setAuthLevelIfSpecified((AuthLevel) getAnnotationProperty(api, "authLevel")); - config.setScopesIfSpecified((String[]) getAnnotationProperty(api, "scopes")); - config.setAudiencesIfSpecified((String[]) getAnnotationProperty(api, "audiences")); + config.setResourceIfNotEmpty(getAnnotationProperty(api, "resource")); + config.setAuthLevelIfSpecified(getAnnotationProperty(api, "authLevel")); + config.setScopesIfSpecified(getAnnotationProperty(api, "scopes")); + config.setAudiencesIfSpecified(getAnnotationProperty(api, "audiences")); config.setIssuersIfSpecified(getIssuerConfigs(api)); config.setIssuerAudiencesIfSpecified(getIssuerAudiences(api)); - config.setClientIdsIfSpecified((String[]) getAnnotationProperty(api, "clientIds")); + config.setClientIdsIfSpecified(getAnnotationProperty(api, "clientIds")); config.setAuthenticatorsIfSpecified( this.[]>getAnnotationProperty(api, "authenticators")); config.setPeerAuthenticatorsIfSpecified(this .[]>getAnnotationProperty(api, "peerAuthenticators")); config.setApiKeyRequiredIfSpecified( - (AnnotationBoolean) this.getAnnotationProperty(api, "apiKeyRequired")); + this.getAnnotationProperty(api, "apiKeyRequired")); config.setApiLimitMetrics( - (ApiLimitMetric[]) this.getAnnotationProperty(api, "limitDefinitions")); + this.getAnnotationProperty(api, "limitDefinitions")); } private ApiIssuerConfigs getIssuerConfigs(Annotation annotation) @@ -239,22 +241,22 @@ private T getAnnotationProperty(Annotation annotation, String name) protected void readApiAuth(ApiAuthAnnotationConfig config, Annotation auth) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { config.setAllowCookieAuthIfSpecified( - (AnnotationBoolean) getAnnotationProperty(auth, "allowCookieAuth")); - config.setBlockedRegionsIfNotEmpty((String[]) getAnnotationProperty(auth, "blockedRegions")); + getAnnotationProperty(auth, "allowCookieAuth")); + config.setBlockedRegionsIfNotEmpty(getAnnotationProperty(auth, "blockedRegions")); } private void readApiFrontendLimits(ApiFrontendLimitsAnnotationConfig config, Annotation frontendLimits) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { config.setUnregisteredUserQpsIfSpecified( - (Integer) getAnnotationProperty(frontendLimits, "unregisteredUserQps")); + getAnnotationProperty(frontendLimits, "unregisteredUserQps")); config.setUnregisteredQpsIfSpecified( - (Integer) getAnnotationProperty(frontendLimits, "unregisteredQps")); + getAnnotationProperty(frontendLimits, "unregisteredQps")); config.setUnregisteredDailyIfSpecified( - (Integer) getAnnotationProperty(frontendLimits, "unregisteredDaily")); + getAnnotationProperty(frontendLimits, "unregisteredDaily")); readApiFrontendLimitRules(config, - (Annotation[]) getAnnotationProperty(frontendLimits, "rules")); + getAnnotationProperty(frontendLimits, "rules")); } private void readSerializers( @@ -266,15 +268,15 @@ private void readSerializers( private void readApiCacheControl(ApiCacheControlAnnotationConfig config, Annotation cacheControl) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - config.setTypeIfNotEmpty((String) getAnnotationProperty(cacheControl, "type")); - config.setMaxAgeIfSpecified((Integer) getAnnotationProperty(cacheControl, "maxAge")); + config.setTypeIfNotEmpty(getAnnotationProperty(cacheControl, "type")); + config.setMaxAgeIfSpecified(getAnnotationProperty(cacheControl, "maxAge")); } protected void readApiNamespace(ApiNamespaceAnnotationConfig config, Annotation namespace) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { - config.setOwnerDomainIfNotEmpty((String) getAnnotationProperty(namespace, "ownerDomain")); - config.setOwnerNameIfNotEmpty((String) getAnnotationProperty(namespace, "ownerName")); - config.setPackagePathIfNotEmpty((String) getAnnotationProperty(namespace, "packagePath")); + config.setOwnerDomainIfNotEmpty(getAnnotationProperty(namespace, "ownerDomain")); + config.setOwnerNameIfNotEmpty(getAnnotationProperty(namespace, "ownerName")); + config.setPackagePathIfNotEmpty(getAnnotationProperty(namespace, "packagePath")); } private void readApiFrontendLimitRules(ApiFrontendLimitsAnnotationConfig config, @@ -282,9 +284,9 @@ private void readApiFrontendLimitRules(ApiFrontendLimitsAnnotationConfig config, InvocationTargetException { for (Annotation rule : rules) { String match = getAnnotationProperty(rule, "match"); - int qps = (Integer) getAnnotationProperty(rule, "qps"); - int userQps = (Integer) getAnnotationProperty(rule, "userQps"); - int daily = (Integer) getAnnotationProperty(rule, "daily"); + int qps = getAnnotationProperty(rule, "qps"); + int userQps = getAnnotationProperty(rule, "userQps"); + int daily = getAnnotationProperty(rule, "daily"); String analyticsId = getAnnotationProperty(rule, "analyticsId"); config.getConfig().addRule(match, qps, userQps, daily, analyticsId); } @@ -292,20 +294,20 @@ private void readApiFrontendLimitRules(ApiFrontendLimitsAnnotationConfig config, private void readApiClass(ApiClassAnnotationConfig config, Annotation apiClass) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { - config.setResourceIfNotEmpty((String) getAnnotationProperty(apiClass, "resource")); - config.setAuthLevelIfSpecified((AuthLevel) getAnnotationProperty(apiClass, "authLevel")); - config.setScopesIfSpecified((String[]) getAnnotationProperty(apiClass, "scopes")); - config.setAudiencesIfSpecified((String[]) getAnnotationProperty(apiClass, "audiences")); + config.setResourceIfNotEmpty(getAnnotationProperty(apiClass, "resource")); + config.setAuthLevelIfSpecified(getAnnotationProperty(apiClass, "authLevel")); + config.setScopesIfSpecified(getAnnotationProperty(apiClass, "scopes")); + config.setAudiencesIfSpecified(getAnnotationProperty(apiClass, "audiences")); config.setIssuerAudiencesIfSpecified(getIssuerAudiences(apiClass)); - config.setClientIdsIfSpecified((String[]) getAnnotationProperty(apiClass, "clientIds")); + config.setClientIdsIfSpecified(getAnnotationProperty(apiClass, "clientIds")); config.setAuthenticatorsIfSpecified( this.[]>getAnnotationProperty(apiClass, "authenticators")); config.setPeerAuthenticatorsIfSpecified(this.< Class[]>getAnnotationProperty(apiClass, "peerAuthenticators")); config.setUseDatastoreIfSpecified( - (AnnotationBoolean) getAnnotationProperty(apiClass, "useDatastoreForAdditionalConfig")); + getAnnotationProperty(apiClass, "useDatastoreForAdditionalConfig")); config.setApiKeyRequiredIfSpecified( - (AnnotationBoolean) this.getAnnotationProperty(apiClass, "apiKeyRequired")); + this.getAnnotationProperty(apiClass, "apiKeyRequired")); } private void readEndpointMethods(Class endpointClass, @@ -313,55 +315,59 @@ private void readEndpointMethods(Class endpointClass, throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { MethodHierarchyReader methodReader = new MethodHierarchyReader(endpointClass); - Iterable> methods = methodReader.getEndpointOverrides(); + Iterable> methods = methodReader.getEndpointOverrides(); - for (List overrides : methods) { - readEndpointMethod(methodConfigMap, overrides); + for (Collection overrides : methods) { + readEndpointMethod(methodConfigMap, overrides, + endpointClass.getAnnotation(Deprecated.class) != null); } } - private void readEndpointMethod(ApiClassConfig.MethodConfigMap methodConfigMap, - List overrides) + private void readEndpointMethod(MethodConfigMap methodConfigMap, + Collection overrides, boolean deprecated) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { Class apiMethodClass = annotationTypes.get("ApiMethod"); - final EndpointMethod finalMethod = overrides.get(0); + final EndpointMethod finalMethod = overrides.iterator().next(); ApiMethodConfig methodConfig = methodConfigMap.getOrCreate(finalMethod); readMethodRequestParameters(finalMethod, methodConfig); // Process overrides in reverse order. - for (EndpointMethod method : Lists.reverse(overrides)) { + for (EndpointMethod method : Lists.reverse(ImmutableList.copyOf(overrides))) { + ApiMethodAnnotationConfig config = new ApiMethodAnnotationConfig(methodConfig); Annotation apiMethod = method.getMethod().getAnnotation(apiMethodClass); if (apiMethod != null) { - readApiMethodInstance(new ApiMethodAnnotationConfig(methodConfig), apiMethod); + readApiMethodInstance(config, apiMethod); } + methodConfig.setDeprecated(deprecated + || method.getMethod().getAnnotation(Deprecated.class) != null); } } private void readApiMethodInstance(ApiMethodAnnotationConfig config, Annotation apiMethod) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { - config.setNameIfNotEmpty((String) getAnnotationProperty(apiMethod, "name")); - config.setDescriptionIfNotEmpty((String) getAnnotationProperty(apiMethod, "description")); - config.setPathIfNotEmpty((String) getAnnotationProperty(apiMethod, "path")); - config.setHttpMethodIfNotEmpty((String) getAnnotationProperty(apiMethod, "httpMethod")); - config.setAuthLevelIfSpecified((AuthLevel) getAnnotationProperty(apiMethod, "authLevel")); - config.setScopesIfSpecified((String[]) getAnnotationProperty(apiMethod, "scopes")); - config.setAudiencesIfSpecified((String[]) getAnnotationProperty(apiMethod, "audiences")); + config.setNameIfNotEmpty(getAnnotationProperty(apiMethod, "name")); + config.setDescriptionIfNotEmpty(getAnnotationProperty(apiMethod, "description")); + config.setPathIfNotEmpty(getAnnotationProperty(apiMethod, "path")); + config.setHttpMethodIfNotEmpty(getAnnotationProperty(apiMethod, "httpMethod")); + config.setAuthLevelIfSpecified(getAnnotationProperty(apiMethod, "authLevel")); + config.setScopesIfSpecified(getAnnotationProperty(apiMethod, "scopes")); + config.setAudiencesIfSpecified(getAnnotationProperty(apiMethod, "audiences")); config.setIssuerAudiencesIfSpecified(getIssuerAudiences(apiMethod)); - config.setClientIdsIfSpecified((String[]) getAnnotationProperty(apiMethod, "clientIds")); + config.setClientIdsIfSpecified(getAnnotationProperty(apiMethod, "clientIds")); config.setAuthenticatorsIfSpecified( this.[]>getAnnotationProperty(apiMethod, "authenticators")); config.setPeerAuthenticatorsIfSpecified(this.< Class[]>getAnnotationProperty(apiMethod, "peerAuthenticators")); - config.setIgnoredIfSpecified((AnnotationBoolean) getAnnotationProperty(apiMethod, "ignored")); + config.setIgnoredIfSpecified(getAnnotationProperty(apiMethod, "ignored")); config.setApiKeyRequiredIfSpecified( - (AnnotationBoolean) this.getAnnotationProperty(apiMethod, "apiKeyRequired")); + this.getAnnotationProperty(apiMethod, "apiKeyRequired")); config.setMetricCosts( - (ApiMetricCost[]) getAnnotationProperty(apiMethod, "metricCosts")); + getAnnotationProperty(apiMethod, "metricCosts")); } private void readMethodRequestParameters(EndpointMethod endpointMethod, diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/config/annotationreader/IssuerUtil.java b/endpoints-framework/src/main/java/com/google/api/server/spi/config/annotationreader/IssuerUtil.java index 5ccdb16e..1d54ee39 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/config/annotationreader/IssuerUtil.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/config/annotationreader/IssuerUtil.java @@ -36,7 +36,8 @@ public static ApiIssuerConfigs toConfig(ApiIssuer[] issuerConfigs) { ApiIssuerConfigs.Builder builder = ApiIssuerConfigs.builder(); for (ApiIssuer issuerConfig : issuerConfigs) { builder.addIssuer( - new IssuerConfig(issuerConfig.name(), issuerConfig.issuer(), issuerConfig.jwksUri())); + new IssuerConfig(issuerConfig.name(), issuerConfig.issuer(), issuerConfig.jwksUri(), + issuerConfig.authorizationUrl(), issuerConfig.useScopesInAuthFlow())); } return builder.build(); } diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiClassConfig.java b/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiClassConfig.java index 1cb4cbfb..fa8ed086 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiClassConfig.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiClassConfig.java @@ -174,7 +174,7 @@ public List getAudiences() { public void setIssuerAudiences(ApiIssuerAudienceConfig issuerAudiences) { Preconditions.checkNotNull(issuerAudiences, "issuerAudiences should never be null"); this.issuerAudiences = issuerAudiences; - if (issuerAudiences.hasIssuer(Constant.GOOGLE_ID_TOKEN_NAME)) { + if (issuerAudiences.hasGoogleIssuer()) { getApiConfig().ensureGoogleIssuer(); } } diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiConfig.java b/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiConfig.java index a76b26bc..b684f34f 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiConfig.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiConfig.java @@ -453,7 +453,7 @@ public ApiIssuerConfigs getIssuers() { public void setIssuerAudiences(ApiIssuerAudienceConfig issuerAudiences) { Preconditions.checkNotNull(issuerAudiences, "issuerAudiences should never be null"); this.issuerAudiences = issuerAudiences; - if (issuerAudiences.hasIssuer(Constant.GOOGLE_ID_TOKEN_NAME)) { + if (issuerAudiences.hasGoogleIssuer()) { ensureGoogleIssuer(); } } diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiIssuerAudienceConfig.java b/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiIssuerAudienceConfig.java index a1aab9d1..192ea800 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiIssuerAudienceConfig.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiIssuerAudienceConfig.java @@ -15,7 +15,7 @@ */ package com.google.api.server.spi.config.model; -import com.google.common.collect.ImmutableListMultimap; +import com.google.api.server.spi.Constant; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -51,8 +51,9 @@ public boolean isEmpty() { return issuerAudiences.isEmpty(); } - public boolean hasIssuer(String issuer) { - return issuerAudiences.containsKey(issuer); + public boolean hasGoogleIssuer() { + return issuerAudiences.containsKey(Constant.GOOGLE_ID_TOKEN_NAME) + || issuerAudiences.containsKey(Constant.GOOGLE_ID_TOKEN_ALT); } public ImmutableSet getIssuerNames() { diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiIssuerConfigs.java b/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiIssuerConfigs.java index 296ad793..db70fcde 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiIssuerConfigs.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiIssuerConfigs.java @@ -10,14 +10,19 @@ */ public class ApiIssuerConfigs { static final String UNSPECIFIED_NAME = "_unspecified_issuer_name"; + + //according to https://developers.google.com/identity/protocols/OpenIDConnect#server-flow + //issuer can be either with or without https:// prefix public static final IssuerConfig GOOGLE_ID_TOKEN_ISSUER = new IssuerConfig( - Constant.GOOGLE_ID_TOKEN_NAME, "accounts.google.com", - "https://www.googleapis.com/oauth2/v1/certs"); + Constant.GOOGLE_ID_TOKEN_NAME, "https://accounts.google.com", + Constant.GOOGLE_JWKS_URI, + Constant.GOOGLE_AUTH_URL, true); public static final IssuerConfig GOOGLE_ID_TOKEN_ISSUER_ALT = new IssuerConfig( - Constant.GOOGLE_ID_TOKEN_NAME_HTTPS, "https://accounts.google.com", - "https://www.googleapis.com/oauth2/v1/certs"); + Constant.GOOGLE_ID_TOKEN_ALT, "accounts.google.com", + Constant.GOOGLE_JWKS_URI, + Constant.GOOGLE_AUTH_URL, true); public static final ApiIssuerConfigs UNSPECIFIED = builder() - .addIssuer(new IssuerConfig(UNSPECIFIED_NAME, null, null)) + .addIssuer(new IssuerConfig(UNSPECIFIED_NAME, null, null, "", false)) .build(); public static final ApiIssuerConfigs EMPTY = builder().build(); private final ImmutableMap issuerConfigs; @@ -43,8 +48,7 @@ public boolean isSpecified() { } public ApiIssuerConfigs withGoogleIdToken() { - if (hasIssuer(Constant.GOOGLE_ID_TOKEN_NAME) - && hasIssuer(Constant.GOOGLE_ID_TOKEN_NAME_HTTPS)) { + if (hasIssuer(Constant.GOOGLE_ID_TOKEN_NAME) && hasIssuer(Constant.GOOGLE_ID_TOKEN_ALT)) { return this; } Builder builder = builder(); @@ -74,11 +78,16 @@ public static class IssuerConfig { private final String name; private final String issuer; private final String jwksUri; + private final String authorizationUrl; + private final boolean useScopesInAuthFlow; - public IssuerConfig(String name, String issuer, String jwksUri) { + public IssuerConfig(String name, String issuer, String jwksUri, String authorizationUrl, + boolean useScopesInAuthFlow) { this.name = name; this.issuer = issuer; this.jwksUri = jwksUri; + this.authorizationUrl = authorizationUrl; + this.useScopesInAuthFlow = useScopesInAuthFlow; } public String getName() { @@ -93,12 +102,22 @@ public String getJwksUri() { return jwksUri; } + public String getAuthorizationUrl() { + return authorizationUrl; + } + + public boolean isUseScopesInAuthFlow() { + return useScopesInAuthFlow; + } + @Override public boolean equals(Object o) { return o != null && o instanceof IssuerConfig && Objects.equals(name, ((IssuerConfig) o).name) && Objects.equals(issuer, ((IssuerConfig) o).issuer) - && Objects.equals(jwksUri, ((IssuerConfig) o).jwksUri); + && Objects.equals(jwksUri, ((IssuerConfig) o).jwksUri) + && Objects.equals(authorizationUrl, ((IssuerConfig) o).authorizationUrl) + && Objects.equals(useScopesInAuthFlow, ((IssuerConfig) o).useScopesInAuthFlow); } @Override diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiMethodConfig.java b/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiMethodConfig.java index be1e6adf..a42fcf09 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiMethodConfig.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/config/model/ApiMethodConfig.java @@ -156,6 +156,7 @@ public String guessResourceName( private List> authenticators; private List> peerAuthenticators; private boolean ignored = false; + private boolean deprecated = false; private Boolean apiKeyRequired; private TypeToken returnType; private List metricCosts; @@ -442,7 +443,7 @@ public List getAudiences() { public void setIssuerAudiences(ApiIssuerAudienceConfig issuerAudiences) { Preconditions.checkNotNull(issuerAudiences, "issuerAudiences should never be null"); this.issuerAudiences = issuerAudiences; - if (issuerAudiences.hasIssuer(Constant.GOOGLE_ID_TOKEN_NAME)) { + if (issuerAudiences.hasGoogleIssuer()) { getApiClassConfig().getApiConfig().ensureGoogleIssuer(); } } @@ -483,6 +484,14 @@ public boolean isIgnored() { return ignored; } + public void setDeprecated(boolean deprecated) { + this.deprecated = deprecated; + } + + public boolean isDeprecated() { + return deprecated; + } + public void setApiKeyRequired(boolean apiKeyRequired) { this.apiKeyRequired = apiKeyRequired; } 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 dc3efd92..c5046c56 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 @@ -280,4 +280,9 @@ private void fillInFieldInformation(Field.Builder builder, TypeToken fieldTyp builder.setArrayItemSchema(arrayItemBuilder.build()); } } + + public static boolean isJsonMapSchema(Schema schema) { + return schema == MAP_SCHEMA; + } + } diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/swagger/SwaggerGenerator.java b/endpoints-framework/src/main/java/com/google/api/server/spi/swagger/SwaggerGenerator.java index 83567565..d298ea82 100644 --- a/endpoints-framework/src/main/java/com/google/api/server/spi/swagger/SwaggerGenerator.java +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/swagger/SwaggerGenerator.java @@ -32,6 +32,7 @@ import com.google.api.server.spi.config.model.FieldType; import com.google.api.server.spi.config.model.Schema; import com.google.api.server.spi.config.model.Schema.Field; +import com.google.api.server.spi.config.model.Schema.SchemaReference; import com.google.api.server.spi.config.model.SchemaRepository; import com.google.api.server.spi.config.model.Types; import com.google.api.server.spi.config.validation.ApiConfigValidator; @@ -46,6 +47,8 @@ import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.ImmutableSortedMap.Builder; import com.google.common.reflect.TypeToken; import java.lang.reflect.Type; @@ -53,12 +56,15 @@ import java.net.URL; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.TreeMap; +import io.swagger.models.ExternalDocs; import io.swagger.models.Info; import io.swagger.models.Model; import io.swagger.models.ModelImpl; @@ -68,10 +74,12 @@ import io.swagger.models.Response; import io.swagger.models.Scheme; import io.swagger.models.Swagger; +import io.swagger.models.Tag; import io.swagger.models.auth.ApiKeyAuthDefinition; import io.swagger.models.auth.In; import io.swagger.models.auth.OAuth2Definition; import io.swagger.models.auth.SecuritySchemeDefinition; +import io.swagger.models.parameters.AbstractSerializableParameter; import io.swagger.models.parameters.BodyParameter; import io.swagger.models.parameters.PathParameter; import io.swagger.models.parameters.QueryParameter; @@ -85,7 +93,10 @@ import io.swagger.models.properties.FloatProperty; import io.swagger.models.properties.IntegerProperty; import io.swagger.models.properties.LongProperty; +import io.swagger.models.properties.MapProperty; +import io.swagger.models.properties.ObjectProperty; import io.swagger.models.properties.Property; +import io.swagger.models.properties.PropertyBuilder; import io.swagger.models.properties.RefProperty; import io.swagger.models.properties.StringProperty; @@ -162,6 +173,10 @@ public class SwaggerGenerator { .put(FieldType.INT64, LongProperty.class) .put(FieldType.STRING, StringProperty.class) .build(); + //expected "additionalProperties: true" for free-from objects is not possible with Java API + //using an object property with empty properties is semantically identical + private static final ObjectProperty FREE_FORM_PROPERTY = new ObjectProperty() + .properties(Collections.emptyMap()); private static final Function CONFIG_TO_ROOTLESS_KEY = new Function() { @@ -171,14 +186,13 @@ public ApiKey apply(ApiConfig config) { } }; - public Swagger writeSwagger(Iterable configs, boolean writeInternal, - SwaggerContext context) throws ApiConfigException { + public Swagger writeSwagger(Iterable configs, SwaggerContext context) + throws ApiConfigException { try { TypeLoader typeLoader = new TypeLoader(SwaggerGenerator.class.getClassLoader()); SchemaRepository repo = new SchemaRepository(typeLoader); GenerationContext genCtx = new GenerationContext(); genCtx.validator = new ApiConfigValidator(typeLoader, repo); - genCtx.writeInternal = writeInternal; genCtx.schemata = new SchemaRepository(typeLoader); return writeSwagger(configs, context, genCtx); } catch (ClassNotFoundException e) { @@ -203,6 +217,12 @@ private Swagger writeSwagger(Iterable configs, SwaggerContext context for (ApiKey apiKey : configsByKey.keySet()) { writeApi(apiKey, configsByKey.get(apiKey), swagger, genCtx); } + //reorder paths by string order to have consistent output + Builder builder = ImmutableSortedMap.naturalOrder(); + for (Entry pathEntry : swagger.getPaths().entrySet()) { + builder.put(pathEntry); + } + swagger.paths(builder.build()); writeQuotaDefinitions(swagger, genCtx); return swagger; } @@ -244,15 +264,34 @@ private void writeApi(ApiKey apiKey, ImmutableList apiConfi addNonConflictingApiLimitMetric(genCtx.limitMetrics, limitMetric); } writeApiClass(apiConfig, swagger, genCtx); + swagger.tag(getTag(apiConfig)); } List schemas = genCtx.schemata.getAllSchemaForApi(apiKey); for (Schema schema : schemas) { - if (schema.enumValues().isEmpty()) { + if (isNotInlined(schema)) { getOrCreateDefinitionMap(swagger).put(schema.name(), convertToSwaggerSchema(schema)); } } } + //enum and map schemas are inlined, shouldn't be in the model definitions + private boolean isNotInlined(Schema schema) { + return schema.enumValues().isEmpty() && schema.mapValueSchema() == null; + } + + private Tag getTag(ApiConfig apiConfig) { + Tag tag = new Tag().name(getTagName(apiConfig)); + String description = apiConfig.getDescription(); + if (!Strings.isEmptyOrWhitespace(description)) { + tag.description(description); + } + String documentationLink = apiConfig.getDocumentationLink(); + if (!Strings.isEmptyOrWhitespace(documentationLink)) { + tag.externalDocs(new ExternalDocs().url(documentationLink)); + } + return tag; + } + private void addNonConflictingApiLimitMetric( Map limitMetrics, ApiLimitMetricConfig limitMetric) throws ApiConfigException { @@ -282,21 +321,28 @@ private void writeApiMethod( Path path = getOrCreatePath(swagger, methodConfig); Operation operation = new Operation(); operation.setOperationId(getOperationId(apiConfig, methodConfig)); + operation.setTags(Collections.singletonList(getTagName(apiConfig))); operation.setDescription(methodConfig.getDescription()); + operation.setDeprecated(methodConfig.isDeprecated() ? true : null); Collection pathParameters = methodConfig.getPathParameters(); for (ApiParameterConfig parameterConfig : methodConfig.getParameterConfigs()) { switch (parameterConfig.getClassification()) { case API_PARAMETER: boolean isPathParameter = pathParameters.contains(parameterConfig.getName()); - SerializableParameter parameter = + AbstractSerializableParameter parameter = isPathParameter ? new PathParameter() : new QueryParameter(); parameter.setName(parameterConfig.getName()); parameter.setDescription(parameterConfig.getDescription()); + String defaultValue = parameterConfig.getDefaultValue(); + if (!Strings.isEmptyOrWhitespace(defaultValue)) { + parameter.setDefaultValue(defaultValue); + } boolean required = isPathParameter || (!parameterConfig.getNullable() - && parameterConfig.getDefaultValue() == null); + && defaultValue == null); if (parameterConfig.isRepeated()) { TypeToken t = parameterConfig.getRepeatedItemSerializedType(); parameter.setType("array"); + parameter.setCollectionFormat("multi"); Property p = getSwaggerArrayProperty(t); if (parameterConfig.isEnum()) { // TODO: Not sure if this is the right check ((StringProperty) p).setEnum(getEnumValues(t)); @@ -320,7 +366,7 @@ private void writeApiMethod( Schema schema = genCtx.schemata.getOrAdd(requestType, apiConfig); BodyParameter bodyParameter = new BodyParameter(); bodyParameter.setName("body"); - bodyParameter.setSchema(new RefModel(schema.name()).asDefault(schema.name())); + bodyParameter.setSchema(getSchema(schema)); operation.addParameter(bodyParameter); break; case UNKNOWN: @@ -334,7 +380,7 @@ private void writeApiMethod( TypeToken returnType = ApiAnnotationIntrospector.getSchemaType(methodConfig.getReturnType(), apiConfig); Schema schema = genCtx.schemata.getOrAdd(returnType, apiConfig); - response.setResponseSchema(new RefModel(schema.name()).asDefault(schema.name())); + response.setResponseSchema(getSchema(schema)); } operation.response(200, response); writeAuthConfig(swagger, methodConfig, operation); @@ -359,6 +405,17 @@ private void writeApiMethod( addDefinedMetricCosts(genCtx.limitMetrics, operation, methodConfig.getMetricCosts()); } + private Model getSchema(Schema schema) { + if (SchemaRepository.isJsonMapSchema(schema)) { + return new ModelImpl().additionalProperties(FREE_FORM_PROPERTY); + } + Field mapField = schema.mapValueSchema(); + if (mapField != null) { + return new ModelImpl().additionalProperties(convertToSwaggerProperty(mapField)); + } + return new RefModel(schema.name()).asDefault(schema.name()); + } + private void writeAuthConfig(Swagger swagger, ApiMethodConfig methodConfig, Operation operation) throws ApiConfigException { ApiIssuerAudienceConfig issuerAudiences = methodConfig.getIssuerAudiences(); @@ -368,12 +425,15 @@ private void writeAuthConfig(Swagger swagger, ApiMethodConfig methodConfig, Oper if (issuerAudiencesIsEmpty && legacyAudiencesIsEmpty) { return; } + ImmutableList scopes = ImmutableList + .copyOf(methodConfig.getScopeExpression().getAllScopes()); if (!issuerAudiencesIsEmpty) { for (String issuer : issuerAudiences.getIssuerNames()) { ImmutableSet audiences = issuerAudiences.getAudiences(issuer); IssuerConfig issuerConfig = methodConfig.getApiConfig().getIssuers().getIssuer(issuer); String fullIssuer = addNonConflictingSecurityDefinition(swagger, issuerConfig, audiences); - operation.addSecurity(fullIssuer, ImmutableList.of()); + operation.addSecurity(fullIssuer, issuerConfig.isUseScopesInAuthFlow() ? scopes : + Collections.emptyList()); } } if (!legacyAudiencesIsEmpty) { @@ -382,8 +442,8 @@ private void writeAuthConfig(Swagger swagger, ApiMethodConfig methodConfig, Oper swagger, ApiIssuerConfigs.GOOGLE_ID_TOKEN_ISSUER, legacyAudienceSet); String fullAltIssuer = addNonConflictingSecurityDefinition( swagger, ApiIssuerConfigs.GOOGLE_ID_TOKEN_ISSUER_ALT, legacyAudienceSet); - operation.addSecurity(fullIssuer, ImmutableList.of()); - operation.addSecurity(fullAltIssuer, ImmutableList.of()); + operation.addSecurity(fullIssuer, scopes); + operation.addSecurity(fullAltIssuer, scopes); } } @@ -412,6 +472,7 @@ private Model convertToSwaggerSchema(Schema schema) { } docSchema.setProperties(fields); } + //map schema should be inlined, but handling anyway if (schema.mapValueSchema() != null) { docSchema.setAdditionalProperties(convertToSwaggerProperty(schema.mapValueSchema())); } @@ -428,13 +489,18 @@ private Property convertToSwaggerProperty(Field f) { //cannot happen, as Property subclasses are guaranteed to have a default constructor } } else { + SchemaReference schemaReference = f.schemaReference(); if (f.type() == FieldType.OBJECT) { - String name = f.schemaReference().get().name(); - p = new RefProperty(name).asDefault(name); + if (schemaReference.type().isSubtypeOf(Map.class)) { + p = inlineMapProperty(schemaReference); + } else { + String name = schemaReference.get().name(); + p = new RefProperty(name).asDefault(name); + } } else if (f.type() == FieldType.ARRAY) { p = new ArrayProperty(convertToSwaggerProperty(f.arrayItemSchema())); } else if (f.type() == FieldType.ENUM) { - p = new StringProperty()._enum(getEnumValues(f.schemaReference().type())); + p = new StringProperty()._enum(getEnumValues(schemaReference.type())); } } if (p == null) { @@ -447,10 +513,30 @@ private Property convertToSwaggerProperty(Field f) { return p; } + private MapProperty inlineMapProperty(SchemaReference schemaReference) { + Schema schema = schemaReference.repository() + .get(schemaReference.type(), schemaReference.apiConfig()); + Field mapField = schema.mapValueSchema(); + if (SchemaRepository.isJsonMapSchema(schema) + || mapField == null) { //map field should not be null for non-JsonMap schema, handling anyway + return new MapProperty(FREE_FORM_PROPERTY); + } + return new MapProperty(convertToSwaggerProperty(mapField)); + } + + private static String getTagName(ApiConfig apiConfig) { + String tag = apiConfig.getName() + ":" + apiConfig.getVersion(); + String resource = apiConfig.getApiClassConfig().getResource(); + if (!Strings.isEmptyOrWhitespace(resource)) { + tag += "." + CONVERTER.convert(resource); + } + return tag; + } + private static String getOperationId(ApiConfig apiConfig, ApiMethodConfig methodConfig) { return FluentIterable.of(apiConfig.getName(), apiConfig.getVersion(), - apiConfig.getResource(), apiConfig.getApiClassConfig().getResource(), - methodConfig.getEndpointMethodName()).transform(CONVERTER).join(JOINER); + apiConfig.getApiClassConfig().getResource(), methodConfig.getEndpointMethodName()) + .transform(CONVERTER).join(JOINER); } private static Property getSwaggerArrayProperty(TypeToken typeToken) { @@ -468,7 +554,10 @@ private static Property getSwaggerArrayProperty(TypeToken typeToken) { } else if (type == Double.class || type == Double.TYPE) { return new DoubleProperty(); } else if (type == byte[].class) { - return new ByteArrayProperty(); + ByteArrayProperty property = new ByteArrayProperty(); + //this will add a base64 pattern to the property + return PropertyBuilder.build(property.getType(), property.getFormat(), + Collections.emptyMap()); } else if (type.isEnum()) { return new StringProperty(); } @@ -491,7 +580,8 @@ private static List getEnumValues(TypeToken t) { private static SecuritySchemeDefinition toScheme( IssuerConfig issuerConfig, ImmutableSet audiences) { - OAuth2Definition tokenDef = new OAuth2Definition().implicit(""); + OAuth2Definition tokenDef = new OAuth2Definition() + .implicit(issuerConfig.getAuthorizationUrl()); tokenDef.setVendorExtension("x-google-issuer", issuerConfig.getIssuer()); if (!com.google.common.base.Strings.isNullOrEmpty(issuerConfig.getJwksUri())) { tokenDef.setVendorExtension("x-google-jwks_uri", issuerConfig.getJwksUri()); @@ -535,6 +625,7 @@ private static String addNonConflictingSecurityDefinition( return issuerPlusHash; } + //TODO add title and description public static class SwaggerContext { private Scheme scheme = Scheme.HTTPS; private String hostname = "myapi.appspot.com"; @@ -581,7 +672,6 @@ public SwaggerContext setDocVersion(String docVersion) { private static class GenerationContext { private final Map limitMetrics = new TreeMap<>(); private ApiConfigValidator validator; - private boolean writeInternal; private SchemaRepository schemata; } } diff --git a/endpoints-framework/src/main/java/com/google/api/server/spi/swagger/UNSUPPORTED_FEATURES.md b/endpoints-framework/src/main/java/com/google/api/server/spi/swagger/UNSUPPORTED_FEATURES.md new file mode 100644 index 00000000..3471ecee --- /dev/null +++ b/endpoints-framework/src/main/java/com/google/api/server/spi/swagger/UNSUPPORTED_FEATURES.md @@ -0,0 +1,18 @@ +- Info + - **title, description => visible in Endpoints Portal** + - license, contact, terms of service +- Paths + - Configurable tag content and format +- Parameters + - **common parameters (global and per-path) => make spec shorter** + - range for ints (minimum, maximum) + - cardinality (minItems, maxItems) + - other repeated param features (uniqueItems, default values) + - empty value parameters + - headers in params +- Models + - required properties (need new annotation property) +- Responses + - **schema for Google JSON error format as default for errors** + - **error code descriptions** => use introspection to analyze throws of subclasses of ServiceException + - headers in response \ No newline at end of file diff --git a/endpoints-framework/src/test/java/com/google/api/server/spi/MethodHierarchyReaderTest.java b/endpoints-framework/src/test/java/com/google/api/server/spi/MethodHierarchyReaderTest.java index 998fd311..0a9d6cd1 100644 --- a/endpoints-framework/src/test/java/com/google/api/server/spi/MethodHierarchyReaderTest.java +++ b/endpoints-framework/src/test/java/com/google/api/server/spi/MethodHierarchyReaderTest.java @@ -35,6 +35,7 @@ import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; @@ -84,7 +85,7 @@ private void verifySingleMethod(Method method) { } } - private void verifyOverrides(Method method, List overrides) { + private void verifyOverrides(Method method, Collection overrides) { TestEndpoint.ExpectedMethod expectedMethod = TestEndpoint.ExpectedMethod.fromName(method.getName()); @@ -92,7 +93,7 @@ private void verifyOverrides(Method method, List overrides) { assertEquals("Wrong number of overrides for method: " + method.getName(), 2, overrides.size()); assertEquals("Overridden " + method.getName() + " is wrong class.", - TestEndpointSuperclass.class, overrides.get(1).getMethod().getDeclaringClass()); + TestEndpointSuperclass.class, Iterables.get(overrides, 1).getMethod().getDeclaringClass()); } else { assertEquals("Wrong number of overrides for method: " + method.getName(), 1, overrides.size()); @@ -112,11 +113,11 @@ public void testGetLeafMethods() { @Test public void testGetEndpointOverrides() { - Iterable> methods = methodReader.getEndpointOverrides(); + Iterable> methods = methodReader.getEndpointOverrides(); TestHelper helper = new TestHelper(Iterables.size(methods)); - for (List overrides : methods) { - Method method = overrides.get(0).getMethod(); + for (Collection overrides : methods) { + Method method = overrides.iterator().next().getMethod(); helper.verifySingleMethod(method); verifyOverrides(method, overrides); } diff --git a/endpoints-framework/src/test/java/com/google/api/server/spi/config/annotationreader/ApiConfigAnnotationReaderTest.java b/endpoints-framework/src/test/java/com/google/api/server/spi/config/annotationreader/ApiConfigAnnotationReaderTest.java index a11087d9..b07a2779 100644 --- a/endpoints-framework/src/test/java/com/google/api/server/spi/config/annotationreader/ApiConfigAnnotationReaderTest.java +++ b/endpoints-framework/src/test/java/com/google/api/server/spi/config/annotationreader/ApiConfigAnnotationReaderTest.java @@ -105,6 +105,7 @@ /** * Tests for {@link ApiConfigAnnotationReader}. + * TODO test deprecation */ @RunWith(JUnit4.class) public class ApiConfigAnnotationReaderTest { diff --git a/endpoints-framework/src/test/java/com/google/api/server/spi/config/model/ApiIssuerConfigsTest.java b/endpoints-framework/src/test/java/com/google/api/server/spi/config/model/ApiIssuerConfigsTest.java index fdf5479c..f5ca03c5 100644 --- a/endpoints-framework/src/test/java/com/google/api/server/spi/config/model/ApiIssuerConfigsTest.java +++ b/endpoints-framework/src/test/java/com/google/api/server/spi/config/model/ApiIssuerConfigsTest.java @@ -23,19 +23,19 @@ public void isSpecified() { public void asMap() { assertThat(ApiIssuerConfigs.builder().build().asMap()).isEmpty(); ApiIssuerConfigs configs = ApiIssuerConfigs.builder() - .addIssuer(new IssuerConfig("issuerName", "issuer", "jwks")) + .addIssuer(new IssuerConfig("issuerName", "issuer", "jwks", "authUrl", true)) .build(); assertThat(configs.asMap()).containsExactly( - "issuerName", new IssuerConfig("issuerName", "issuer", "jwks")); + "issuerName", new IssuerConfig("issuerName", "issuer", "jwks", "authUrl", true)); } @Test public void equals() { ApiIssuerConfigs configs1 = ApiIssuerConfigs.builder() - .addIssuer(new IssuerConfig("issuerName", "issuer", "jwks")) + .addIssuer(new IssuerConfig("issuerName", "issuer", "jwks", "authUrl", true)) .build(); ApiIssuerConfigs configs2 = ApiIssuerConfigs.builder() - .addIssuer(new IssuerConfig("issuerName", "issuer", "jwks")) + .addIssuer(new IssuerConfig("issuerName", "issuer", "jwks", "authUrl", true)) .build(); assertThat(configs1).isEqualTo(configs2); assertThat(configs1).isNotEqualTo(ApiIssuerConfigs.UNSPECIFIED); diff --git a/endpoints-framework/src/test/java/com/google/api/server/spi/swagger/SwaggerGeneratorTest.java b/endpoints-framework/src/test/java/com/google/api/server/spi/swagger/SwaggerGeneratorTest.java index 471ba6ea..7f913700 100644 --- a/endpoints-framework/src/test/java/com/google/api/server/spi/swagger/SwaggerGeneratorTest.java +++ b/endpoints-framework/src/test/java/com/google/api/server/spi/swagger/SwaggerGeneratorTest.java @@ -18,7 +18,6 @@ import static com.google.api.server.spi.config.model.EndpointsFlag.MAP_SCHEMA_FORCE_JSON_MAP_SCHEMA; import static com.google.api.server.spi.config.model.EndpointsFlag.MAP_SCHEMA_IGNORE_UNSUPPORTED_KEY_TYPES; import static com.google.api.server.spi.config.model.EndpointsFlag.MAP_SCHEMA_SUPPORT_ARRAYS_VALUES; -import static com.google.common.truth.Truth.assertThat; import com.google.api.server.spi.Constant; import com.google.api.server.spi.IoUtil; @@ -47,9 +46,7 @@ import com.google.api.server.spi.testing.MultiResourceEndpoint.Resource2Endpoint; import com.google.api.server.spi.testing.MultiVersionEndpoint.Version1Endpoint; import com.google.api.server.spi.testing.MultiVersionEndpoint.Version2Endpoint; -import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Multimap; import com.fasterxml.jackson.databind.ObjectMapper; @@ -59,15 +56,9 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import io.swagger.models.HttpMethod; -import io.swagger.models.Operation; -import io.swagger.models.Path; import io.swagger.models.Swagger; import io.swagger.util.Json; -import java.util.Collection; -import java.util.Map.Entry; - /** * Tests for {@link SwaggerGenerator}. */ @@ -91,7 +82,7 @@ public void setUp() throws Exception { @Test public void testWriteSwagger_FooEndpoint() throws Exception { ApiConfig config = configLoader.loadConfiguration(ServiceContext.create(), FooEndpoint.class); - Swagger swagger = generator.writeSwagger(ImmutableList.of(config), false, context); + Swagger swagger = generator.writeSwagger(ImmutableList.of(config), context); Swagger expected = readExpectedAsSwagger("foo_endpoint.swagger"); checkSwagger(expected, swagger); } @@ -99,7 +90,7 @@ public void testWriteSwagger_FooEndpoint() throws Exception { @Test public void testWriteSwagger_FooEndpointDefaultContext() throws Exception { ApiConfig config = configLoader.loadConfiguration(ServiceContext.create(), FooEndpoint.class); - Swagger swagger = generator.writeSwagger(ImmutableList.of(config), false, new SwaggerContext()); + Swagger swagger = generator.writeSwagger(ImmutableList.of(config), new SwaggerContext()); Swagger expected = readExpectedAsSwagger("foo_endpoint_default_context.swagger"); checkSwagger(expected, swagger); } @@ -107,28 +98,28 @@ public void testWriteSwagger_FooEndpointDefaultContext() throws Exception { @Test public void testWriteSwagger_FooEndpointLocalhost() throws Exception { Swagger swagger = getSwagger( - FooEndpoint.class, new SwaggerContext().setApiRoot("http://localhost:8080/api"), false); + FooEndpoint.class, new SwaggerContext().setApiRoot("http://localhost:8080/api")); Swagger expected = readExpectedAsSwagger("foo_endpoint_localhost.swagger"); checkSwagger(expected, swagger); } @Test public void testWriteSwagger_EnumEndpoint() throws Exception { - Swagger swagger = getSwagger(EnumEndpoint.class, new SwaggerContext(), true); + Swagger swagger = getSwagger(EnumEndpoint.class, new SwaggerContext()); Swagger expected = readExpectedAsSwagger("enum_endpoint.swagger"); checkSwagger(expected, swagger); } @Test public void testWriteSwagger_ArrayEndpoint() throws Exception { - Swagger swagger = getSwagger(ArrayEndpoint.class, new SwaggerContext(), true); + Swagger swagger = getSwagger(ArrayEndpoint.class, new SwaggerContext()); Swagger expected = readExpectedAsSwagger("array_endpoint.swagger"); checkSwagger(expected, swagger); } @Test public void testWriteSwagger_MapEndpoint() throws Exception { - Swagger swagger = getSwagger(MapEndpoint.class, new SwaggerContext(), true); + Swagger swagger = getSwagger(MapEndpoint.class, new SwaggerContext()); Swagger expected = readExpectedAsSwagger("map_endpoint.swagger"); checkSwagger(expected, swagger); } @@ -137,7 +128,7 @@ public void testWriteSwagger_MapEndpoint() throws Exception { public void testWriteSwagger_MapEndpoint_Legacy() throws Exception { System.setProperty(MAP_SCHEMA_FORCE_JSON_MAP_SCHEMA.systemPropertyName, ""); try { - Swagger swagger = getSwagger(MapEndpoint.class, new SwaggerContext(), true); + Swagger swagger = getSwagger(MapEndpoint.class, new SwaggerContext()); Swagger expected = readExpectedAsSwagger("map_endpoint_legacy.swagger"); checkSwagger(expected, swagger); } finally { @@ -148,7 +139,7 @@ public void testWriteSwagger_MapEndpoint_Legacy() throws Exception { @Test public void testWriteDiscovery_MapEndpoint_InvalidKeyType() throws Exception { try { - getSwagger(MapEndpointInvalid.class, new SwaggerContext(), true); + getSwagger(MapEndpointInvalid.class, new SwaggerContext()); Assert.fail("Should have failed to generate schema for invalid key type"); } catch (IllegalArgumentException e) { //expected @@ -159,7 +150,7 @@ public void testWriteDiscovery_MapEndpoint_InvalidKeyType() throws Exception { public void testWriteDiscovery_MapEndpoint_InvalidKeyType_ignore() throws Exception { System.setProperty(MAP_SCHEMA_IGNORE_UNSUPPORTED_KEY_TYPES.systemPropertyName, "true"); try { - getSwagger(MapEndpointInvalid.class, new SwaggerContext(), true); + getSwagger(MapEndpointInvalid.class, new SwaggerContext()); } finally { System.clearProperty(MAP_SCHEMA_IGNORE_UNSUPPORTED_KEY_TYPES.systemPropertyName); } @@ -169,7 +160,7 @@ public void testWriteDiscovery_MapEndpoint_InvalidKeyType_ignore() throws Except public void testWriteSwagger_MapEndpoint_WithArrayValue() throws Exception { System.setProperty(MAP_SCHEMA_SUPPORT_ARRAYS_VALUES.systemPropertyName, "TRUE"); try { - Swagger swagger = getSwagger(MapEndpoint.class, new SwaggerContext(), true); + Swagger swagger = getSwagger(MapEndpoint.class, new SwaggerContext()); Swagger expected = readExpectedAsSwagger("map_endpoint_with_array.swagger"); checkSwagger(expected, swagger); } finally { @@ -177,19 +168,11 @@ public void testWriteSwagger_MapEndpoint_WithArrayValue() throws Exception { } } - @Test - public void testWriteSwagger_FooEndpoint_internal() throws Exception { - ApiConfig config = configLoader.loadConfiguration(ServiceContext.create(), FooEndpoint.class); - Swagger swagger = generator.writeSwagger(ImmutableList.of(config), true, context); - Swagger expected = readExpectedAsSwagger("foo_endpoint_internal.swagger"); - checkSwagger(expected, swagger); - } - @Test public void testWriteSwagger_ThirdPartyAuthEndpoint() throws Exception { ApiConfig config = configLoader.loadConfiguration(ServiceContext.create(), ThirdPartyAuthEndpoint.class); - Swagger swagger = generator.writeSwagger(ImmutableList.of(config), true, context); + Swagger swagger = generator.writeSwagger(ImmutableList.of(config), context); Swagger expected = readExpectedAsSwagger("third_party_auth.swagger"); checkSwagger(expected, swagger); } @@ -198,7 +181,7 @@ public void testWriteSwagger_ThirdPartyAuthEndpoint() throws Exception { public void testWriteSwagger_GoogleAuthEndpoint() throws Exception { ApiConfig config = configLoader.loadConfiguration(ServiceContext.create(), GoogleAuthEndpoint.class); - Swagger swagger = generator.writeSwagger(ImmutableList.of(config), true, context); + Swagger swagger = generator.writeSwagger(ImmutableList.of(config), context); Swagger expected = readExpectedAsSwagger("google_auth.swagger"); checkSwagger(expected, swagger); } @@ -207,28 +190,28 @@ public void testWriteSwagger_GoogleAuthEndpoint() throws Exception { public void testWriteSwagger_ApiKeys() throws Exception { ApiConfig config = configLoader.loadConfiguration(ServiceContext.create(), ApiKeysEndpoint.class); - Swagger swagger = generator.writeSwagger(ImmutableList.of(config), true, context); + Swagger swagger = generator.writeSwagger(ImmutableList.of(config), context); Swagger expected = readExpectedAsSwagger("api_keys.swagger"); checkSwagger(expected, swagger); } @Test public void testWriteSwagger_AbsolutePathEndpoint() throws Exception { - Swagger swagger = getSwagger(AbsolutePathEndpoint.class, new SwaggerContext(), true); + Swagger swagger = getSwagger(AbsolutePathEndpoint.class, new SwaggerContext()); Swagger expected = readExpectedAsSwagger("absolute_path_endpoint.swagger"); checkSwagger(expected, swagger); } @Test public void testWriteSwagger_AbsoluteCommonPathEndpoint() throws Exception { - Swagger swagger = getSwagger(AbsoluteCommonPathEndpoint.class, new SwaggerContext(), true); + Swagger swagger = getSwagger(AbsoluteCommonPathEndpoint.class, new SwaggerContext()); Swagger expected = readExpectedAsSwagger("absolute_common_path_endpoint.swagger"); checkSwagger(expected, swagger); } @Test public void testWriteSwagger_LimitMetricsEndpoint() throws Exception { - Swagger swagger = getSwagger(LimitMetricsEndpoint.class, new SwaggerContext(), true); + Swagger swagger = getSwagger(LimitMetricsEndpoint.class, new SwaggerContext()); Swagger expected = readExpectedAsSwagger("limit_metrics_endpoint.swagger"); checkSwagger(expected, swagger); } @@ -236,7 +219,7 @@ public void testWriteSwagger_LimitMetricsEndpoint() throws Exception { @Test public void testWriteSwagger_FooEndpointWithDescription() throws Exception { ApiConfig config = configLoader.loadConfiguration(ServiceContext.create(), FooDescriptionEndpoint.class); - Swagger swagger = generator.writeSwagger(ImmutableList.of(config), false, context); + Swagger swagger = generator.writeSwagger(ImmutableList.of(config), context); Swagger expected = readExpectedAsSwagger("foo_with_description_endpoint.swagger"); checkSwagger(expected, swagger); } @@ -248,7 +231,7 @@ public void testWriteSwagger_MultiResourceEndpoint() throws Exception { configLoader.loadConfiguration(serviceContext, NoResourceEndpoint.class), configLoader.loadConfiguration(serviceContext, Resource1Endpoint.class), configLoader.loadConfiguration(serviceContext, Resource2Endpoint.class)); - Swagger swagger = generator.writeSwagger(configs, false, context); + Swagger swagger = generator.writeSwagger(configs, context); Swagger expected = readExpectedAsSwagger("multi_resource_endpoint.swagger"); checkSwagger(expected, swagger); } @@ -259,15 +242,15 @@ public void testWriteSwagger_MultiVersionEndpoint() throws Exception { ImmutableList configs = ImmutableList.of( configLoader.loadConfiguration(serviceContext, Version1Endpoint.class), configLoader.loadConfiguration(serviceContext, Version2Endpoint.class)); - Swagger swagger = generator.writeSwagger(configs, false, context); + Swagger swagger = generator.writeSwagger(configs, context); Swagger expected = readExpectedAsSwagger("multi_version_endpoint.swagger"); checkSwagger(expected, swagger); } - private Swagger getSwagger(Class serviceClass, SwaggerContext context, boolean internal) + private Swagger getSwagger(Class serviceClass, SwaggerContext context) throws Exception { ApiConfig config = configLoader.loadConfiguration(ServiceContext.create(), serviceClass); - return generator.writeSwagger(ImmutableList.of(config), internal, context); + return generator.writeSwagger(ImmutableList.of(config), context); } private Swagger readExpectedAsSwagger(String file) throws Exception { @@ -276,50 +259,8 @@ private Swagger readExpectedAsSwagger(String file) throws Exception { } private void checkSwagger(Swagger expected, Swagger actual) throws Exception { - compareSwagger(expected, actual); - // operationIds should be unique to be deployed on Endpoints Management - checkDuplicateOperations(actual); - // Jackson preserves order when deserializing expected result, and SwaggerGenerator should - // always output resource and security definitions in the same order - checkOrdering(expected, actual); - } - - private void compareSwagger(Swagger expected, Swagger actual) throws Exception { - System.out.println("Actual: " + mapper.writeValueAsString(actual)); - System.out.println("Expected: " + mapper.writeValueAsString(expected)); - assertThat(actual).isEqualTo(expected); - // TODO: Remove once Swagger models check this in equals - assertThat(actual.getVendorExtensions()).isEqualTo(expected.getVendorExtensions()); - } - - private void checkDuplicateOperations(Swagger actual) { - Multimap operationIds = HashMultimap.create(); - for (Entry pathEntry : actual.getPaths().entrySet()) { - for (Entry opEntry : pathEntry.getValue().getOperationMap() - .entrySet()) { - operationIds - .put(opEntry.getValue().getOperationId(), pathEntry.getKey() + "|" + opEntry.getKey()); - } - } - int duplicateOperationIdCount = 0; - for (Entry> entry : operationIds.asMap().entrySet()) { - if (entry.getValue().size() > 1) { - System.out.println("Duplicate operation id: " + entry); - duplicateOperationIdCount++; - } - } - assertThat(duplicateOperationIdCount).named("Duplicate operation ids").isEqualTo(0); - } - - private void checkOrdering(Swagger expected, Swagger actual) { - if (expected.getSecurityDefinitions() != null && actual.getSecurityDefinitions() != null) { - assertThat(ImmutableList.of(expected.getSecurityDefinitions().keySet())) - .isEqualTo(ImmutableList.of(actual.getSecurityDefinitions().keySet())); - } - if (expected.getDefinitions() != null && actual.getDefinitions() != null) { - assertThat(ImmutableList.of(expected.getDefinitions().keySet())) - .isEqualTo(ImmutableList.of(actual.getDefinitions().keySet())); - } + SwaggerSubject.assertThat(actual).isSameAs(expected); + SwaggerSubject.assertThat(actual).hasNoDuplicateOperations(); } @Api(name = "thirdparty", version = "v1", @@ -345,7 +286,7 @@ public void noOverride() { } private static class GoogleAuthEndpoint extends ThirdPartyAuthEndpoint { @ApiMethod( issuerAudiences = { - @ApiIssuerAudience(name = Constant.GOOGLE_ID_TOKEN_NAME, audiences = "googleaud") + @ApiIssuerAudience(name = Constant.GOOGLE_ID_TOKEN_ALT, audiences = "googleaud") } ) public void googleAuth() { } diff --git a/endpoints-framework/src/test/java/com/google/api/server/spi/swagger/SwaggerSubject.java b/endpoints-framework/src/test/java/com/google/api/server/spi/swagger/SwaggerSubject.java new file mode 100644 index 00000000..4ba9eaac --- /dev/null +++ b/endpoints-framework/src/test/java/com/google/api/server/spi/swagger/SwaggerSubject.java @@ -0,0 +1,111 @@ +package com.google.api.server.spi.swagger; + +import static com.google.common.truth.Truth.assertAbout; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import com.google.common.truth.Fact; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Subject; +import io.swagger.models.HttpMethod; +import io.swagger.models.Operation; +import io.swagger.models.Path; +import io.swagger.models.Swagger; +import io.swagger.util.Json; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.junit.ComparisonFailure; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; + +import javax.annotation.Nullable; + +public final class SwaggerSubject extends Subject { + + private final ObjectWriter witer = Json.mapper().writerWithDefaultPrettyPrinter(); + + private final Swagger actual; + + public static SwaggerSubject assertThat(@NullableDecl Swagger swagger) { + return assertAbout(swaggers()).that(swagger); + } + + private static Factory swaggers() { + return SwaggerSubject::new; + } + + private SwaggerSubject(FailureMetadata failureStrategy, @Nullable Object actual) { + super(failureStrategy, actual); + this.actual = actual instanceof Swagger ? (Swagger) actual : null; + } + + void hasNoDuplicateOperations() { + Multimap operationIds = HashMultimap.create(); + for (Entry pathEntry : actual.getPaths().entrySet()) { + for (Entry opEntry : pathEntry.getValue().getOperationMap() + .entrySet()) { + operationIds + .put(opEntry.getValue().getOperationId(), pathEntry.getKey() + "|" + opEntry.getKey()); + } + } + Set duplicateOperationIds = Sets.newHashSet(); + for (Entry> entry : operationIds.asMap().entrySet()) { + if (entry.getValue().size() > 1) { + System.out.println("Duplicate operation id: " + entry); + duplicateOperationIds.add(entry.getKey()); + } + } + if (!duplicateOperationIds.isEmpty()) { + failWithActual("Duplicates operations ids found", duplicateOperationIds); + } + } + + void isSameAs(Swagger expected) { + checkEquality(expected); + // Jackson preserves order when deserializing expected result, so we should + // always output resource and security definitions in the same order + compareMapOrdering("Security definition", actual, expected, Swagger::getSecurityDefinitions); + compareMapOrdering("Model definition", actual, expected, Swagger::getDefinitions); + compareMapOrdering("Path", actual, expected, Swagger::getPaths); + } + + private void checkEquality(Swagger expected) { + if (!Objects.equals(actual, expected)) { + throw new ComparisonFailure("Swagger specs don't match", + toString(expected), toString(actual)); + } + } + + private void compareMapOrdering(String message, Swagger actual, + Swagger expected, Function> mapFunction) { + Map actualMap = mapFunction.apply(actual); + Map expectedMap = mapFunction.apply(expected); + if (expectedMap != null && actualMap != null) { + Set actualKeys = actualMap.keySet(); + Set expectedKeys = expectedMap.keySet(); + if (!ImmutableList.copyOf(actualKeys).equals(ImmutableList.copyOf(expectedKeys))) { + throw new ComparisonFailure(message + " orders don't match" + + Fact.fact("\nExpected keys", expectedKeys).toString() + + Fact.fact("\nActual keys", actualKeys).toString(), + toString(expected), toString(actual)); + } + } + } + + private String toString(Swagger expected) { + try { + return witer.writeValueAsString(expected); + } catch (JsonProcessingException e) { + throw new AssertionError("Cannot create String representation for specs", e); + } + } + +} 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 da404334..0a84ef8b 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 @@ -211,6 +211,218 @@ "scopes": [ "https://www.googleapis.com/auth/userinfo.email" ] + }, + "setListOfBooleans": { + "httpMethod": "POST", + "id": "myapi.arrayEndpoint.setListOfBooleans", + "parameterOrder": [ + "list", + "array" + ], + "parameters": { + "list": { + "location": "path", + "repeated": true, + "required": true, + "type": "boolean" + }, + "array": { + "location": "path", + "repeated": true, + "required": true, + "type": "boolean" + } + }, + "path": "setListOfBooleans/{list}/{array}", + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "setListOfByteArrays": { + "httpMethod": "POST", + "id": "myapi.arrayEndpoint.setListOfByteArrays", + "parameterOrder": [ + "list", + "array" + ], + "parameters": { + "list": { + "format": "byte", + "location": "path", + "repeated": true, + "required": true, + "type": "string" + }, + "array": { + "format": "byte", + "location": "path", + "repeated": true, + "required": true, + "type": "string" + } + }, + "path": "setListOfByteArrays/{list}/{array}", + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "setListOfDoubles": { + "httpMethod": "POST", + "id": "myapi.arrayEndpoint.setListOfDoubles", + "parameterOrder": [ + "list", + "array" + ], + "parameters": { + "list": { + "format": "double", + "location": "path", + "repeated": true, + "required": true, + "type": "number" + }, + "array": { + "format": "double", + "location": "path", + "repeated": true, + "required": true, + "type": "number" + } + }, + "path": "setListOfDoubles/{list}/{array}", + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "setListOfEnums": { + "httpMethod": "POST", + "id": "myapi.arrayEndpoint.setListOfEnums", + "parameterOrder": [ + "list" + ], + "parameters": { + "list": { + "enum": [ + "VALUE1", + "value_2" + ], + "enumDescriptions": [ + "", + "" + ], + "location": "path", + "repeated": true, + "required": true, + "type": "string" + } + }, + "path": "setListOfEnums/{list}", + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "setListOfFloats": { + "httpMethod": "POST", + "id": "myapi.arrayEndpoint.setListOfFloats", + "parameterOrder": [ + "list", + "array" + ], + "parameters": { + "list": { + "format": "float", + "location": "path", + "repeated": true, + "required": true, + "type": "number" + }, + "array": { + "format": "float", + "location": "path", + "repeated": true, + "required": true, + "type": "number" + } + }, + "path": "setListOfFloats/{list}/{array}", + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "setListOfIntegers": { + "httpMethod": "POST", + "id": "myapi.arrayEndpoint.setListOfIntegers", + "parameterOrder": [ + "list", + "array" + ], + "parameters": { + "list": { + "format": "int32", + "location": "path", + "repeated": true, + "required": true, + "type": "integer" + }, + "array": { + "format": "int32", + "location": "path", + "repeated": true, + "required": true, + "type": "integer" + } + }, + "path": "setListOfIntegers/{list}/{array}", + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "setListOfLongs": { + "httpMethod": "POST", + "id": "myapi.arrayEndpoint.setListOfLongs", + "parameterOrder": [ + "list", + "array" + ], + "parameters": { + "list": { + "format": "int64", + "location": "path", + "repeated": true, + "required": true, + "type": "string" + }, + "array": { + "format": "int64", + "location": "path", + "repeated": true, + "required": true, + "type": "string" + } + }, + "path": "setListOfLongs/{list}/{array}", + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] + }, + "setListOfString": { + "httpMethod": "POST", + "id": "myapi.arrayEndpoint.setListOfString", + "parameterOrder": [ + "list" + ], + "parameters": { + "list": { + "location": "path", + "repeated": true, + "required": true, + "type": "string" + } + }, + "path": "setListOfString/{list}", + "scopes": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } } } @@ -275,6 +487,12 @@ "integersResponse": { "$ref": "CollectionResponse_Integer" }, + "listOfEnums": { + "items": { + "$ref": "TestEnum" + }, + "type": "array" + }, "listOfString": { "$ref": "ListContainer" }, @@ -437,6 +655,18 @@ } }, "type": "object" + }, + "TestEnum": { + "enum": [ + "VALUE1", + "value_2" + ], + "enumDescriptions": [ + "", + "" + ], + "id": "TestEnum", + "type": "string" } }, "servicePath": "myapi/v1/", diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/absolute_common_path_endpoint.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/absolute_common_path_endpoint.swagger index 075c602a..cef2a091 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/absolute_common_path_endpoint.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/absolute_common_path_endpoint.swagger @@ -6,6 +6,11 @@ }, "host": "myapi.appspot.com", "basePath": "/_ah/api", + "tags": [ + { + "name": "absolutepath:v1" + } + ], "schemes": [ "https" ], @@ -18,6 +23,9 @@ "paths": { "/absolutepath/v1/absolutepathmethod": { "post": { + "tags": [ + "absolutepath:v1" + ], "operationId": "AbsolutepathV1AbsolutePath", "parameters": [], "responses": { @@ -29,6 +37,9 @@ }, "/absolutepath/v1/create": { "post": { + "tags": [ + "absolutepath:v1" + ], "operationId": "AbsolutepathV1CreateFoo", "parameters": [], "responses": { diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/absolute_path_endpoint.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/absolute_path_endpoint.swagger index 53b6bf50..a14d4929 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/absolute_path_endpoint.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/absolute_path_endpoint.swagger @@ -6,6 +6,11 @@ }, "host": "myapi.appspot.com", "basePath": "/_ah/api", + "tags": [ + { + "name": "absolutepath:v1" + } + ], "schemes": [ "https" ], @@ -18,6 +23,9 @@ "paths": { "/absolutepath/v1/create": { "post": { + "tags": [ + "absolutepath:v1" + ], "operationId": "AbsolutepathV1CreateFoo", "parameters": [], "responses": { @@ -32,6 +40,9 @@ }, "/absolutepathmethod/v1": { "post": { + "tags": [ + "absolutepath:v1" + ], "operationId": "AbsolutepathV1AbsolutePath", "parameters": [], "responses": { @@ -43,6 +54,9 @@ }, "/absolutepathmethod2/v1": { "post": { + "tags": [ + "absolutepath:v1" + ], "operationId": "AbsolutepathV1AbsolutePath2", "parameters": [], "responses": { diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/api_keys.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/api_keys.swagger index 44143838..38e0bed4 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/api_keys.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/api_keys.swagger @@ -6,6 +6,11 @@ }, "host": "swagger-test.appspot.com", "basePath": "/api", + "tags": [ + { + "name": "apikeys:v1" + } + ], "schemes": [ "https" ], @@ -18,6 +23,9 @@ "paths": { "/apikeys/v1/apiKeyWithAuth": { "post": { + "tags": [ + "apikeys:v1" + ], "operationId": "ApikeysV1ApiKeyWithAuth", "parameters": [], "responses": { @@ -35,6 +43,9 @@ }, "/apikeys/v1/inheritApiKeySetting": { "post": { + "tags": [ + "apikeys:v1" + ], "operationId": "ApikeysV1InheritApiKeySetting", "parameters": [], "responses": { @@ -51,6 +62,9 @@ }, "/apikeys/v1/overrideApiKeySetting": { "post": { + "tags": [ + "apikeys:v1" + ], "operationId": "ApikeysV1OverrideApiKeySetting", "parameters": [], "responses": { @@ -62,6 +76,11 @@ } }, "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "key", + "in": "query" + }, "auth0-6fa4a909": { "type": "oauth2", "authorizationUrl": "", @@ -69,11 +88,6 @@ "x-google-issuer": "https://test.auth0.com/authorize", "x-google-jwks_uri": "https://test.auth0.com/.wellknown/jwks.json", "x-google-audiences": "auth0audmethod" - }, - "api_key": { - "type": "apiKey", - "name": "key", - "in": "query" } } } 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 b2efe3c3..5b45b181 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 @@ -6,6 +6,11 @@ }, "host": "myapi.appspot.com", "basePath": "/_ah/api", + "tags": [ + { + "name": "myapi:v1" + } + ], "schemes": [ "https" ], @@ -18,6 +23,9 @@ "paths": { "/myapi/v1/arrayendpoint": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetArrayService", "parameters": [], "responses": { @@ -32,6 +40,9 @@ }, "/myapi/v1/baz": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetBaz", "parameters": [], "responses": { @@ -46,6 +57,9 @@ }, "/myapi/v1/collectionresponse_foo": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetFoosResponse", "parameters": [], "responses": { @@ -60,6 +74,9 @@ }, "/myapi/v1/foocollection": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetFoos", "parameters": [], "responses": { @@ -74,6 +91,9 @@ }, "/myapi/v1/foocollectioncollection": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetAllArrayedFoos", "parameters": [], "responses": { @@ -88,6 +108,9 @@ }, "/myapi/v1/getAllFoos": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetAllFoos", "parameters": [], "responses": { @@ -102,6 +125,9 @@ }, "/myapi/v1/getAllFoosResponse": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetAllFoosResponse", "parameters": [], "responses": { @@ -116,6 +142,9 @@ }, "/myapi/v1/getAllNestedFoosResponse": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetAllNestedFoosResponse", "parameters": [], "responses": { @@ -130,6 +159,9 @@ }, "/myapi/v1/getArrayedFoos": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetArrayedFoos", "parameters": [], "responses": { @@ -144,6 +176,9 @@ }, "/myapi/v1/getIntegersResponse": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetIntegersResponse", "parameters": [], "responses": { @@ -158,6 +193,9 @@ }, "/myapi/v1/getListOfString": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetListOfString", "parameters": [], "responses": { @@ -172,6 +210,9 @@ }, "/myapi/v1/getObjectIntegers": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetObjectIntegers", "parameters": [], "responses": { @@ -186,6 +227,9 @@ }, "/myapi/v1/integercollection": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetIntegers", "parameters": [], "responses": { @@ -197,118 +241,285 @@ } } } - } - }, - "definitions": { - "CollectionResponse_Integer": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "type": "integer", - "format": "int32" + }, + "/myapi/v1/setListOfBooleans/{list}/{array}": { + "post": { + "tags": [ + "myapi:v1" + ], + "operationId": "MyapiV1SetListOfBooleans", + "parameters": [ + { + "name": "list", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "boolean" + }, + "collectionFormat": "multi" + }, + { + "name": "array", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "boolean" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "A successful response" } - }, - "nextPageToken": { - "type": "string" } } }, - "CollectionResponse_Foo": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/Foo" + "/myapi/v1/setListOfByteArrays/{list}/{array}": { + "post": { + "tags": [ + "myapi:v1" + ], + "operationId": "MyapiV1SetListOfByteArrays", + "parameters": [ + { + "name": "list", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "string", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$" + }, + "collectionFormat": "multi" + }, + { + "name": "array", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "string", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "A successful response" } - }, - "nextPageToken": { - "type": "string" } } }, - "Foo": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "integer", - "format": "int32" + "/myapi/v1/setListOfDoubles/{list}/{array}": { + "post": { + "tags": [ + "myapi:v1" + ], + "operationId": "MyapiV1SetListOfDoubles", + "parameters": [ + { + "name": "list", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "collectionFormat": "multi" + }, + { + "name": "array", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "A successful response" + } } } }, - "FooCollectionCollection": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { + "/myapi/v1/setListOfEnums/{list}": { + "post": { + "tags": [ + "myapi:v1" + ], + "operationId": "MyapiV1SetListOfEnums", + "parameters": [ + { + "name": "list", + "in": "path", + "required": true, "type": "array", "items": { - "$ref": "#/definitions/Foo" - } + "type": "string", + "enum": [ + "VALUE1", + "value_2" + ] + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "A successful response" } } } }, - "Baz": { - "type": "object", - "properties": { - "foo": { - "$ref": "#/definitions/Foo" - }, - "foos": { - "type": "array", - "items": { - "$ref": "#/definitions/Foo" + "/myapi/v1/setListOfFloats/{list}/{array}": { + "post": { + "tags": [ + "myapi:v1" + ], + "operationId": "MyapiV1SetListOfFloats", + "parameters": [ + { + "name": "list", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "number", + "format": "float" + }, + "collectionFormat": "multi" + }, + { + "name": "array", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "number", + "format": "float" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "A successful response" } } } }, - "ListContainer": { - "type": "object", - "properties": { - "strings": { - "type": "array", - "items": { - "type": "string" + "/myapi/v1/setListOfIntegers/{list}/{array}": { + "post": { + "tags": [ + "myapi:v1" + ], + "operationId": "MyapiV1SetListOfIntegers", + "parameters": [ + { + "name": "list", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "collectionFormat": "multi" + }, + { + "name": "array", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "A successful response" } } } }, - "IntegerCollection": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "type": "integer", - "format": "int32" + "/myapi/v1/setListOfLongs/{list}/{array}": { + "post": { + "tags": [ + "myapi:v1" + ], + "operationId": "MyapiV1SetListOfLongs", + "parameters": [ + { + "name": "list", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "collectionFormat": "multi" + }, + { + "name": "array", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "A successful response" } } } }, - "CollectionResponse_FooCollection": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { + "/myapi/v1/setListOfString/{list}": { + "post": { + "tags": [ + "myapi:v1" + ], + "operationId": "MyapiV1SetListOfString", + "parameters": [ + { + "name": "list", + "in": "path", + "required": true, "type": "array", "items": { - "$ref": "#/definitions/Foo" - } + "type": "string" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "A successful response" } - }, - "nextPageToken": { - "type": "string" } } - }, + } + }, + "definitions": { "ArrayEndpoint": { "type": "object", "properties": { @@ -367,6 +578,16 @@ "integersResponse": { "$ref": "#/definitions/CollectionResponse_Integer" }, + "listOfEnums": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "VALUE1", + "value_2" + ] + } + }, "listOfString": { "$ref": "#/definitions/ListContainer" }, @@ -379,7 +600,21 @@ } } }, - "FooCollection": { + "Baz": { + "type": "object", + "properties": { + "foo": { + "$ref": "#/definitions/Foo" + }, + "foos": { + "type": "array", + "items": { + "$ref": "#/definitions/Foo" + } + } + } + }, + "CollectionResponse_Foo": { "type": "object", "properties": { "items": { @@ -387,6 +622,26 @@ "items": { "$ref": "#/definitions/Foo" } + }, + "nextPageToken": { + "type": "string" + } + } + }, + "CollectionResponse_FooCollection": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/Foo" + } + } + }, + "nextPageToken": { + "type": "string" } } }, @@ -409,6 +664,81 @@ "type": "string" } } + }, + "CollectionResponse_Integer": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + }, + "nextPageToken": { + "type": "string" + } + } + }, + "Foo": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "integer", + "format": "int32" + } + } + }, + "FooCollection": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/Foo" + } + } + } + }, + "FooCollectionCollection": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/Foo" + } + } + } + } + }, + "IntegerCollection": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + } + }, + "ListContainer": { + "type": "object", + "properties": { + "strings": { + "type": "array", + "items": { + "type": "string" + } + } + } } } } diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/enum_endpoint.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/enum_endpoint.swagger index 2d7ff181..44ab8fc4 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/enum_endpoint.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/enum_endpoint.swagger @@ -6,6 +6,11 @@ }, "host": "myapi.appspot.com", "basePath": "/_ah/api", + "tags": [ + { + "name": "enum:v1" + } + ], "schemes": [ "https" ], @@ -18,6 +23,9 @@ "paths": { "/enum/v1/{value}": { "post": { + "tags": [ + "enum:v1" + ], "operationId": "EnumV1Create", "parameters": [ { diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint.swagger index 4d6a053e..0bc8cd30 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint.swagger @@ -6,6 +6,15 @@ }, "host": "swagger-test.appspot.com", "basePath": "/api", + "tags": [ + { + "name": "foo:v1", + "description": "Just Foo Things", + "externalDocs": { + "url": "https://example.com" + } + } + ], "schemes": [ "https" ], @@ -18,6 +27,9 @@ "paths": { "/foo/v1/foos": { "get": { + "tags": [ + "foo:v1" + ], "description": "list desc", "operationId": "FooV1ListFoos", "parameters": [ @@ -39,14 +51,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "post": { + "tags": [ + "foo:v1" + ], "operationId": "FooV1Toplevel", "parameters": [], "responses": { @@ -59,16 +78,23 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] } }, "/foo/v1/foos/{id}": { "get": { + "tags": [ + "foo:v1" + ], "description": "get desc", "operationId": "FooV1GetFoo", "parameters": [ @@ -90,14 +116,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "post": { + "tags": [ + "foo:v1" + ], "description": "update desc", "operationId": "FooV1UpdateFoo", "parameters": [ @@ -127,14 +160,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "put": { + "tags": [ + "foo:v1" + ], "description": "create desc", "operationId": "FooV1CreateFoo", "parameters": [ @@ -164,14 +204,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "delete": { + "tags": [ + "foo:v1" + ], "description": "delete desc", "operationId": "FooV1DeleteFoo", "parameters": [ @@ -193,10 +240,14 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] } @@ -205,34 +256,22 @@ "securityDefinitions": { "google_id_token-3a26ea04": { "type": "oauth2", - "authorizationUrl": "", + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", "flow": "implicit", - "x-google-issuer": "accounts.google.com", + "x-google-issuer": "https://accounts.google.com", "x-google-jwks_uri": "https://www.googleapis.com/oauth2/v1/certs", "x-google-audiences": "audience" }, - "google_id_token_https-3a26ea04": { + "google_id_token_legacy-3a26ea04": { "type": "oauth2", - "authorizationUrl": "", + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", "flow": "implicit", - "x-google-issuer": "https://accounts.google.com", + "x-google-issuer": "accounts.google.com", "x-google-jwks_uri": "https://www.googleapis.com/oauth2/v1/certs", "x-google-audiences": "audience" } }, "definitions": { - "Foo": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "integer", - "format": "int32" - } - } - }, "CollectionResponse_Foo": { "type": "object", "properties": { @@ -246,6 +285,18 @@ "type": "string" } } + }, + "Foo": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "integer", + "format": "int32" + } + } } } } diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint_default_context.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint_default_context.swagger index f531f79e..b771bbeb 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint_default_context.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint_default_context.swagger @@ -6,6 +6,15 @@ }, "host": "myapi.appspot.com", "basePath": "/_ah/api", + "tags": [ + { + "name": "foo:v1", + "description": "Just Foo Things", + "externalDocs": { + "url": "https://example.com" + } + } + ], "schemes": [ "https" ], @@ -18,6 +27,9 @@ "paths": { "/foo/v1/foos": { "get": { + "tags": [ + "foo:v1" + ], "description": "list desc", "operationId": "FooV1ListFoos", "parameters": [ @@ -39,14 +51,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "post": { + "tags": [ + "foo:v1" + ], "operationId": "FooV1Toplevel", "parameters": [], "responses": { @@ -59,16 +78,23 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] } }, "/foo/v1/foos/{id}": { "get": { + "tags": [ + "foo:v1" + ], "description": "get desc", "operationId": "FooV1GetFoo", "parameters": [ @@ -90,14 +116,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "post": { + "tags": [ + "foo:v1" + ], "description": "update desc", "operationId": "FooV1UpdateFoo", "parameters": [ @@ -127,14 +160,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "put": { + "tags": [ + "foo:v1" + ], "description": "create desc", "operationId": "FooV1CreateFoo", "parameters": [ @@ -164,14 +204,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "delete": { + "tags": [ + "foo:v1" + ], "description": "delete desc", "operationId": "FooV1DeleteFoo", "parameters": [ @@ -193,10 +240,14 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] } @@ -205,17 +256,17 @@ "securityDefinitions": { "google_id_token-3a26ea04": { "type": "oauth2", - "authorizationUrl": "", + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", "flow": "implicit", - "x-google-issuer": "accounts.google.com", + "x-google-issuer": "https://accounts.google.com", "x-google-jwks_uri": "https://www.googleapis.com/oauth2/v1/certs", "x-google-audiences": "audience" }, - "google_id_token_https-3a26ea04": { + "google_id_token_legacy-3a26ea04": { "type": "oauth2", - "authorizationUrl": "", + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", "flow": "implicit", - "x-google-issuer": "https://accounts.google.com", + "x-google-issuer": "accounts.google.com", "x-google-jwks_uri": "https://www.googleapis.com/oauth2/v1/certs", "x-google-audiences": "audience" } diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint_internal.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint_internal.swagger deleted file mode 100644 index 95dd8029..00000000 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint_internal.swagger +++ /dev/null @@ -1,251 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "version": "1.0.0", - "title": "swagger-test.appspot.com" - }, - "host": "swagger-test.appspot.com", - "basePath": "/api", - "schemes": [ - "https" - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "paths": { - "/foo/v1/foos": { - "get": { - "description": "list desc", - "operationId": "FooV1ListFoos", - "parameters": [ - { - "name": "n", - "in": "query", - "required": true, - "type": "integer", - "format": "int32" - } - ], - "responses": { - "200": { - "description": "A successful response", - "schema": { - "$ref": "#/definitions/CollectionResponse_Foo" - } - } - }, - "security": [ - { - "google_id_token-3a26ea04": [] - }, - { - "google_id_token_https-3a26ea04": [] - } - ] - }, - "post": { - "operationId": "FooV1Toplevel", - "parameters": [], - "responses": { - "200": { - "description": "A successful response", - "schema": { - "$ref": "#/definitions/CollectionResponse_Foo" - } - } - }, - "security": [ - { - "google_id_token-3a26ea04": [] - }, - { - "google_id_token_https-3a26ea04": [] - } - ] - } - }, - "/foo/v1/foos/{id}": { - "get": { - "description": "get desc", - "operationId": "FooV1GetFoo", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "id desc", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "A successful response", - "schema": { - "$ref": "#/definitions/Foo" - } - } - }, - "security": [ - { - "google_id_token-3a26ea04": [] - }, - { - "google_id_token_https-3a26ea04": [] - } - ] - }, - "post": { - "description": "update desc", - "operationId": "FooV1UpdateFoo", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "id desc", - "required": true, - "type": "string" - }, - { - "in": "body", - "name": "body", - "required": false, - "schema": { - "$ref": "#/definitions/Foo" - } - } - ], - "responses": { - "200": { - "description": "A successful response", - "schema": { - "$ref": "#/definitions/Foo" - } - } - }, - "security": [ - { - "google_id_token-3a26ea04": [] - }, - { - "google_id_token_https-3a26ea04": [] - } - ] - }, - "put": { - "description": "create desc", - "operationId": "FooV1CreateFoo", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "id desc", - "required": true, - "type": "string" - }, - { - "in": "body", - "name": "body", - "required": false, - "schema": { - "$ref": "#/definitions/Foo" - } - } - ], - "responses": { - "200": { - "description": "A successful response", - "schema": { - "$ref": "#/definitions/Foo" - } - } - }, - "security": [ - { - "google_id_token-3a26ea04": [] - }, - { - "google_id_token_https-3a26ea04": [] - } - ] - }, - "delete": { - "description": "delete desc", - "operationId": "FooV1DeleteFoo", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "id desc", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "A successful response", - "schema": { - "$ref": "#/definitions/Foo" - } - } - }, - "security": [ - { - "google_id_token-3a26ea04": [] - }, - { - "google_id_token_https-3a26ea04": [] - } - ] - } - } - }, - "securityDefinitions": { - "google_id_token-3a26ea04": { - "type": "oauth2", - "authorizationUrl": "", - "flow": "implicit", - "x-google-issuer": "accounts.google.com", - "x-google-jwks_uri": "https://www.googleapis.com/oauth2/v1/certs", - "x-google-audiences": "audience" - }, - "google_id_token_https-3a26ea04": { - "type": "oauth2", - "authorizationUrl": "", - "flow": "implicit", - "x-google-issuer": "https://accounts.google.com", - "x-google-jwks_uri": "https://www.googleapis.com/oauth2/v1/certs", - "x-google-audiences": "audience" - } - }, - "definitions": { - "CollectionResponse_Foo": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/Foo" - } - }, - "nextPageToken": { - "type": "string" - } - } - }, - "Foo": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "integer", - "format": "int32" - } - } - } - } -} diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint_localhost.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint_localhost.swagger index f91532dd..25f6dcd2 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint_localhost.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_endpoint_localhost.swagger @@ -6,6 +6,15 @@ }, "host": "localhost:8080", "basePath": "/api", + "tags": [ + { + "name": "foo:v1", + "description": "Just Foo Things", + "externalDocs": { + "url": "https://example.com" + } + } + ], "schemes": [ "http" ], @@ -18,6 +27,9 @@ "paths": { "/foo/v1/foos": { "get": { + "tags": [ + "foo:v1" + ], "description": "list desc", "operationId": "FooV1ListFoos", "parameters": [ @@ -39,14 +51,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "post": { + "tags": [ + "foo:v1" + ], "operationId": "FooV1Toplevel", "parameters": [], "responses": { @@ -59,16 +78,23 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] } }, "/foo/v1/foos/{id}": { "get": { + "tags": [ + "foo:v1" + ], "description": "get desc", "operationId": "FooV1GetFoo", "parameters": [ @@ -90,14 +116,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "post": { + "tags": [ + "foo:v1" + ], "description": "update desc", "operationId": "FooV1UpdateFoo", "parameters": [ @@ -127,14 +160,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "put": { + "tags": [ + "foo:v1" + ], "description": "create desc", "operationId": "FooV1CreateFoo", "parameters": [ @@ -164,14 +204,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "delete": { + "tags": [ + "foo:v1" + ], "description": "delete desc", "operationId": "FooV1DeleteFoo", "parameters": [ @@ -193,10 +240,14 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] } @@ -205,34 +256,22 @@ "securityDefinitions": { "google_id_token-3a26ea04": { "type": "oauth2", - "authorizationUrl": "", + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", "flow": "implicit", - "x-google-issuer": "accounts.google.com", + "x-google-issuer": "https://accounts.google.com", "x-google-jwks_uri": "https://www.googleapis.com/oauth2/v1/certs", "x-google-audiences": "audience" }, - "google_id_token_https-3a26ea04": { + "google_id_token_legacy-3a26ea04": { "type": "oauth2", - "authorizationUrl": "", + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", "flow": "implicit", - "x-google-issuer": "https://accounts.google.com", + "x-google-issuer": "accounts.google.com", "x-google-jwks_uri": "https://www.googleapis.com/oauth2/v1/certs", "x-google-audiences": "audience" } }, "definitions": { - "Foo": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "integer", - "format": "int32" - } - } - }, "CollectionResponse_Foo": { "type": "object", "properties": { @@ -246,6 +285,18 @@ "type": "string" } } + }, + "Foo": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "integer", + "format": "int32" + } + } } } } diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_with_description_endpoint.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_with_description_endpoint.swagger index c291a6a6..b492326c 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_with_description_endpoint.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/foo_with_description_endpoint.swagger @@ -6,6 +6,12 @@ }, "host": "swagger-test.appspot.com", "basePath": "/api", + "tags": [ + { + "name": "foo:v1", + "description": "Just Foo Things" + } + ], "schemes": [ "https" ], @@ -18,6 +24,9 @@ "paths": { "/foo/v1/foos": { "get": { + "tags": [ + "foo:v1" + ], "description": "list desc", "operationId": "FooV1ListFoos", "parameters": [ @@ -50,14 +59,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "post": { + "tags": [ + "foo:v1" + ], "operationId": "FooV1Toplevel", "parameters": [], "responses": { @@ -70,16 +86,23 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] } }, "/foo/v1/foos/{id}": { "get": { + "tags": [ + "foo:v1" + ], "description": "get desc", "operationId": "FooV1GetFoo", "parameters": [ @@ -101,14 +124,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "post": { + "tags": [ + "foo:v1" + ], "description": "update desc", "operationId": "FooV1UpdateFoo", "parameters": [ @@ -138,14 +168,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "put": { + "tags": [ + "foo:v1" + ], "description": "create desc", "operationId": "FooV1CreateFoo", "parameters": [ @@ -175,14 +212,21 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] }, "delete": { + "tags": [ + "foo:v1" + ], "description": "delete desc", "operationId": "FooV1DeleteFoo", "parameters": [ @@ -204,10 +248,14 @@ }, "security": [ { - "google_id_token-3a26ea04": [] + "google_id_token-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] }, { - "google_id_token_https-3a26ea04": [] + "google_id_token_legacy-3a26ea04": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] } @@ -216,22 +264,36 @@ "securityDefinitions": { "google_id_token-3a26ea04": { "type": "oauth2", - "authorizationUrl": "", + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", "flow": "implicit", - "x-google-issuer": "accounts.google.com", + "x-google-issuer": "https://accounts.google.com", "x-google-jwks_uri": "https://www.googleapis.com/oauth2/v1/certs", "x-google-audiences": "audience" }, - "google_id_token_https-3a26ea04": { + "google_id_token_legacy-3a26ea04": { "type": "oauth2", - "authorizationUrl": "", + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", "flow": "implicit", - "x-google-issuer": "https://accounts.google.com", + "x-google-issuer": "accounts.google.com", "x-google-jwks_uri": "https://www.googleapis.com/oauth2/v1/certs", "x-google-audiences": "audience" } }, "definitions": { + "CollectionResponse_FooDescription": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/FooDescription" + } + }, + "nextPageToken": { + "type": "string" + } + } + }, "FooDescription": { "type": "object", "properties": { @@ -253,20 +315,6 @@ "description": "description of value" } } - }, - "CollectionResponse_FooDescription": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/FooDescription" - } - }, - "nextPageToken": { - "type": "string" - } - } } } } diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/google_auth.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/google_auth.swagger index 532b9fde..26706b94 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/google_auth.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/google_auth.swagger @@ -6,6 +6,11 @@ }, "host": "swagger-test.appspot.com", "basePath": "/api", + "tags": [ + { + "name": "thirdparty:v1" + } + ], "schemes": [ "https" ], @@ -18,6 +23,9 @@ "paths": { "/thirdparty/v1/authOverride": { "post": { + "tags": [ + "thirdparty:v1" + ], "operationId": "ThirdpartyV1AuthOverride", "parameters": [], "responses": { @@ -34,6 +42,9 @@ }, "/thirdparty/v1/googleAuth": { "post": { + "tags": [ + "thirdparty:v1" + ], "operationId": "ThirdpartyV1GoogleAuth", "parameters": [], "responses": { @@ -43,13 +54,18 @@ }, "security": [ { - "google_id_token-57e345d7": [] + "google_id_token_legacy-57e345d7": [ + "https://www.googleapis.com/auth/userinfo.email" + ] } ] } }, "/thirdparty/v1/noOverride": { "post": { + "tags": [ + "thirdparty:v1" + ], "operationId": "ThirdpartyV1NoOverride", "parameters": [], "responses": { @@ -74,14 +90,6 @@ "x-google-jwks_uri": "https://test.auth0.com/.wellknown/jwks.json", "x-google-audiences": "auth0audmethod" }, - "google_id_token-57e345d7": { - "type": "oauth2", - "authorizationUrl": "", - "flow": "implicit", - "x-google-issuer": "accounts.google.com", - "x-google-jwks_uri": "https://www.googleapis.com/oauth2/v1/certs", - "x-google-audiences": "googleaud" - }, "auth0-a05d2f2": { "type": "oauth2", "authorizationUrl": "", @@ -89,6 +97,14 @@ "x-google-issuer": "https://test.auth0.com/authorize", "x-google-jwks_uri": "https://test.auth0.com/.wellknown/jwks.json", "x-google-audiences": "auth0audapi" + }, + "google_id_token_legacy-57e345d7": { + "type": "oauth2", + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", + "flow": "implicit", + "x-google-issuer": "accounts.google.com", + "x-google-jwks_uri": "https://www.googleapis.com/oauth2/v1/certs", + "x-google-audiences": "googleaud" } } } diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/limit_metrics_endpoint.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/limit_metrics_endpoint.swagger index 05eaf11c..55f0c2b2 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/limit_metrics_endpoint.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/limit_metrics_endpoint.swagger @@ -6,6 +6,11 @@ }, "host": "myapi.appspot.com", "basePath": "/_ah/api", + "tags": [ + { + "name": "limits:v1" + } + ], "schemes": [ "https" ], @@ -18,6 +23,9 @@ "paths": { "/limits/v1/create": { "post": { + "tags": [ + "limits:v1" + ], "operationId": "LimitsV1CreateFoo", "parameters": [], "responses": { @@ -37,6 +45,9 @@ }, "/limits/v1/customFoo": { "post": { + "tags": [ + "limits:v1" + ], "operationId": "LimitsV1CustomFoo", "parameters": [], "responses": { diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/map_endpoint.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/map_endpoint.swagger index 5fb2ba37..eb8e118c 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/map_endpoint.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/map_endpoint.swagger @@ -6,6 +6,11 @@ }, "host": "myapi.appspot.com", "basePath": "/_ah/api", + "tags": [ + { + "name": "myapi:v1" + } + ], "schemes": [ "https" ], @@ -18,13 +23,19 @@ "paths": { "/myapi/v1/getDateTimeKeyMap": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetDateTimeKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_DateTime_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -32,6 +43,9 @@ }, "/myapi/v1/getMapOfStrings": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetMapOfStrings", "parameters": [], "responses": { @@ -46,13 +60,19 @@ }, "/myapi/v1/getStringCollectionMap": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetStringCollectionMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -60,13 +80,19 @@ }, "/myapi/v1/map_boolean_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetBooleanKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_Boolean_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -74,13 +100,19 @@ }, "/myapi/v1/map_datetime_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetDateKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_DateTime_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -88,13 +120,19 @@ }, "/myapi/v1/map_float_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetFloatKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_Float_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -102,13 +140,19 @@ }, "/myapi/v1/map_integer_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetIntKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_Integer_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -116,13 +160,19 @@ }, "/myapi/v1/map_long_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetLongKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_Long_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -130,13 +180,19 @@ }, "/myapi/v1/map_string_baz": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetBazMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_Baz" + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Baz" + } } } } @@ -144,13 +200,19 @@ }, "/myapi/v1/map_string_foo": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetFooMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_Foo" + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Foo" + } } } } @@ -158,13 +220,20 @@ }, "/myapi/v1/map_string_integer": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetIntMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_Integer" + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } } } } @@ -172,13 +241,22 @@ }, "/myapi/v1/map_string_map_string_foo": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetFooMapMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_Map_String_Foo" + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Foo" + } + } } } } @@ -186,13 +264,19 @@ }, "/myapi/v1/map_string_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetStringMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -200,13 +284,19 @@ }, "/myapi/v1/map_string_stringcollection": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetStringArrayMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -214,13 +304,19 @@ }, "/myapi/v1/map_string_stringvalue": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetStringValueMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_StringValue" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -228,13 +324,19 @@ }, "/myapi/v1/map_testenum_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetEnumKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_TestEnum_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -242,6 +344,9 @@ }, "/myapi/v1/mapendpoint": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetMapService", "parameters": [], "responses": { @@ -256,13 +361,20 @@ }, "/myapi/v1/mapsubclass": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetMapSubclass", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_Boolean_Integer" + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } } } } @@ -270,16 +382,18 @@ } }, "definitions": { - "Map_Boolean_String": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "Map_Float_String": { + "Baz": { "type": "object", - "additionalProperties": { - "type": "string" + "properties": { + "foo": { + "$ref": "#/definitions/Foo" + }, + "foos": { + "type": "array", + "items": { + "$ref": "#/definitions/Foo" + } + } } }, "Foo": { @@ -294,122 +408,93 @@ } } }, - "Map_Integer_String": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "Map_TestEnum_String": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "Map_Boolean_Integer": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int32" - } - }, "JsonMap": { "type": "object" }, - "Map_String_StringValue": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, "MapContainer": { "type": "object", "properties": { "stringMap": { - "$ref": "#/definitions/Map_String_StringValue" - } - } - }, - "Map_String_Integer": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int32" - } - }, - "Map_String_Baz": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Baz" - } - }, - "Map_String_Map_String_Foo": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Map_String_Foo" - } - }, - "Map_String_String": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "Baz": { - "type": "object", - "properties": { - "foo": { - "$ref": "#/definitions/Foo" - }, - "foos": { - "type": "array", - "items": { - "$ref": "#/definitions/Foo" + "type": "object", + "description": "A map of string values", + "additionalProperties": { + "type": "string" } } } }, - "Map_Long_String": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, "MapEndpoint": { "type": "object", "properties": { "bazMap": { - "$ref": "#/definitions/Map_String_Baz" + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Baz" + } }, "booleanKeyMap": { - "$ref": "#/definitions/Map_Boolean_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "dateKeyMap": { - "$ref": "#/definitions/Map_DateTime_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "dateTimeKeyMap": { - "$ref": "#/definitions/Map_DateTime_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "enumKeyMap": { - "$ref": "#/definitions/Map_TestEnum_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "floatKeyMap": { - "$ref": "#/definitions/Map_Float_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "fooMap": { - "$ref": "#/definitions/Map_String_Foo" + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Foo" + } }, "fooMapMap": { - "$ref": "#/definitions/Map_String_Map_String_Foo" + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Foo" + } + } }, "intKeyMap": { - "$ref": "#/definitions/Map_Integer_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "intMap": { - "$ref": "#/definitions/Map_String_Integer" + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } }, "longKeyMap": { - "$ref": "#/definitions/Map_Long_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "mapOfStrings": { "$ref": "#/definitions/MapContainer" @@ -418,34 +503,37 @@ "$ref": "#/definitions/MapEndpoint" }, "mapSubclass": { - "$ref": "#/definitions/Map_Boolean_Integer" + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } }, "stringArrayMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "stringCollectionMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "stringMap": { - "$ref": "#/definitions/Map_String_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "stringValueMap": { - "$ref": "#/definitions/Map_String_StringValue" + "type": "object", + "additionalProperties": { + "type": "string" + } } } - }, - "Map_String_Foo": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Foo" - } - }, - "Map_DateTime_String": { - "type": "object", - "additionalProperties": { - "type": "string" - } } } } - diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/map_endpoint_legacy.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/map_endpoint_legacy.swagger index 8d4cf4e1..2ef30008 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/map_endpoint_legacy.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/map_endpoint_legacy.swagger @@ -6,6 +6,11 @@ }, "host": "myapi.appspot.com", "basePath": "/_ah/api", + "tags": [ + { + "name": "myapi:v1" + } + ], "schemes": [ "https" ], @@ -18,13 +23,19 @@ "paths": { "/myapi/v1/getDateTimeKeyMap": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetDateTimeKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -32,6 +43,9 @@ }, "/myapi/v1/getMapOfStrings": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetMapOfStrings", "parameters": [], "responses": { @@ -46,13 +60,19 @@ }, "/myapi/v1/getStringCollectionMap": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetStringCollectionMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -60,13 +80,19 @@ }, "/myapi/v1/map_boolean_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetBooleanKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -74,13 +100,19 @@ }, "/myapi/v1/map_datetime_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetDateKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -88,13 +120,19 @@ }, "/myapi/v1/map_float_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetFloatKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -102,13 +140,19 @@ }, "/myapi/v1/map_integer_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetIntKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -116,13 +160,19 @@ }, "/myapi/v1/map_long_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetLongKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -130,13 +180,19 @@ }, "/myapi/v1/map_string_baz": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetBazMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -144,13 +200,19 @@ }, "/myapi/v1/map_string_foo": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetFooMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -158,13 +220,19 @@ }, "/myapi/v1/map_string_integer": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetIntMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -172,13 +240,19 @@ }, "/myapi/v1/map_string_map_string_foo": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetFooMapMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -186,13 +260,19 @@ }, "/myapi/v1/map_string_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetStringMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -200,13 +280,19 @@ }, "/myapi/v1/map_string_stringcollection": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetStringArrayMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -214,13 +300,19 @@ }, "/myapi/v1/map_string_stringvalue": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetStringValueMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -228,13 +320,19 @@ }, "/myapi/v1/map_testenum_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetEnumKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -242,6 +340,9 @@ }, "/myapi/v1/mapendpoint": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetMapService", "parameters": [], "responses": { @@ -256,13 +357,19 @@ }, "/myapi/v1/mapsubclass": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetMapSubclass", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } @@ -277,7 +384,11 @@ "type": "object", "properties": { "stringMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "description": "A map of string values", + "additionalProperties": { + "type": "object" + } } } }, @@ -285,37 +396,70 @@ "type": "object", "properties": { "bazMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "booleanKeyMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "dateKeyMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "dateTimeKeyMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "enumKeyMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "floatKeyMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "fooMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "fooMapMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "intKeyMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "intMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "longKeyMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "mapOfStrings": { "$ref": "#/definitions/MapContainer" @@ -324,19 +468,34 @@ "$ref": "#/definitions/MapEndpoint" }, "mapSubclass": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "stringArrayMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "stringCollectionMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "stringMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } }, "stringValueMap": { - "$ref": "#/definitions/JsonMap" + "type": "object", + "additionalProperties": { + "type": "object" + } } } } diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/map_endpoint_with_array.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/map_endpoint_with_array.swagger index aa7e536b..960664db 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/map_endpoint_with_array.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/map_endpoint_with_array.swagger @@ -6,6 +6,11 @@ }, "host": "myapi.appspot.com", "basePath": "/_ah/api", + "tags": [ + { + "name": "myapi:v1" + } + ], "schemes": [ "https" ], @@ -18,13 +23,19 @@ "paths": { "/myapi/v1/getDateTimeKeyMap": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetDateTimeKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_DateTime_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -32,6 +43,9 @@ }, "/myapi/v1/getMapOfStrings": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetMapOfStrings", "parameters": [], "responses": { @@ -46,13 +60,22 @@ }, "/myapi/v1/getStringCollectionMap": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetStringCollectionMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_StringCollection" + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } } } } @@ -60,13 +83,19 @@ }, "/myapi/v1/map_boolean_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetBooleanKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_Boolean_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -74,13 +103,19 @@ }, "/myapi/v1/map_datetime_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetDateKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_DateTime_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -88,13 +123,19 @@ }, "/myapi/v1/map_float_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetFloatKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_Float_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -102,13 +143,19 @@ }, "/myapi/v1/map_integer_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetIntKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_Integer_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -116,13 +163,19 @@ }, "/myapi/v1/map_long_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetLongKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_Long_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -130,13 +183,19 @@ }, "/myapi/v1/map_string_baz": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetBazMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_Baz" + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Baz" + } } } } @@ -144,13 +203,19 @@ }, "/myapi/v1/map_string_foo": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetFooMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_Foo" + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Foo" + } } } } @@ -158,13 +223,20 @@ }, "/myapi/v1/map_string_integer": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetIntMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_Integer" + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } } } } @@ -172,13 +244,22 @@ }, "/myapi/v1/map_string_map_string_foo": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetFooMapMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_Map_String_Foo" + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Foo" + } + } } } } @@ -186,13 +267,19 @@ }, "/myapi/v1/map_string_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetStringMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -200,13 +287,22 @@ }, "/myapi/v1/map_string_stringcollection": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetStringArrayMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_StringCollection" + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } } } } @@ -214,13 +310,19 @@ }, "/myapi/v1/map_string_stringvalue": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetStringValueMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_String_StringValue" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -228,13 +330,19 @@ }, "/myapi/v1/map_testenum_string": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetEnumKeyMap", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_TestEnum_String" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -242,6 +350,9 @@ }, "/myapi/v1/mapendpoint": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetMapService", "parameters": [], "responses": { @@ -256,13 +367,20 @@ }, "/myapi/v1/mapsubclass": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1GetMapSubclass", "parameters": [], "responses": { "200": { "description": "A successful response", "schema": { - "$ref": "#/definitions/Map_Boolean_Integer" + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } } } } @@ -270,16 +388,18 @@ } }, "definitions": { - "Map_Boolean_String": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "Map_Float_String": { + "Baz": { "type": "object", - "additionalProperties": { - "type": "string" + "properties": { + "foo": { + "$ref": "#/definitions/Foo" + }, + "foos": { + "type": "array", + "items": { + "$ref": "#/definitions/Foo" + } + } } }, "Foo": { @@ -294,128 +414,90 @@ } } }, - "Map_Integer_String": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "Map_TestEnum_String": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "Map_Boolean_Integer": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int32" - } - }, - "Map_String_StringValue": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, "MapContainer": { "type": "object", "properties": { "stringMap": { - "$ref": "#/definitions/Map_String_StringValue" - } - } - }, - "Map_String_Integer": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int32" - } - }, - "Map_String_Baz": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Baz" - } - }, - "Map_String_Map_String_Foo": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Map_String_Foo" - } - }, - "Map_String_String": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "Baz": { - "type": "object", - "properties": { - "foo": { - "$ref": "#/definitions/Foo" - }, - "foos": { - "type": "array", - "items": { - "$ref": "#/definitions/Foo" + "type": "object", + "description": "A map of string values", + "additionalProperties": { + "type": "string" } } } }, - "Map_String_StringCollection": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "Map_Long_String": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, "MapEndpoint": { "type": "object", "properties": { "bazMap": { - "$ref": "#/definitions/Map_String_Baz" + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Baz" + } }, "booleanKeyMap": { - "$ref": "#/definitions/Map_Boolean_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "dateKeyMap": { - "$ref": "#/definitions/Map_DateTime_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "dateTimeKeyMap": { - "$ref": "#/definitions/Map_DateTime_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "enumKeyMap": { - "$ref": "#/definitions/Map_TestEnum_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "floatKeyMap": { - "$ref": "#/definitions/Map_Float_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "fooMap": { - "$ref": "#/definitions/Map_String_Foo" + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Foo" + } }, "fooMapMap": { - "$ref": "#/definitions/Map_String_Map_String_Foo" + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Foo" + } + } }, "intKeyMap": { - "$ref": "#/definitions/Map_Integer_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "intMap": { - "$ref": "#/definitions/Map_String_Integer" + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } }, "longKeyMap": { - "$ref": "#/definitions/Map_Long_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "mapOfStrings": { "$ref": "#/definitions/MapContainer" @@ -424,33 +506,43 @@ "$ref": "#/definitions/MapEndpoint" }, "mapSubclass": { - "$ref": "#/definitions/Map_Boolean_Integer" + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } }, "stringArrayMap": { - "$ref": "#/definitions/Map_String_StringCollection" + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } }, "stringCollectionMap": { - "$ref": "#/definitions/Map_String_StringCollection" + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } }, "stringMap": { - "$ref": "#/definitions/Map_String_String" + "type": "object", + "additionalProperties": { + "type": "string" + } }, "stringValueMap": { - "$ref": "#/definitions/Map_String_StringValue" + "type": "object", + "additionalProperties": { + "type": "string" + } } } - }, - "Map_String_Foo": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Foo" - } - }, - "Map_DateTime_String": { - "type": "object", - "additionalProperties": { - "type": "string" - } } } } diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/multi_resource_endpoint.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/multi_resource_endpoint.swagger index f207c1c2..e66abe5f 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/multi_resource_endpoint.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/multi_resource_endpoint.swagger @@ -6,6 +6,17 @@ }, "host": "swagger-test.appspot.com", "basePath": "/api", + "tags": [ + { + "name": "multiresource:v1" + }, + { + "name": "multiresource:v1.Resource1" + }, + { + "name": "multiresource:v1.Resource2" + } + ], "schemes": [ "https" ], @@ -18,6 +29,9 @@ "paths": { "/multiresource/v1/noresource": { "get": { + "tags": [ + "multiresource:v1" + ], "operationId": "MultiresourceV1Get", "parameters": [], "responses": { @@ -32,6 +46,9 @@ }, "/multiresource/v1/resource1": { "get": { + "tags": [ + "multiresource:v1.Resource1" + ], "operationId": "MultiresourceV1Resource1Get", "parameters": [], "responses": { @@ -46,6 +63,9 @@ }, "/multiresource/v1/resource2": { "get": { + "tags": [ + "multiresource:v1.Resource2" + ], "operationId": "MultiresourceV1Resource2Get", "parameters": [], "responses": { diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/multi_version_endpoint.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/multi_version_endpoint.swagger index ec4900b6..734d7519 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/multi_version_endpoint.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/multi_version_endpoint.swagger @@ -6,6 +6,14 @@ }, "host": "swagger-test.appspot.com", "basePath": "/api", + "tags": [ + { + "name": "myapi:v1" + }, + { + "name": "myapi:v2" + } + ], "schemes": [ "https" ], @@ -18,6 +26,9 @@ "paths": { "/myapi/v1/foo": { "get": { + "tags": [ + "myapi:v1" + ], "operationId": "MyapiV1Get", "parameters": [], "responses": { @@ -32,6 +43,9 @@ }, "/myapi/v2/foo": { "get": { + "tags": [ + "myapi:v2" + ], "operationId": "MyapiV2Get", "parameters": [], "responses": { diff --git a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/third_party_auth.swagger b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/third_party_auth.swagger index ce360959..b27f766e 100644 --- a/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/third_party_auth.swagger +++ b/endpoints-framework/src/test/resources/com/google/api/server/spi/swagger/third_party_auth.swagger @@ -6,6 +6,11 @@ }, "host": "swagger-test.appspot.com", "basePath": "/api", + "tags": [ + { + "name": "thirdparty:v1" + } + ], "schemes": [ "https" ], @@ -18,6 +23,9 @@ "paths": { "/thirdparty/v1/authOverride": { "post": { + "tags": [ + "thirdparty:v1" + ], "operationId": "ThirdpartyV1AuthOverride", "parameters": [], "responses": { @@ -34,6 +42,9 @@ }, "/thirdparty/v1/noOverride": { "post": { + "tags": [ + "thirdparty:v1" + ], "operationId": "ThirdpartyV1NoOverride", "parameters": [], "responses": { diff --git a/gradle.properties b/gradle.properties index 9c58cde0..00f30ed5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,5 +22,5 @@ floggerVersion=0.4 junitVersion=4.12 mockitoVersion=1.10.19 jsonassertVersion=1.5.0 -truthVersion=0.28 +truthVersion=1.0 springtestVersion=3.2.16.RELEASE 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 d681e637..03e318e3 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 @@ -17,6 +17,7 @@ import com.google.api.server.spi.config.Api; import com.google.api.server.spi.config.ApiMethod; +import com.google.api.server.spi.config.Named; import com.google.api.server.spi.response.CollectionResponse; import java.util.Collection; @@ -87,6 +88,30 @@ public ListContainer getListOfString() { return null; } + @ApiMethod + public void setListOfString(@Named("list") List list) {} + + @ApiMethod + public void setListOfBooleans(@Named("list") List list, @Named("array") boolean[] array) {} + + @ApiMethod + public void setListOfIntegers(@Named("list") List list, @Named("array") int[] array) {} + + @ApiMethod + public void setListOfLongs(@Named("list") List list, @Named("array") long[] array) {} + + @ApiMethod + public void setListOfFloats(@Named("list") List list, @Named("array") float[] array) {} + + @ApiMethod + public void setListOfDoubles(@Named("list") List list, @Named("array") double[] array) {} + + @ApiMethod + public void setListOfByteArrays(@Named("list") List list, @Named("array") byte[][] array) {} + + @ApiMethod + public void setListOfEnums(@Named("list") List list) {} + public static class ListContainer { public List strings; }