This is an automated email from the ASF dual-hosted git repository. chesnay pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/flink.git
commit 97a4cc88eee32ad766e5e0c1b647ebce5dc2bfee Author: Chesnay Schepler <ches...@apache.org> AuthorDate: Wed Apr 5 14:49:09 2023 +0200 [FLINK-31733][docs] Detect OpenAPI model name clashes --- .../flink/docs/rest/OpenApiSpecGenerator.java | 32 +++++++- .../flink/docs/rest/OpenApiSpecGeneratorTest.java | 32 ++++++++ .../docs/rest/data/BaseTestMessageHeaders.java | 95 ++++++++++++++++++++++ .../inner/TestNameClashingMessageHeaders1.java | 39 +++++++++ .../inner/TestNameClashingMessageHeaders2.java | 39 +++++++++ .../data/clash/top/pkg1/ClashingRequestBody.java | 27 ++++++ .../TestTopLevelNameClashingMessageHeaders1.java | 32 ++++++++ .../data/clash/top/pkg2/ClashingRequestBody.java | 27 ++++++ .../TestTopLevelNameClashingMessageHeaders2.java | 32 ++++++++ 9 files changed, 354 insertions(+), 1 deletion(-) diff --git a/flink-docs/src/main/java/org/apache/flink/docs/rest/OpenApiSpecGenerator.java b/flink-docs/src/main/java/org/apache/flink/docs/rest/OpenApiSpecGenerator.java index ad5f62e2969..e109a7eb6ba 100644 --- a/flink-docs/src/main/java/org/apache/flink/docs/rest/OpenApiSpecGenerator.java +++ b/flink-docs/src/main/java/org/apache/flink/docs/rest/OpenApiSpecGenerator.java @@ -51,6 +51,7 @@ import io.swagger.v3.core.converter.AnnotatedType; import io.swagger.v3.core.converter.ModelConverterContext; import io.swagger.v3.core.converter.ModelConverterContextImpl; import io.swagger.v3.core.jackson.ModelResolver; +import io.swagger.v3.core.jackson.TypeNameResolver; import io.swagger.v3.core.util.Yaml; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; @@ -103,7 +104,10 @@ public class OpenApiSpecGenerator { JacksonMapperFactory.createObjectMapper() .configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); modelConverterContext = - new ModelConverterContextImpl(Collections.singletonList(new ModelResolver(mapper))); + new ModelConverterContextImpl( + Collections.singletonList( + new ModelResolver( + mapper, new NameClashDetectingTypeNameResolver()))); } @VisibleForTesting @@ -494,4 +498,30 @@ public class OpenApiSpecGenerator { } throw new IllegalArgumentException("not supported"); } + + /** A {@link TypeNameResolver} that detects name-clashes between top-level and inner classes. */ + public static class NameClashDetectingTypeNameResolver extends TypeNameResolver { + private final Map<String, String> seenClassNamesToFQCN = new HashMap<>(); + + @Override + protected String getNameOfClass(Class<?> cls) { + final String modelName = super.getNameOfClass(cls); + + final String fqcn = cls.getCanonicalName(); + final String previousFqcn = seenClassNamesToFQCN.put(modelName, fqcn); + + if (previousFqcn != null && !fqcn.equals(previousFqcn)) { + throw new IllegalStateException( + String.format( + "Detected name clash for model name '%s'.%n" + + "\tClasses:%n" + + "\t\t- %s%n" + + "\t\t- %s%n" + + "\tEither rename the classes or annotate them with '%s' and set a unique 'name'.", + modelName, fqcn, previousFqcn, Schema.class.getCanonicalName())); + } + + return modelName; + } + } } diff --git a/flink-docs/src/test/java/org/apache/flink/docs/rest/OpenApiSpecGeneratorTest.java b/flink-docs/src/test/java/org/apache/flink/docs/rest/OpenApiSpecGeneratorTest.java index 5b83d46c2af..965fd35b272 100644 --- a/flink-docs/src/test/java/org/apache/flink/docs/rest/OpenApiSpecGeneratorTest.java +++ b/flink-docs/src/test/java/org/apache/flink/docs/rest/OpenApiSpecGeneratorTest.java @@ -21,6 +21,10 @@ package org.apache.flink.docs.rest; import org.apache.flink.docs.rest.data.TestAdditionalFieldsMessageHeaders; import org.apache.flink.docs.rest.data.TestEmptyMessageHeaders; import org.apache.flink.docs.rest.data.TestExcludeMessageHeaders; +import org.apache.flink.docs.rest.data.clash.inner.TestNameClashingMessageHeaders1; +import org.apache.flink.docs.rest.data.clash.inner.TestNameClashingMessageHeaders2; +import org.apache.flink.docs.rest.data.clash.top.pkg1.TestTopLevelNameClashingMessageHeaders1; +import org.apache.flink.docs.rest.data.clash.top.pkg2.TestTopLevelNameClashingMessageHeaders2; import org.apache.flink.runtime.rest.util.DocumentingRestEndpoint; import org.apache.flink.runtime.rest.versioning.RuntimeRestAPIVersion; @@ -118,4 +122,32 @@ class OpenApiSpecGeneratorTest { assertThat(x.getAdditionalProperties()) .isInstanceOf(StringSchema.class)); } + + @Test + void testModelNameClashByInnerClassesDetected() { + assertThatThrownBy( + () -> + OpenApiSpecGenerator.createDocumentation( + "title", + DocumentingRestEndpoint.forRestHandlerSpecifications( + new TestNameClashingMessageHeaders1(), + new TestNameClashingMessageHeaders2()), + RuntimeRestAPIVersion.V0)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("clash"); + } + + @Test + void testModelNameClashByTopLevelClassesDetected() { + assertThatThrownBy( + () -> + OpenApiSpecGenerator.createDocumentation( + "title", + DocumentingRestEndpoint.forRestHandlerSpecifications( + new TestTopLevelNameClashingMessageHeaders1(), + new TestTopLevelNameClashingMessageHeaders2()), + RuntimeRestAPIVersion.V0)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("clash"); + } } diff --git a/flink-docs/src/test/java/org/apache/flink/docs/rest/data/BaseTestMessageHeaders.java b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/BaseTestMessageHeaders.java new file mode 100644 index 00000000000..42d58876486 --- /dev/null +++ b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/BaseTestMessageHeaders.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flink.docs.rest.data; + +import org.apache.flink.runtime.rest.HttpMethodWrapper; +import org.apache.flink.runtime.rest.messages.EmptyMessageParameters; +import org.apache.flink.runtime.rest.messages.EmptyResponseBody; +import org.apache.flink.runtime.rest.messages.MessageHeaders; +import org.apache.flink.runtime.rest.messages.RequestBody; +import org.apache.flink.runtime.rest.messages.RuntimeMessageHeaders; +import org.apache.flink.runtime.rest.versioning.RuntimeRestAPIVersion; + +import org.apache.flink.shaded.netty4.io.netty.handler.codec.http.HttpResponseStatus; + +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; + +/** A {@link MessageHeaders} for testing purpose. */ +public abstract class BaseTestMessageHeaders<T extends RequestBody> + implements RuntimeMessageHeaders<T, EmptyResponseBody, EmptyMessageParameters> { + + private static final String URL = "/test/empty"; + private static final String DESCRIPTION = "This is an empty testing REST API."; + + private final String url; + private final String description; + private final String operationId; + + public BaseTestMessageHeaders() { + this(URL, DESCRIPTION, UUID.randomUUID().toString()); + } + + private BaseTestMessageHeaders(String url, String description, String operationId) { + this.url = url; + this.description = description; + this.operationId = operationId; + } + + @Override + public Class<EmptyResponseBody> getResponseClass() { + return EmptyResponseBody.class; + } + + @Override + public HttpMethodWrapper getHttpMethod() { + return HttpMethodWrapper.GET; + } + + @Override + public HttpResponseStatus getResponseStatusCode() { + return HttpResponseStatus.OK; + } + + @Override + public String operationId() { + return operationId; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public EmptyMessageParameters getUnresolvedMessageParameters() { + return EmptyMessageParameters.getInstance(); + } + + @Override + public String getTargetRestEndpointURL() { + return url; + } + + @Override + public Collection<RuntimeRestAPIVersion> getSupportedAPIVersions() { + return Collections.singleton(RuntimeRestAPIVersion.V0); + } +} diff --git a/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/inner/TestNameClashingMessageHeaders1.java b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/inner/TestNameClashingMessageHeaders1.java new file mode 100644 index 00000000000..29e09cd3098 --- /dev/null +++ b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/inner/TestNameClashingMessageHeaders1.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flink.docs.rest.data.clash.inner; + +import org.apache.flink.docs.rest.data.BaseTestMessageHeaders; +import org.apache.flink.runtime.rest.messages.MessageHeaders; +import org.apache.flink.runtime.rest.messages.RequestBody; + +/** A {@link MessageHeaders} for testing purpose. */ +public class TestNameClashingMessageHeaders1 + extends BaseTestMessageHeaders<TestNameClashingMessageHeaders1.ClashingRequestBody> { + + @Override + public Class<ClashingRequestBody> getRequestClass() { + return ClashingRequestBody.class; + } + + /** + * A {@link RequestBody} whose name clashes with {@link + * org.apache.flink.docs.rest.data.clash.inner.TestNameClashingMessageHeaders2.ClashingRequestBody}. + */ + public static class ClashingRequestBody implements RequestBody {} +} diff --git a/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/inner/TestNameClashingMessageHeaders2.java b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/inner/TestNameClashingMessageHeaders2.java new file mode 100644 index 00000000000..9b84061bf6b --- /dev/null +++ b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/inner/TestNameClashingMessageHeaders2.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flink.docs.rest.data.clash.inner; + +import org.apache.flink.docs.rest.data.BaseTestMessageHeaders; +import org.apache.flink.runtime.rest.messages.MessageHeaders; +import org.apache.flink.runtime.rest.messages.RequestBody; + +/** A {@link MessageHeaders} for testing purpose. */ +public class TestNameClashingMessageHeaders2 + extends BaseTestMessageHeaders<TestNameClashingMessageHeaders2.ClashingRequestBody> { + + @Override + public Class<ClashingRequestBody> getRequestClass() { + return ClashingRequestBody.class; + } + + /** + * A {@link RequestBody} whose name clashes with {@link + * org.apache.flink.docs.rest.data.clash.inner.TestNameClashingMessageHeaders1.ClashingRequestBody}. + */ + public static class ClashingRequestBody implements RequestBody {} +} diff --git a/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/top/pkg1/ClashingRequestBody.java b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/top/pkg1/ClashingRequestBody.java new file mode 100644 index 00000000000..5663a65dcb6 --- /dev/null +++ b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/top/pkg1/ClashingRequestBody.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flink.docs.rest.data.clash.top.pkg1; + +import org.apache.flink.runtime.rest.messages.RequestBody; + +/** + * A {@link RequestBody} whose name clashes with {@link + * org.apache.flink.docs.rest.data.clash.top.pkg2.ClashingRequestBody}. + */ +public class ClashingRequestBody implements RequestBody {} diff --git a/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/top/pkg1/TestTopLevelNameClashingMessageHeaders1.java b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/top/pkg1/TestTopLevelNameClashingMessageHeaders1.java new file mode 100644 index 00000000000..a90fa554a69 --- /dev/null +++ b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/top/pkg1/TestTopLevelNameClashingMessageHeaders1.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flink.docs.rest.data.clash.top.pkg1; + +import org.apache.flink.docs.rest.data.BaseTestMessageHeaders; +import org.apache.flink.runtime.rest.messages.MessageHeaders; + +/** A {@link MessageHeaders} for testing purpose. */ +public class TestTopLevelNameClashingMessageHeaders1 + extends BaseTestMessageHeaders<ClashingRequestBody> { + + @Override + public Class<ClashingRequestBody> getRequestClass() { + return ClashingRequestBody.class; + } +} diff --git a/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/top/pkg2/ClashingRequestBody.java b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/top/pkg2/ClashingRequestBody.java new file mode 100644 index 00000000000..fe565f8a7f5 --- /dev/null +++ b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/top/pkg2/ClashingRequestBody.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flink.docs.rest.data.clash.top.pkg2; + +import org.apache.flink.runtime.rest.messages.RequestBody; + +/** + * A {@link RequestBody} whose name clashes with {@link + * org.apache.flink.docs.rest.data.clash.top.pkg1.ClashingRequestBody}. + */ +public class ClashingRequestBody implements RequestBody {} diff --git a/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/top/pkg2/TestTopLevelNameClashingMessageHeaders2.java b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/top/pkg2/TestTopLevelNameClashingMessageHeaders2.java new file mode 100644 index 00000000000..b39bbe03864 --- /dev/null +++ b/flink-docs/src/test/java/org/apache/flink/docs/rest/data/clash/top/pkg2/TestTopLevelNameClashingMessageHeaders2.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flink.docs.rest.data.clash.top.pkg2; + +import org.apache.flink.docs.rest.data.BaseTestMessageHeaders; +import org.apache.flink.runtime.rest.messages.MessageHeaders; + +/** A {@link MessageHeaders} for testing purpose. */ +public class TestTopLevelNameClashingMessageHeaders2 + extends BaseTestMessageHeaders<ClashingRequestBody> { + + @Override + public Class<ClashingRequestBody> getRequestClass() { + return ClashingRequestBody.class; + } +}