1
0
mirror of synced 2026-08-06 07:26:55 +00:00

fix: generate return and param types from api.json (#276)

This commit is contained in:
Yury Semikhatsky
2021-02-10 15:42:03 -08:00
committed by GitHub
parent 442a577506
commit ea34deeb2b
23 changed files with 345 additions and 452 deletions
@@ -24,7 +24,6 @@ import com.google.gson.JsonObject;
import java.io.*;
import java.nio.file.FileSystems;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
import static java.util.Collections.reverse;
@@ -120,48 +119,6 @@ class TypeRef extends Element {
createCustomType();
}
enum GeneratedType { ENUM, CLASS, OTHER };
private static GeneratedType generatedTypeFor(JsonObject jsonType) {
if (jsonType.has("union")) {
if (!jsonType.get("name").getAsString().isEmpty()) {
return GeneratedType.ENUM;
}
for (JsonElement item : jsonType.getAsJsonArray("union")) {
String valueName = item.getAsJsonObject().get("name").getAsString();
if ("null".equals(valueName)) {
continue;
}
GeneratedType memberType = generatedTypeFor(item.getAsJsonObject());
if (memberType == GeneratedType.ENUM) {
// Nullable enum.
return GeneratedType.ENUM;
}
// If a value is not null and not a string it is a class name.
return GeneratedType.OTHER;
}
throw new RuntimeException("Unknown union type: " + jsonType);
}
switch (jsonType.get("name").getAsString()) {
case "Object": {
String expression = typeExpression(jsonType);
if ("Object<string, string>".equals(expression) || "Object<string, any>".equals(expression)) {
return GeneratedType.OTHER;
}
return GeneratedType.CLASS;
}
case "Array":
case "Promise": {
for (JsonElement item : jsonType.getAsJsonArray("templates")) {
return generatedTypeFor(item.getAsJsonObject());
}
return GeneratedType.OTHER;
}
default:
return GeneratedType.OTHER;
}
}
private static String typeExpression(JsonObject jsonType) {
String typeName = jsonType.get("name").getAsString();
if (jsonType.has("union")) {
@@ -200,52 +157,26 @@ class TypeRef extends Element {
}
void createCustomType() {
GeneratedType generatedType = generatedTypeFor(stripNullable());
if (generatedType == GeneratedType.ENUM) {
createEnums(jsonElement.getAsJsonObject());
return;
}
// Use path to the corresponding method, param of field as the key.
String parentPath = parent.jsonPath;
Types.Mapping mapping = TypeDefinition.types.findForPath(parentPath);
if (mapping == null) {
if (generatedType != GeneratedType.CLASS) {
return;
}
if (parent instanceof Field) {
customType = toTitle(parent.jsonName);
} else {
customType = toTitle(parent.parent.jsonName) + toTitle(parent.jsonName);
}
} else {
if (mapping != null) {
String typeExpression = typeExpression(jsonElement.getAsJsonObject());
if (!mapping.from.equals(typeExpression)) {
throw new RuntimeException("Unexpected source type for: " + parentPath +". Expected: " + mapping.from + "; found: " + typeExpression);
}
customType = mapping.to;
if (mapping.customMapping != null) {
mapping.customMapping.defineTypesIn(typeScope());
return;
}
}
if (generatedType == GeneratedType.CLASS) {
if (parent instanceof Field ) {
typeScope().createTopLevelClass(customType, this, jsonElement.getAsJsonObject());
} else {
typeScope().createNestedClass(customType, this, jsonElement.getAsJsonObject());
isNestedClass = true;
}
return;
}
createClassesAndEnums(jsonElement.getAsJsonObject());
}
private void createEnums(JsonObject jsonObject) {
private void createClassesAndEnums(JsonObject jsonObject) {
if (jsonObject.has("union")) {
if (jsonObject.get("name").getAsString().isEmpty()) {
for (JsonElement item : jsonObject.getAsJsonArray("union")) {
if (item.isJsonObject()) {
createEnums(item.getAsJsonObject());
createClassesAndEnums(item.getAsJsonObject());
}
}
} else {
@@ -256,9 +187,26 @@ class TypeRef extends Element {
if (jsonObject.has("templates")) {
for (JsonElement item : jsonObject.getAsJsonArray("templates")) {
if (item.isJsonObject()) {
createEnums(item.getAsJsonObject());
createClassesAndEnums(item.getAsJsonObject());
}
}
return;
}
if ("Object".equals(jsonObject.get("name").getAsString())) {
if (customType != null) {
throw new RuntimeException("Custom type has already been created: " + customType);
}
if (parent.jsonName.equals("cookies")) {
customType = "Cookie";
typeScope().createTopLevelClass(customType, this, jsonElement.getAsJsonObject());
} else if (parent instanceof Method || parent instanceof Field || (parent instanceof Param && !"options".equals(parent.jsonName))) {
customType = toTitle(parent.jsonName);
typeScope().createTopLevelClass(customType, this, jsonElement.getAsJsonObject());
} else {
customType = toTitle(parent.parent.jsonName) + toTitle(parent.jsonName);
typeScope().createNestedClass(customType, this, jsonElement.getAsJsonObject());
isNestedClass = true;
}
}
}
@@ -269,18 +217,6 @@ class TypeRef extends Element {
if (jsonElement.isJsonNull()) {
return "void";
}
// Convert optional fields to boxed types.
if (!parent.jsonElement.getAsJsonObject().get("required").getAsBoolean()) {
if (jsonName.equals("int")) {
return "Integer";
}
if (jsonName.equals("float")) {
return "Double";
}
if (jsonName.equals("boolean")) {
return "Boolean";
}
}
return convertBuiltinType(stripNullable());
}
@@ -525,7 +461,7 @@ class Method extends Element {
"List<Cookie> cookies(List<String> urls);",
});
customSignature.put("BrowserContext.addCookies", new String[]{
"void addCookies(List<AddCookie> cookies);"
"void addCookies(List<Cookie> cookies);"
});
customSignature.put("FileChooser.setFiles", new String[]{
"default void setFiles(Path file) { setFiles(file, null); }",
@@ -771,7 +707,7 @@ class Field extends Element {
jsonElement.getAsJsonObject().get("required").getAsBoolean();
}
void writeTo(List<String> output, String offset, String access) {
void writeTo(List<String> output, String offset) {
writeJavadoc(output, offset, comment());
if (asList("Frame.waitForNavigation.options.url",
"Page.waitForNavigation.options.url").contains(jsonPath)) {
@@ -789,7 +725,17 @@ class Field extends Element {
if (type.isNullable()) {
typeStr = "Optional<" + typeStr + ">";
}
output.add(offset + access + typeStr + " " + name + ";");
// Convert optional fields to boxed types.
if (!isRequired()) {
if (typeStr.equals("int")) {
typeStr = "Integer";
} else if (typeStr.equals("double")) {
typeStr = "Double";
} else if (typeStr.equals("boolean")) {
typeStr = "Boolean";
}
}
output.add(offset + "public " + typeStr + " " + name + ";");
}
void writeGetter(List<String> output, String offset) {
@@ -949,7 +895,7 @@ class Interface extends TypeDefinition {
if ("Playwright".equals(jsonName)) {
output.add("import com.microsoft.playwright.impl.PlaywrightImpl;");
}
if (asList("Page", "Frame", "ElementHandle", "Browser", "BrowserContext", "BrowserType", "Mouse", "Keyboard").contains(jsonName)) {
if (asList("Page", "Request", "Frame", "ElementHandle", "Browser", "BrowserContext", "BrowserType", "Mouse", "Keyboard").contains(jsonName)) {
output.add("import com.microsoft.playwright.options.*;");
}
if (jsonName.equals("Route")) {
@@ -1042,13 +988,6 @@ class Interface extends TypeDefinition {
break;
}
case "ElementHandle": {
output.add(offset + "class BoundingBox {");
output.add(offset + " public double x;");
output.add(offset + " public double y;");
output.add(offset + " public double width;");
output.add(offset + " public double height;");
output.add(offset + "}");
output.add("");
output.add(offset + "class SelectOption {");
output.add(offset + " public String value;");
output.add(offset + " public String label;");
@@ -1131,22 +1070,17 @@ class NestedClass extends TypeDefinition {
if (asList("RecordHar", "RecordVideo").contains(name)) {
output.add("import java.nio.file.Path;");
}
String access = parent.typeScope() instanceof NestedClass ? "public " : "";
String access = (parent.typeScope() instanceof NestedClass) || topLevelClasses().containsKey(name) ? "public " : "";
output.add(offset + access + "class " + name + " {");
String bodyOffset = offset + " ";
super.writeTo(output, bodyOffset);
boolean isReturnType = parent.parent instanceof Method;
String fieldAccess = isReturnType ? "private " : "public ";
for (Field f : fields) {
f.writeTo(output, bodyOffset, fieldAccess);
f.writeTo(output, bodyOffset);
}
output.add("");
if (isReturnType) {
for (Field f : fields) {
f.writeGetter(output, bodyOffset);
}
} else {
if (!isReturnType) {
writeConstructor(output, bodyOffset);
writeBuilderMethods(output, bodyOffset);
if (asList("Browser.newContext.options",
@@ -57,18 +57,12 @@ class Types {
add("Page.addInitScript.script", "Object|function|string", "String");
add("Selectors.register.script", "Object|function|string", "String");
// Return structures
add("ConsoleMessage.location", "Object", "Location");
add("ElementHandle.boundingBox", "Object|null", "BoundingBox", new Empty());
// The method has custom signatures
add("BrowserContext.cookies", "Array<Object>", "Cookie");
add("BrowserContext.cookies.sameSite", "\"Lax\"|\"None\"|\"Strict\"", "SameSite", new Empty());
add("BrowserContext.addCookies.cookies", "Array<Object>", "AddCookie");
add("BrowserContext.addCookies.cookies.sameSite", "\"Lax\"|\"None\"|\"Strict\"", "SameSite", new Empty());
add("BrowserContext.route.url", "RegExp|function(URL):boolean|string", "String");
add("BrowserContext.unroute.url", "RegExp|function(URL):boolean|string", "String");
add("Page.waitForNavigation.options.url", "RegExp|function(URL):boolean|string", "Custom");
add("Page.frame.frameSelector", "Object|string", "Custom", new Empty());
add("Page.frame.options", "Object", "FrameOptions", new Empty());
add("Page.route.url", "RegExp|function(URL):boolean|string", "String");
add("Page.selectOption.values", "Array<ElementHandle>|Array<Object>|Array<string>|ElementHandle|Object|null|string", "String");
@@ -84,11 +78,6 @@ class Types {
add("FileChooser.setFiles.files", "Array<Object>|Array<path>|Object|path", "String");
add("Route.resume.options.postData", "Buffer|string", "byte[]", new Empty());
add("BrowserContext.setGeolocation.geolocation", "Object|null", "Geolocation", new Empty());
add("Browser.newContext.options.geolocation", "Object", "Geolocation", new Empty());
add("Browser.newPage.options.geolocation", "Object", "Geolocation", new Empty());
add("BrowserType.launchPersistentContext.options.geolocation", "Object", "Geolocation", new Empty());
// TODO: fix upstream types!
add("Playwright.devices", "Object", "Map<String, DeviceDescriptor>", new Empty());
}