Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sanitizes model names so they're valid class/interface names #4127

Merged
merged 1 commit into from
Oct 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -426,17 +426,22 @@ private void fixStringModel(Schema m) {
* Generates a unique model name. Non-alphanumeric characters will be replaced
* with underscores
*
* e.g. io.schema.User_name => io_schema_User_name
*
* @param title String title field in the schema if present
* @param key String model name
*
* @return if provided the sanitized {@code title}, else the sanitized {@code key}
*/
private String resolveModelName(String title, String key) {
if (title == null) {
// for auto-generated schema name, replace non-alphanumeric characters with underscore
// to avoid bugs with schema look up with inline schema created on the fly
// e.g. io.schema.User_name => io_schema_User_name
return uniqueName(key).replaceAll("[^A-Za-z0-9]", "_");
if (key == null) {
LOGGER.warn("Found an inline schema without the `title` attribute. Default the model name to InlineObject instead. To have better control of the model naming, define the model separately so that it can be reused throughout the spec.");
return uniqueName("InlineObject");
}
return uniqueName(sanitizeName(key));
} else {
return uniqueName(title);
return uniqueName(sanitizeName(title));
}
}

Expand All @@ -452,29 +457,31 @@ private void addGenerated(String name, Schema model) {
generatedSignature.put(Json.pretty(model), name);
}

private String uniqueName(String key) {
if (key == null) {
key = "InlineObject";
LOGGER.warn("Found an inline schema without the `title` attribute. Default the model name to InlineObject instead. To have better control of the model naming, define the model separately so that it can be reused throughout the spec.");
/**
* Sanitizes the input so that it's valid name for a class or interface
*
* e.g. 12.schema.User name => _2_schema_User_name
*/
private String sanitizeName(final String name) {
return name
.replaceAll("^[0-9]", "_") // e.g. 12object => _2object
.replaceAll("[^A-Za-z0-9]", "_"); // e.g. io.schema.User name => io_schema_User_name
}

private String uniqueName(final String name) {
if (openapi.getComponents().getSchemas() == null) {
return name;
}

String uniqueName = name;
int count = 0;
boolean done = false;
key = key.replaceAll("/", "_"); // e.g. /me/videos => _me_videos
key = key.replaceAll("[^a-z_\\.A-Z0-9 ]", ""); // FIXME: a parameter
Copy link
Contributor Author

@snebjorn snebjorn Oct 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't make sense of why \ and . was allowed, so I removed them.
If anyone got some input on it I'm listening 👂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea either. git blame doesn't reveal useful information. It may be a bug that's present a while ago.

// should not be assigned. Also declare the methods parameters as 'final'.
while (!done) {
String name = key;
if (count > 0) {
name = key + "_" + count;
}
if (openapi.getComponents().getSchemas() == null) {
return name;
} else if (!openapi.getComponents().getSchemas().containsKey(name)) {
return name;
while (true) {
if (!openapi.getComponents().getSchemas().containsKey(uniqueName)) {
return uniqueName;
}
count += 1;
uniqueName = name + "_" + ++count;
}
return key;
// TODO it would probably be a good idea to check against a list of used uniqueNames to make sure there are no collisions
}

private void flattenProperties(Map<String, Schema> properties, String path) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,35 @@ public void resolveInlineModelTestWithTitle() {
assertNotNull(address.getProperties().get("street"));
}

@Test
public void resolveInlineModelTestWithTitleWithSpaces() {
OpenAPI openapi = new OpenAPI();
openapi.setComponents(new Components());
openapi.getComponents().addSchemas("User", new ObjectSchema()
.name("user")
.description("a common user")
.addProperties("name", new StringSchema())
.addProperties("address", new ObjectSchema()
.title("User Address Title")
.readOnly(false)
.description("description")
.name("name")
.addProperties("street", new StringSchema())
.addProperties("city", new StringSchema())));

new InlineModelResolver().flatten(openapi);

Schema user = openapi.getComponents().getSchemas().get("User");

assertNotNull(user);
assertTrue(user.getProperties().get("address") instanceof Schema);

Schema address = openapi.getComponents().getSchemas().get("User_Address_Title");
assertNotNull(address);
assertNotNull(address.getProperties().get("city"));
assertNotNull(address.getProperties().get("street"));
}

@Test
public void resolveInlineModel2EqualInnerModels() {
OpenAPI openapi = new OpenAPI();
Expand Down