diff --git a/CHANGELOG b/CHANGELOG index d862d4f08c..fc4fbe85a6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ 6.0.2 ------ +(RB=710508) +Fix doc generation for action methods with custom parameter types. + (RB=701756) Added cookies to isEquals/hashCode/toString in Request.java diff --git a/data/src/test/java/com/linkedin/data/template/TestDynamicRecordTemplate.java b/data/src/test/java/com/linkedin/data/template/TestDynamicRecordTemplate.java index a8176c5ffb..bea45c2362 100644 --- a/data/src/test/java/com/linkedin/data/template/TestDynamicRecordTemplate.java +++ b/data/src/test/java/com/linkedin/data/template/TestDynamicRecordTemplate.java @@ -20,7 +20,9 @@ import com.linkedin.data.ByteString; import com.linkedin.data.DataMap; import com.linkedin.data.schema.RecordDataSchema; + import java.util.ArrayList; + import org.testng.Assert; import org.testng.annotations.Test; @@ -46,7 +48,8 @@ public class TestDynamicRecordTemplate "{ \"name\" : \"enumArray\", \"type\" : { \"type\" : \"array\", \"items\" : \"EnumType\" } }, \n" + "{ \"name\" : \"fixed\", \"type\" : { \"type\" : \"fixed\", \"name\" : \"fixedType\", \"size\" : 4 } }, \n" + "{ \"name\" : \"fixedArray\", \"type\" : { \"type\" : \"array\", \"items\" : \"fixedType\" } }, \n" + - "{ \"name\" : \"record\", \"type\" : { \"type\" : \"record\", \"name\" : \"Bar\", \"fields\" : [ { \"name\" : \"int\", \"type\" : \"int\" } ] } } \n" + + "{ \"name\" : \"record\", \"type\" : { \"type\" : \"record\", \"name\" : \"Bar\", \"fields\" : [ { \"name\" : \"int\", \"type\" : \"int\" } ] } }, \n" + + "{ \"name\" : \"typeRef\", \"type\" : { \"type\" : \"typeref\", \"name\" : \"CustomNumber\", \"ref\" : \"int\", \"java\" : { \"class\" : \"com.linkedin.data.template.TestDynamicRecordTemplate.CustomNumber\" } } } \n" + "] }" ); @@ -81,6 +84,8 @@ public class TestDynamicRecordTemplate new FieldDef("enumArray", TestRecordAndUnionTemplate.EnumType[].class, SCHEMA.getField("enumArray").getType()); public static final FieldDef FIELD_intArrayTemplate = new FieldDef("intArrayTemplate", IntegerArray.class, SCHEMA.getField("intArray").getType()); + public static final FieldDef FIELD_typeRef = + new FieldDef("typeRef", CustomNumber.class, SCHEMA.getField("typeRef").getType()); public static DynamicRecordMetadata METADATA; static @@ -101,6 +106,7 @@ public class TestDynamicRecordTemplate fieldDefs.add(FIELD_record); fieldDefs.add(FIELD_recordArray); fieldDefs.add(FIELD_string); + fieldDefs.add(FIELD_typeRef); METADATA = new DynamicRecordMetadata("dynamic", fieldDefs); } @@ -260,6 +266,53 @@ public void setFixedArray(TestRecordAndUnionTemplate.FixedType[] value) { setValue(FIELD_fixedArray, value); } + + public CustomNumber getTypeRef() + { + return getValue(FIELD_typeRef); + } + + public void setTypeRef(CustomNumber value) + { + setValue(FIELD_typeRef, value); + } + } + + public static class CustomNumber + { + private int _num; + + public CustomNumber(int n) + { + _num = n * 100; + } + + public int getNum() + { + return _num; + } + + public static class CustomNumberCoercer implements DirectCoercer + { + public Object coerceInput(CustomNumber object) throws ClassCastException + { + return object.getNum() / 100; + } + + public CustomNumber coerceOutput(Object object) throws TemplateOutputCastException + { + if (object instanceof Integer == false) + { + throw new TemplateOutputCastException("Output " + object + " is not a integer, and cannot be coerced to " + CustomNumber.class.getName()); + } + return new CustomNumber((Integer) object); + } + } + + static + { + Custom.registerCoercer(new CustomNumberCoercer(), CustomNumber.class); + } } @Test @@ -300,6 +353,15 @@ public void TestComplexTypeFieldsOnDynamicRecord() Assert.assertEquals(fixed, foo.getFixed()); } + @Test + public void TestTypeRefOnDynamicRecord() + { + DynamicFoo foo = new DynamicFoo(); + + foo.setTypeRef(new CustomNumber(5)); + Assert.assertEquals(500, foo.getTypeRef().getNum()); + } + @Test public void TestArrayFieldsOnDynamicRecord() { diff --git a/restli-docgen/src/main/java/com/linkedin/restli/docgen/examplegen/ExampleRequestResponseGenerator.java b/restli-docgen/src/main/java/com/linkedin/restli/docgen/examplegen/ExampleRequestResponseGenerator.java index d06aa3cc5e..d73c885573 100644 --- a/restli-docgen/src/main/java/com/linkedin/restli/docgen/examplegen/ExampleRequestResponseGenerator.java +++ b/restli-docgen/src/main/java/com/linkedin/restli/docgen/examplegen/ExampleRequestResponseGenerator.java @@ -21,6 +21,7 @@ import com.linkedin.data.DataMap; import com.linkedin.data.schema.ArrayDataSchema; import com.linkedin.data.schema.DataSchema; +import com.linkedin.data.schema.DataSchema.Type; import com.linkedin.data.schema.DataSchemaResolver; import com.linkedin.data.schema.EnumDataSchema; import com.linkedin.data.schema.FixedDataSchema; @@ -115,6 +116,7 @@ import com.linkedin.restli.server.ResourceLevel; import com.linkedin.restli.server.RestLiServiceException; import com.linkedin.restli.server.UpdateResponse; + import java.io.IOException; import java.net.URI; import java.util.ArrayList; @@ -771,6 +773,14 @@ private void addParams(ActionRequestBuilder request, DynamicRecordMetadata { FieldDef fieldDef = requestMetadata.getFieldDef(parameter.getName()); Object value = generateFieldDefValue(fieldDef); + // For custom types(TypeRefs) we generate the example values using the dereferenced type. Changing the field-def + // to the dereferenced type so the example values can be set on the request without coercing. + if (fieldDef.getDataSchema().getType() == Type.TYPEREF) + { + FieldDef deRefFieldDef = new FieldDef<>(fieldDef.getName(), fieldDef.getDataClass(), fieldDef.getDataSchema().getDereferencedDataSchema()); + deRefFieldDef.getField().setRecord(fieldDef.getField().getRecord()); + fieldDef = deRefFieldDef; + } request.setParam(fieldDef, value); } } diff --git a/restli-int-test-server/src/test/java/com/linkedin/restli/docgen/TestExamplesGenerator.java b/restli-int-test-server/src/test/java/com/linkedin/restli/docgen/TestExamplesGenerator.java index b547038d6b..7e7af16993 100644 --- a/restli-int-test-server/src/test/java/com/linkedin/restli/docgen/TestExamplesGenerator.java +++ b/restli-int-test-server/src/test/java/com/linkedin/restli/docgen/TestExamplesGenerator.java @@ -41,6 +41,7 @@ import com.linkedin.restli.examples.greetings.api.Greeting; import com.linkedin.restli.examples.greetings.server.ActionsResource; import com.linkedin.restli.examples.greetings.server.CollectionUnderSimpleResource; +import com.linkedin.restli.examples.greetings.server.CustomTypesResource; import com.linkedin.restli.examples.greetings.server.GreetingsResource; import com.linkedin.restli.examples.greetings.server.RootSimpleResource; import com.linkedin.restli.examples.greetings.server.SimpleResourceUnderCollectionResource; @@ -65,6 +66,7 @@ import com.linkedin.restli.restspec.RestMethodSchemaArray; import com.linkedin.restli.restspec.SimpleSchema; import com.linkedin.restli.server.ResourceLevel; + import java.io.IOException; import java.net.URI; import java.util.Arrays; @@ -73,6 +75,7 @@ import java.util.List; import java.util.Map; import java.util.Set; + import org.testng.Assert; import org.testng.annotations.Test; @@ -93,7 +96,8 @@ public void testExamples() throws IOException GroupMembershipsResource2.class, RootSimpleResource.class, CollectionUnderSimpleResource.class, - SimpleResourceUnderCollectionResource.class); + SimpleResourceUnderCollectionResource.class, + CustomTypesResource.class); final ResourceSchemaCollection resourceSchemas = ResourceSchemaCollection.loadOrCreateResourceSchema(resources); final DataSchemaResolver schemaResolver = new ClassNameDataSchemaResolver(); final ValidationOptions valOptions = new ValidationOptions(RequiredMode.MUST_BE_PRESENT); @@ -115,6 +119,9 @@ public void testExamples() throws IOException final ResourceSchema actions = resourceSchemas.getResource("actions"); ExampleRequestResponseGenerator actionsGenerator = new ExampleRequestResponseGenerator(actions, schemaResolver); + final ResourceSchema customTypes = resourceSchemas.getResource("customTypes"); + ExampleRequestResponseGenerator customTypesGenerator = new ExampleRequestResponseGenerator(customTypes, schemaResolver); + List subResources = resourceSchemas.getSubResources(greeting); final ResourceSchema subgreetings = subResources.get(0); ExampleRequestResponseGenerator subgreetingsGenerator = new ExampleRequestResponseGenerator(Collections.singletonList(greeting), subgreetings, schemaResolver); @@ -251,6 +258,14 @@ public void testExamples() throws IOException capture = actionsGenerator.action("echoStringArray", ResourceLevel.COLLECTION); final DataMap echoStringArrayResponse = DataMapUtils.readMap(capture.getResponse()); Assert.assertTrue(echoStringArrayResponse.containsKey("value")); + + capture = customTypesGenerator.action("action", ResourceLevel.COLLECTION); + DataMap requestMap = _codec.bytesToMap(capture.getRequest().getEntity().copyBytes()); + Assert.assertTrue(requestMap.containsKey("l")); + Assert.assertEquals(requestMap.size(), 1); + final DataMap customTypesActionResponse = DataMapUtils.readMap(capture.getResponse()); + Assert.assertTrue(customTypesActionResponse.containsKey("value")); + Assert.assertEquals(customTypesActionResponse.size(), 1); } private static void checkPatchMap(DataMap patchMap)