Skip to content

Commit

Permalink
Merge pull request swagger-api#2695 from swagger-api/ticket-2169
Browse files Browse the repository at this point in the history
refs swagger-api#2169 - rework JsonProperty, readonly/writeonly support
  • Loading branch information
frantuma authored Mar 2, 2018
2 parents 3812ba3 + 3fe3b70 commit 30a24a9
Show file tree
Hide file tree
Showing 6 changed files with 627 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverter;
Expand Down Expand Up @@ -449,12 +450,25 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
String propName = propDef.getName();
Annotation[] annotations = null;

AnnotatedMember member = propDef.getPrimaryMember();
if (member == null) {
final BeanDescription deserBeanDesc = Json.mapper().getDeserializationConfig().introspect(type);
List<BeanPropertyDefinition> deserProperties = deserBeanDesc.findProperties();
for (BeanPropertyDefinition prop : deserProperties) {
if (StringUtils.isNotBlank(prop.getInternalName()) && prop.getInternalName().equals(propDef.getInternalName())) {
member = prop.getPrimaryMember();
break;
}
}
}


// hack to avoid clobbering properties with get/is names
// it's ugly but gets around https://github.com/swagger-api/swagger-core/issues/415
if (propDef.getPrimaryMember() != null) {
java.lang.reflect.Member member = propDef.getPrimaryMember().getMember();
if (member != null) {
String altName = member.getName();
if (member != null) {
java.lang.reflect.Member innerMember = member.getMember();
if (innerMember != null) {
String altName = innerMember.getName();
if (altName != null) {
final int length = altName.length();
for (String prefix : Arrays.asList("get", "is")) {
Expand All @@ -471,35 +485,6 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context

PropertyMetadata md = propDef.getMetadata();

boolean hasSetter = false, hasGetter = false;
try {
if (propDef.getSetter() == null) {
hasSetter = false;
} else {
hasSetter = true;
}
} catch (IllegalArgumentException e) {
//com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder would throw IllegalArgumentException
// if there are overloaded setters. If we only want to know whether a set method exists, suppress the exception
// is reasonable.
// More logs might be added here
hasSetter = true;
}
if (propDef.getGetter() != null) {
JsonProperty pd = propDef.getGetter().getAnnotation(JsonProperty.class);
if (pd != null) {
//if (pd != null && pd.access().equals(JsonProperty.Access.READ_ONLY)) { to be extended
hasGetter = true;
}
}
Boolean isReadOnly = null;
if (!hasSetter & hasGetter) {
isReadOnly = Boolean.TRUE;
} else {
isReadOnly = Boolean.FALSE;
}

final AnnotatedMember member = propDef.getPrimaryMember();
if (member != null && !ignore(member, xmlAccessorTypeAnnotation, propName, propertiesToIgnore)) {

List<Annotation> annotationList = new ArrayList<Annotation>();
Expand All @@ -514,6 +499,12 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
}

JavaType propType = member.getType();
if(propType != null && "void".equals(propType.getRawClass().getName())) {
if (member instanceof AnnotatedMethod) {
propType = ((AnnotatedMethod)member).getParameterType(0);
}

}
Annotation propSchemaOrArray = AnnotationsUtils.mergeSchemaAnnotations(annotations, propType);
final io.swagger.v3.oas.annotations.media.Schema propResolvedSchemaAnnotation =
propSchemaOrArray == null ?
Expand All @@ -524,6 +515,10 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
if (propResolvedSchemaAnnotation != null && !propResolvedSchemaAnnotation.name().isEmpty()) {
propName = propResolvedSchemaAnnotation.name();
}

io.swagger.v3.oas.annotations.media.Schema.AccessMode accessMode = resolveAccessMode(propDef, type, propResolvedSchemaAnnotation);


AnnotatedType aType = new AnnotatedType()
.type(propType)
.ctxAnnotations(annotations)
Expand All @@ -534,8 +529,9 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
.skipSchemaName(true)
.schemaProperty(true);

final AnnotatedMember propMember = member;
aType.jsonUnwrappedHandler((t) -> {
JsonUnwrapped uw = member.getAnnotation(JsonUnwrapped.class);
JsonUnwrapped uw = propMember.getAnnotation(JsonUnwrapped.class);
if (uw != null && uw.enabled()) {
t
.ctxAnnotations(null)
Expand All @@ -556,10 +552,24 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
Boolean required = md.getRequired();
if (required != null && !Boolean.FALSE.equals(required)) {
addRequiredItem(model, propName);
} else {
if (propDef.isRequired()) {
addRequiredItem(model, propName);
}
}
if (property.getReadOnly() == null) {
if (isReadOnly) {
property.readOnly(isReadOnly);
if (accessMode != null) {
switch (accessMode) {
case AUTO:
break;
case READ_ONLY:
property.readOnly(true);
break;
case READ_WRITE:
break;
case WRITE_ONLY:
property.writeOnly(true);
break;
default:
}
}
}
Expand Down Expand Up @@ -1242,6 +1252,62 @@ protected Object resolveExample(Annotated a, Annotation[] annotations, io.swagge
return null;
}

protected io.swagger.v3.oas.annotations.media.Schema.AccessMode resolveAccessMode(BeanPropertyDefinition propDef, JavaType type, io.swagger.v3.oas.annotations.media.Schema schema) {
if (schema != null && !schema.accessMode().equals(io.swagger.v3.oas.annotations.media.Schema.AccessMode.AUTO)) {
return schema.accessMode();
} else if (schema != null && schema.readOnly()) {
return io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
} else if (schema != null && schema.writeOnly()) {
return io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY;
}

JsonProperty.Access access = null;
if (propDef instanceof POJOPropertyBuilder) {
access = ((POJOPropertyBuilder) propDef).findAccess();
}
boolean hasGetter = propDef.hasGetter();
boolean hasSetter = propDef.hasSetter();
boolean hasConstructorParameter = propDef.hasConstructorParameter();
boolean hasField = propDef.hasField();


if (access == null) {
final BeanDescription beanDesc = Json.mapper().getDeserializationConfig().introspect(type);
List<BeanPropertyDefinition> properties = beanDesc.findProperties();
for (BeanPropertyDefinition prop : properties) {
if (StringUtils.isNotBlank(prop.getInternalName()) && prop.getInternalName().equals(propDef.getInternalName())) {
if (prop instanceof POJOPropertyBuilder) {
access = ((POJOPropertyBuilder) prop).findAccess();
}
hasGetter = hasGetter || prop.hasGetter();
hasSetter = hasSetter || prop.hasSetter();
hasConstructorParameter = hasConstructorParameter || prop.hasConstructorParameter();
hasField = hasField || prop.hasField();
break;
}
}
}
if (access == null) {
if (!hasGetter && !hasField && (hasConstructorParameter || hasSetter)) {
return io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY;
}
return null;
} else {
switch (access) {
case AUTO:
return io.swagger.v3.oas.annotations.media.Schema.AccessMode.AUTO;
case READ_ONLY:
return io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
case READ_WRITE:
return io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE;
case WRITE_ONLY:
return io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY;
default:
return io.swagger.v3.oas.annotations.media.Schema.AccessMode.AUTO;
}
}
}

protected Boolean resolveReadOnly(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
if (schema != null && schema.accessMode().equals(io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY)) {
return true;
Expand All @@ -1255,6 +1321,7 @@ protected Boolean resolveReadOnly(Annotated a, Annotation[] annotations, io.swag
return null;
}


protected Boolean resolveNullable(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
if (schema != null && schema.nullable()) {
return schema.nullable();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.swagger.v3.core.jackson;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.introspect.Annotated;
Expand Down Expand Up @@ -39,16 +40,24 @@ public boolean hasIgnoreMarker(AnnotatedMember m) {

@Override
public Boolean hasRequiredMarker(AnnotatedMember m) {
Schema ann = m.getAnnotation(Schema.class);
if (ann != null) {
return ann.required();
}
XmlElement elem = m.getAnnotation(XmlElement.class);
if (elem != null) {
if (elem.required()) {
return true;
}
}
JsonProperty jsonProperty = m.getAnnotation(JsonProperty.class);
if (jsonProperty != null) {
if (jsonProperty.required()) {
return true;
}
}
Schema ann = m.getAnnotation(Schema.class);
if (ann != null) {
if (ann.required()) {
return ann.required();
}
}
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;

public class JacksonReadonlyModel {
@JsonProperty
@JsonProperty (access = JsonProperty.Access.READ_ONLY)
public Integer getCount() {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package io.swagger.v3.core.resolving;

import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.core.matchers.SerializationMatchers;
import io.swagger.v3.core.resolving.resources.User2169;
import org.testng.annotations.Test;

public class JsonPropertyTest {

@Test(description = "test ticket 2169")
public void testTicket2169() {

SerializationMatchers.assertEqualsToYaml(ModelConverters.getInstance().read(User2169.class), "User2169:\n" +
" required:\n" +
" - Age\n" +
" - GetterJsonPropertyOnField\n" +
" - GetterJsonPropertyOnFieldReadOnly\n" +
" - GetterJsonPropertyOnFieldReadWrite\n" +
" - GetterJsonPropertyOnFieldReadWriteCreatorSchemaReadOnlyFalse\n" +
" - GetterJsonPropertyOnFieldReadWriteSchemaReadOnlyFalse\n" +
" - GetterJsonPropertyOnFieldSchemaReadOnlyTrue\n" +
" - Name\n" +
" type: object\n" +
" properties:\n" +
" Name:\n" +
" type: string\n" +
" Age:\n" +
" type: integer\n" +
" format: int32\n" +
" GetterJsonPropertyOnFieldReadWriteCreatorSchemaReadOnlyFalse:\n" +
" type: string\n" +
" publi:\n" +
" type: string\n" +
" getter:\n" +
" type: string\n" +
" setter:\n" +
" type: string\n" +
" writeOnly: true\n" +
" getterSetter:\n" +
" type: string\n" +
" jsonProp:\n" +
" type: string\n" +
" jsonPropReadOnly:\n" +
" type: string\n" +
" readOnly: true\n" +
" jsonPropWriteOnly:\n" +
" type: string\n" +
" writeOnly: true\n" +
" jsonPropReadWrite:\n" +
" type: string\n" +
" getter_jsonProp:\n" +
" type: string\n" +
" getter_jsonPropReadOnly:\n" +
" type: string\n" +
" readOnly: true\n" +
" getter_jsonPropWriteOnly:\n" +
" type: string\n" +
" getter_jsonPropReadWrite:\n" +
" type: string\n" +
" setter_jsonProp:\n" +
" type: string\n" +
" writeOnly: true\n" +
" setter_jsonPropReadOnly:\n" +
" type: string\n" +
" setter_jsonPropWriteOnly:\n" +
" type: string\n" +
" writeOnly: true\n" +
" setter_jsonPropReadWrite:\n" +
" type: string\n" +
" gettersetter_jsonPropGet:\n" +
" type: string\n" +
" gettersetter_jsonPropReadOnlyGet:\n" +
" type: string\n" +
" readOnly: true\n" +
" gettersetter_jsonPropWriteOnlyGet:\n" +
" type: string\n" +
" gettersetter_jsonPropReadWriteGet:\n" +
" type: string\n" +
" gettersetter_jsonPropSet:\n" +
" type: string\n" +
" gettersetter_jsonPropReadOnlySet:\n" +
" type: string\n" +
" gettersetter_jsonPropWriteOnlySet:\n" +
" type: string\n" +
" writeOnly: true\n" +
" gettersetter_jsonPropReadWriteSet:\n" +
" type: string\n" +
" getterIgnore_jsonPropSet:\n" +
" type: string\n" +
" writeOnly: true\n" +
" getterIgnore_jsonPropReadOnlySet:\n" +
" type: string\n" +
" getterIgnore_jsonPropWriteOnlySet:\n" +
" type: string\n" +
" writeOnly: true\n" +
" getterIgnore_jsonPropReadWriteSet:\n" +
" type: string\n" +
" setterIgnore_jsonPropGet:\n" +
" type: string\n" +
" setterIgnore_jsonPropReadOnlyGet:\n" +
" type: string\n" +
" readOnly: true\n" +
" setterIgnore_jsonPropWriteOnlyGet:\n" +
" type: string\n" +
" setterIgnore_jsonPropReadWriteGet:\n" +
" type: string\n" +
" getterSchemaReadOnlyTrue:\n" +
" type: string\n" +
" readOnly: true\n" +
" data:\n" +
" $ref: '#/components/schemas/Data'\n" +
" GetterJsonPropertyOnField:\n" +
" type: string\n" +
" GetterJsonPropertyOnFieldReadWrite:\n" +
" type: string\n" +
" GetterJsonPropertyOnFieldReadWriteSchemaReadOnlyFalse:\n" +
" type: string\n" +
" GetterJsonPropertyOnFieldReadOnly:\n" +
" type: string\n" +
" readOnly: true\n" +
" GetterJsonPropertyOnFieldSchemaReadOnlyTrue:\n" +
" type: string\n" +
" readOnly: true\n" +
" approvePairing:\n" +
" type: boolean\n" +
" writeOnly: true\n");
}
}
Loading

0 comments on commit 30a24a9

Please sign in to comment.