Skip to content

Commit dc1bdac

Browse files
[Python-experimental] JSON schema 'null' type should be modeled as 'none_type' (#6121)
* Handle null type * Handle null type * Handle null type. Add 'null' type in the OAS document for testing purpose * Handle null type. Add 'null' type in the OAS document for testing purpose * Handle null type. Add 'null' type in the OAS document for testing purpose * Handle null type. Add 'null' type in the OAS document for testing purpose * Handle null type. Add 'null' type in the OAS document for testing purpose * Handle null type. Add 'null' type in the OAS document for testing purpose * improve documentation * Handle 'null' type * Handle 'null' type. Add unit tests * Add NullType for go * Add NullType for go * fix modeling of AnyType for go-experimental * execute scripts in bin directory * Add review comments * Add 'null' type in oneOf * Improve OAS YAML file for golang openapi3 samples * 'Any type' includes the null value, so 'isNullable' should be set to TRUE * 'Any type' includes the null value, so 'isNullable' should be set to TRUE * Handle AnyType and NullType * handle anytype for go-experimental * Log warning instead of error * anyOf/oneOf * Change x-golang-is-container extension to x-golang-has-wrapper * Add code comments * Handle Object and any type * Handle Object and any type * Handle object and any type * add code comments * handle additional properties * handle additional properties * handle additional properties * handle anytype and objecttype for go-exerimental * Move golang changes to a separate branch * Move golang changes to a separate branch * Better names for the OAS document test properties * Move golang changes to a separate branch * Run samples scripts * Run samples scripts * fix unit test issues * Handle none type * Fix index out of range exception * fix formatting issues * fix formatting issues * fix formatting issues. Finally figured out how to check formatting in local workspace * fix formatting issues * run samples scripts
1 parent 2c5675a commit dc1bdac

File tree

16 files changed

+154
-44
lines changed

16 files changed

+154
-44
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ public class CodegenProperty implements Cloneable, IJsonSchemaValidationProperti
134134
public boolean isUri;
135135
public boolean isEmail;
136136
/**
137-
* The type is a free-form object, i.e. it is a map of string to values with no declared properties
137+
* The type is a free-form object, i.e. it is a map of string to values with no declared properties.
138+
* A OAS free-form schema may include the 'additionalProperties' attribute, which puts a constraint
139+
* on the type of the undeclared properties.
138140
*/
139141
public boolean isFreeFormObject;
140142
/**
@@ -153,6 +155,9 @@ public class CodegenProperty implements Cloneable, IJsonSchemaValidationProperti
153155
public boolean isDiscriminator;
154156
public List<String> _enum;
155157
public Map<String, Object> allowableValues;
158+
// If 'additionalProperties' is not set, items is null.
159+
// If 'additionalProperties' is set to a type or refers to a type, 'items' provides the type information for
160+
// the undeclared properties.
156161
public CodegenProperty items;
157162
public CodegenProperty mostInnerItems;
158163
public Map<String, Object> vendorExtensions = new HashMap<String, Object>();

modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2180,7 +2180,14 @@ public CodegenModel fromModel(String name, Schema schema) {
21802180
m.xmlNamespace = schema.getXml().getNamespace();
21812181
m.xmlName = schema.getXml().getName();
21822182
}
2183-
2183+
if (ModelUtils.isAnyTypeSchema(schema)) {
2184+
// The 'null' value is allowed when the OAS schema is 'any type'.
2185+
// See https://github.com/OAI/OpenAPI-Specification/issues/1389
2186+
if (Boolean.FALSE.equals(schema.getNullable())) {
2187+
LOGGER.error("Schema '{}' is any type, which includes the 'null' value. 'nullable' cannot be set to 'false'", name);
2188+
}
2189+
m.isNullable = true;
2190+
}
21842191
if (ModelUtils.isArraySchema(schema)) {
21852192
m.isArrayModel = true;
21862193
m.arrayModelType = fromProperty(name, schema).complexType;
@@ -3030,6 +3037,12 @@ public CodegenProperty fromProperty(String name, Schema p) {
30303037
} else if (ModelUtils.isFreeFormObject(p)) {
30313038
property.isFreeFormObject = true;
30323039
} else if (ModelUtils.isAnyTypeSchema(p)) {
3040+
// The 'null' value is allowed when the OAS schema is 'any type'.
3041+
// See https://github.com/OAI/OpenAPI-Specification/issues/1389
3042+
if (Boolean.FALSE.equals(p.getNullable())) {
3043+
LOGGER.warn("Schema '{}' is any type, which includes the 'null' value. 'nullable' cannot be set to 'false'", p.getName());
3044+
}
3045+
property.isNullable = true;
30333046
property.isAnyType = true;
30343047
} else if (ModelUtils.isArraySchema(p)) {
30353048
// default to string if inner item is undefined

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ public PythonClientCodegen() {
143143
// map uuid to string for the time being
144144
typeMapping.put("UUID", "str");
145145
typeMapping.put("URI", "str");
146+
typeMapping.put("null", "none_type");
146147

147148
// from https://docs.python.org/3/reference/lexical_analysis.html#keywords
148149
setReservedWordsLowerCase(

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,9 @@ public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
388388
composedSchemaSets.add(cm.oneOf);
389389
for (Set<String> importSet : composedSchemaSets) {
390390
for (String otherModelName : importSet) {
391-
cm.imports.add(otherModelName);
391+
if (!languageSpecificPrimitives.contains(otherModelName)) {
392+
cm.imports.add(otherModelName);
393+
}
392394
}
393395
}
394396

modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/OneOfImplementorAdditionalData.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,11 @@ public void addFromInterfaceModel(CodegenModel cm, List<Map<String, String>> mod
7575
// note that we can't just toAdd.removeAll(m.vars) for every interfaceModel,
7676
// as they might have different value of `hasMore` and thus are not equal
7777
List<String> omitAdding = new ArrayList<String>();
78-
for (CodegenModel m : cm.interfaceModels) {
79-
for (CodegenProperty v : m.vars) {
80-
omitAdding.add(v.baseName);
78+
if (cm.interfaceModels != null) {
79+
for (CodegenModel m : cm.interfaceModels) {
80+
for (CodegenProperty v : m.vars) {
81+
omitAdding.add(v.baseName);
82+
}
8183
}
8284
}
8385
for (CodegenProperty v : toAdd) {

modules/openapi-generator/src/main/resources/python/python-experimental/model_utils.mustache

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,7 +1153,7 @@ def get_oneof_instance(self, model_args, constant_args):
11531153
and path to item.
11541154

11551155
Returns
1156-
oneof_instance (instance/None)
1156+
oneof_instance (instance)
11571157
"""
11581158
if len(self._composed_schemas['oneOf']) == 0:
11591159
return None
@@ -1162,6 +1162,13 @@ def get_oneof_instance(self, model_args, constant_args):
11621162
# Iterate over each oneOf schema and determine if the input data
11631163
# matches the oneOf schemas.
11641164
for oneof_class in self._composed_schemas['oneOf']:
1165+
# The composed oneOf schema allows the 'null' type and the input data
1166+
# is the null value. This is a OAS >= 3.1 feature.
1167+
if oneof_class is none_type:
1168+
# skip none_types because we are deserializing dict data.
1169+
# none_type deserialization is handled in the __new__ method
1170+
continue
1171+
11651172
# transform js keys from input data to python keys in fixed_model_args
11661173
fixed_model_args = change_keys_js_to_python(
11671174
model_args, oneof_class)
@@ -1207,9 +1214,11 @@ def get_anyof_instances(self, model_args, constant_args):
12071214
Args:
12081215
self: the class we are handling
12091216
model_args (dict): var_name to var_value
1210-
used to make instances
1217+
The input data, e.g. the payload that must match at least one
1218+
anyOf child schema in the OpenAPI document.
12111219
constant_args (dict): var_name to var_value
1212-
used to make instances
1220+
args that every model requires, including configuration, server
1221+
and path to item.
12131222

12141223
Returns
12151224
anyof_instances (list)
@@ -1219,6 +1228,13 @@ def get_anyof_instances(self, model_args, constant_args):
12191228
return anyof_instances
12201229

12211230
for anyof_class in self._composed_schemas['anyOf']:
1231+
# The composed oneOf schema allows the 'null' type and the input data
1232+
# is the null value. This is a OAS >= 3.1 feature.
1233+
if anyof_class is none_type:
1234+
# skip none_types because we are deserializing dict data.
1235+
# none_type deserialization is handled in the __new__ method
1236+
continue
1237+
12221238
# transform js keys to python keys in fixed_model_args
12231239
fixed_model_args = change_keys_js_to_python(model_args, anyof_class)
12241240

modules/openapi-generator/src/test/resources/3_0/python-experimental/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,22 +1250,32 @@ components:
12501250
type: integer
12511251
format: int32
12521252
description: User Status
1253-
arbitraryObject:
1253+
objectWithNoDeclaredProps:
12541254
type: object
12551255
description: test code generation for objects
12561256
Value must be a map of strings to values. It cannot be the 'null' value.
1257-
arbitraryNullableObject:
1257+
objectWithNoDeclaredPropsNullable:
12581258
type: object
12591259
description: test code generation for nullable objects.
12601260
Value must be a map of strings to values or the 'null' value.
12611261
nullable: true
1262-
arbitraryTypeValue:
1262+
anyTypeProp:
12631263
description: test code generation for any type
1264-
Value can be any type - string, number, boolean, array or object.
1265-
arbitraryNullableTypeValue:
1264+
Here the 'type' attribute is not specified, which means the value can be anything,
1265+
including the null value, string, number, boolean, array or object.
1266+
See https://github.com/OAI/OpenAPI-Specification/issues/1389
1267+
# TODO: this should be supported, currently there are some issues in the code generation.
1268+
#anyTypeExceptNullProp:
1269+
# description: any type except 'null'
1270+
# Here the 'type' attribute is not specified, which means the value can be anything,
1271+
# including the null value, string, number, boolean, array or object.
1272+
# not:
1273+
# type: 'null'
1274+
anyTypePropNullable:
12661275
description: test code generation for any type
1267-
Value can be any type - string, number, boolean, array, object or
1268-
the 'null' value.
1276+
Here the 'type' attribute is not specified, which means the value can be anything,
1277+
including the null value, string, number, boolean, array or object.
1278+
The 'nullable' attribute does not change the allowed values.
12691279
nullable: true
12701280
xml:
12711281
name: User
@@ -1859,6 +1869,7 @@ components:
18591869
- $ref: '#/components/schemas/banana'
18601870
fruitReq:
18611871
oneOf:
1872+
- type: 'null'
18621873
- $ref: '#/components/schemas/appleReq'
18631874
- $ref: '#/components/schemas/bananaReq'
18641875
appleReq:

samples/client/petstore/python-experimental/petstore_api/model_utils.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,7 @@ def get_oneof_instance(self, model_args, constant_args):
14191419
and path to item.
14201420
14211421
Returns
1422-
oneof_instance (instance/None)
1422+
oneof_instance (instance)
14231423
"""
14241424
if len(self._composed_schemas['oneOf']) == 0:
14251425
return None
@@ -1428,6 +1428,13 @@ def get_oneof_instance(self, model_args, constant_args):
14281428
# Iterate over each oneOf schema and determine if the input data
14291429
# matches the oneOf schemas.
14301430
for oneof_class in self._composed_schemas['oneOf']:
1431+
# The composed oneOf schema allows the 'null' type and the input data
1432+
# is the null value. This is a OAS >= 3.1 feature.
1433+
if oneof_class is none_type:
1434+
# skip none_types because we are deserializing dict data.
1435+
# none_type deserialization is handled in the __new__ method
1436+
continue
1437+
14311438
# transform js keys from input data to python keys in fixed_model_args
14321439
fixed_model_args = change_keys_js_to_python(
14331440
model_args, oneof_class)
@@ -1473,9 +1480,11 @@ def get_anyof_instances(self, model_args, constant_args):
14731480
Args:
14741481
self: the class we are handling
14751482
model_args (dict): var_name to var_value
1476-
used to make instances
1483+
The input data, e.g. the payload that must match at least one
1484+
anyOf child schema in the OpenAPI document.
14771485
constant_args (dict): var_name to var_value
1478-
used to make instances
1486+
args that every model requires, including configuration, server
1487+
and path to item.
14791488
14801489
Returns
14811490
anyof_instances (list)
@@ -1485,6 +1494,13 @@ def get_anyof_instances(self, model_args, constant_args):
14851494
return anyof_instances
14861495

14871496
for anyof_class in self._composed_schemas['anyOf']:
1497+
# The composed oneOf schema allows the 'null' type and the input data
1498+
# is the null value. This is a OAS >= 3.1 feature.
1499+
if anyof_class is none_type:
1500+
# skip none_types because we are deserializing dict data.
1501+
# none_type deserialization is handled in the __new__ method
1502+
continue
1503+
14881504
# transform js keys to python keys in fixed_model_args
14891505
fixed_model_args = change_keys_js_to_python(model_args, anyof_class)
14901506

samples/openapi3/client/petstore/go-experimental/go-petstore/docs/User.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,16 @@ SetArbitraryTypeValue sets ArbitraryTypeValue field to given value.
321321

322322
HasArbitraryTypeValue returns a boolean if a field has been set.
323323

324+
### SetArbitraryTypeValueNil
325+
326+
`func (o *User) SetArbitraryTypeValueNil(b bool)`
327+
328+
SetArbitraryTypeValueNil sets the value for ArbitraryTypeValue to be an explicit nil
329+
330+
### UnsetArbitraryTypeValue
331+
`func (o *User) UnsetArbitraryTypeValue()`
332+
333+
UnsetArbitraryTypeValue ensures that no value is present for ArbitraryTypeValue, not even an explicit nil
324334
### GetArbitraryNullableTypeValue
325335

326336
`func (o *User) GetArbitraryNullableTypeValue() interface{}`

samples/openapi3/client/petstore/go-experimental/go-petstore/model_user.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type User struct {
2929
// test code generation for nullable objects. Value must be a map of strings to values or the 'null' value.
3030
ArbitraryNullableObject map[string]interface{} `json:"arbitraryNullableObject,omitempty"`
3131
// test code generation for any type Value can be any type - string, number, boolean, array or object.
32-
ArbitraryTypeValue *interface{} `json:"arbitraryTypeValue,omitempty"`
32+
ArbitraryTypeValue interface{} `json:"arbitraryTypeValue,omitempty"`
3333
// test code generation for any type Value can be any type - string, number, boolean, array, object or the 'null' value.
3434
ArbitraryNullableTypeValue interface{} `json:"arbitraryNullableTypeValue,omitempty"`
3535
}
@@ -372,22 +372,23 @@ func (o *User) SetArbitraryNullableObject(v map[string]interface{}) {
372372
o.ArbitraryNullableObject = v
373373
}
374374

375-
// GetArbitraryTypeValue returns the ArbitraryTypeValue field value if set, zero value otherwise.
375+
// GetArbitraryTypeValue returns the ArbitraryTypeValue field value if set, zero value otherwise (both if not set or set to explicit null).
376376
func (o *User) GetArbitraryTypeValue() interface{} {
377-
if o == nil || o.ArbitraryTypeValue == nil {
377+
if o == nil {
378378
var ret interface{}
379379
return ret
380380
}
381-
return *o.ArbitraryTypeValue
381+
return o.ArbitraryTypeValue
382382
}
383383

384384
// GetArbitraryTypeValueOk returns a tuple with the ArbitraryTypeValue field value if set, nil otherwise
385385
// and a boolean to check if the value has been set.
386+
// NOTE: If the value is an explicit nil, `nil, true` will be returned
386387
func (o *User) GetArbitraryTypeValueOk() (*interface{}, bool) {
387388
if o == nil || o.ArbitraryTypeValue == nil {
388389
return nil, false
389390
}
390-
return o.ArbitraryTypeValue, true
391+
return &o.ArbitraryTypeValue, true
391392
}
392393

393394
// HasArbitraryTypeValue returns a boolean if a field has been set.
@@ -401,7 +402,7 @@ func (o *User) HasArbitraryTypeValue() bool {
401402

402403
// SetArbitraryTypeValue gets a reference to the given interface{} and assigns it to the ArbitraryTypeValue field.
403404
func (o *User) SetArbitraryTypeValue(v interface{}) {
404-
o.ArbitraryTypeValue = &v
405+
o.ArbitraryTypeValue = v
405406
}
406407

407408
// GetArbitraryNullableTypeValue returns the ArbitraryNullableTypeValue field value if set, zero value otherwise (both if not set or set to explicit null).

samples/openapi3/client/petstore/python-experimental/docs/User.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ Name | Type | Description | Notes
1111
**password** | **str** | | [optional]
1212
**phone** | **str** | | [optional]
1313
**user_status** | **int** | User Status | [optional]
14-
**arbitrary_object** | **bool, date, datetime, dict, float, int, list, str** | test code generation for objects Value must be a map of strings to values. It cannot be the &#39;null&#39; value. | [optional]
15-
**arbitrary_nullable_object** | **bool, date, datetime, dict, float, int, list, str, none_type** | test code generation for nullable objects. Value must be a map of strings to values or the &#39;null&#39; value. | [optional]
16-
**arbitrary_type_value** | **bool, date, datetime, dict, float, int, list, str, none_type** | test code generation for any type Value can be any type - string, number, boolean, array or object. | [optional]
17-
**arbitrary_nullable_type_value** | **bool, date, datetime, dict, float, int, list, str, none_type** | test code generation for any type Value can be any type - string, number, boolean, array, object or the &#39;null&#39; value. | [optional]
14+
**object_with_no_declared_props** | **bool, date, datetime, dict, float, int, list, str** | test code generation for objects Value must be a map of strings to values. It cannot be the &#39;null&#39; value. | [optional]
15+
**object_with_no_declared_props_nullable** | **bool, date, datetime, dict, float, int, list, str, none_type** | test code generation for nullable objects. Value must be a map of strings to values or the &#39;null&#39; value. | [optional]
16+
**any_type_prop** | **bool, date, datetime, dict, float, int, list, str, none_type** | test code generation for any type Here the &#39;type&#39; attribute is not specified, which means the value can be anything, including the null value, string, number, boolean, array or object. See https://github.com/OAI/OpenAPI-Specification/issues/1389 | [optional]
17+
**any_type_prop_nullable** | **bool, date, datetime, dict, float, int, list, str, none_type** | test code generation for any type Here the &#39;type&#39; attribute is not specified, which means the value can be anything, including the null value, string, number, boolean, array or object. The &#39;nullable&#39; attribute does not change the allowed values. | [optional]
1818

1919
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
2020

samples/openapi3/client/petstore/python-experimental/petstore_api/model_utils.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,7 @@ def get_oneof_instance(self, model_args, constant_args):
14191419
and path to item.
14201420
14211421
Returns
1422-
oneof_instance (instance/None)
1422+
oneof_instance (instance)
14231423
"""
14241424
if len(self._composed_schemas['oneOf']) == 0:
14251425
return None
@@ -1428,6 +1428,13 @@ def get_oneof_instance(self, model_args, constant_args):
14281428
# Iterate over each oneOf schema and determine if the input data
14291429
# matches the oneOf schemas.
14301430
for oneof_class in self._composed_schemas['oneOf']:
1431+
# The composed oneOf schema allows the 'null' type and the input data
1432+
# is the null value. This is a OAS >= 3.1 feature.
1433+
if oneof_class is none_type:
1434+
# skip none_types because we are deserializing dict data.
1435+
# none_type deserialization is handled in the __new__ method
1436+
continue
1437+
14311438
# transform js keys from input data to python keys in fixed_model_args
14321439
fixed_model_args = change_keys_js_to_python(
14331440
model_args, oneof_class)
@@ -1473,9 +1480,11 @@ def get_anyof_instances(self, model_args, constant_args):
14731480
Args:
14741481
self: the class we are handling
14751482
model_args (dict): var_name to var_value
1476-
used to make instances
1483+
The input data, e.g. the payload that must match at least one
1484+
anyOf child schema in the OpenAPI document.
14771485
constant_args (dict): var_name to var_value
1478-
used to make instances
1486+
args that every model requires, including configuration, server
1487+
and path to item.
14791488
14801489
Returns
14811490
anyof_instances (list)
@@ -1485,6 +1494,13 @@ def get_anyof_instances(self, model_args, constant_args):
14851494
return anyof_instances
14861495

14871496
for anyof_class in self._composed_schemas['anyOf']:
1497+
# The composed oneOf schema allows the 'null' type and the input data
1498+
# is the null value. This is a OAS >= 3.1 feature.
1499+
if anyof_class is none_type:
1500+
# skip none_types because we are deserializing dict data.
1501+
# none_type deserialization is handled in the __new__ method
1502+
continue
1503+
14881504
# transform js keys to python keys in fixed_model_args
14891505
fixed_model_args = change_keys_js_to_python(model_args, anyof_class)
14901506

samples/openapi3/client/petstore/python-experimental/petstore_api/models/fruit_req.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,5 +218,6 @@ def _composed_schemas():
218218
'oneOf': [
219219
apple_req.AppleReq,
220220
banana_req.BananaReq,
221+
none_type,
221222
],
222223
}

0 commit comments

Comments
 (0)