Skip to content
This repository was archived by the owner on Dec 25, 2024. It is now read-only.

Commit 03c7ba7

Browse files
committed
Adds test_query_content_json_serialization
1 parent bbd73a7 commit 03c7ba7

File tree

4 files changed

+134
-60
lines changed

4 files changed

+134
-60
lines changed

modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class RequestField(RequestFieldBase):
4949

5050

5151
class JSONEncoder(json.JSONEncoder):
52+
compact_separators = (',', ':')
53+
5254
def default(self, obj):
5355
if isinstance(obj, str):
5456
return str(obj)
@@ -320,8 +322,25 @@ class StyleSimpleSerializer(ParameterSerializerBase):
320322
)
321323

322324

325+
class JSONDetector:
326+
"""
327+
Works for:
328+
application/json
329+
application/json; charset=UTF-8
330+
application/json-patch+json
331+
application/geo+json
332+
"""
333+
__json_content_type_pattern = re.compile("application/[^+]*[+]?(json);?.*")
334+
335+
@classmethod
336+
def _content_type_is_json(cls, content_type: str) -> bool:
337+
if cls.__json_content_type_pattern.match(content_type):
338+
return True
339+
return False
340+
341+
323342
@dataclass
324-
class ParameterBase:
343+
class ParameterBase(JSONDetector):
325344
name: str
326345
in_type: ParameterInType
327346
required: bool
@@ -348,7 +367,6 @@ class ParameterBase:
348367
}
349368
__disallowed_header_names = {'Accept', 'Content-Type', 'Authorization'}
350369
_json_encoder = JSONEncoder()
351-
_json_content_type = 'application/json'
352370

353371
@classmethod
354372
def __verify_style_to_in_type(cls, style: typing.Optional[ParameterStyle], in_type: ParameterInType):
@@ -395,8 +413,11 @@ class ParameterBase:
395413

396414
def _serialize_json(
397415
self,
398-
in_data: typing.Union[None, int, float, str, bool, dict, list]
416+
in_data: typing.Union[None, int, float, str, bool, dict, list],
417+
eliminate_whitespace: bool = False
399418
) -> str:
419+
if eliminate_whitespace:
420+
return json.dumps(in_data, separators=self._json_encoder.compact_separators)
400421
return json.dumps(in_data)
401422

402423

@@ -491,7 +512,7 @@ class PathParameter(ParameterBase, StyleSimpleSerializer):
491512
for content_type, schema in self.content.items():
492513
cast_in_data = schema(in_data)
493514
cast_in_data = self._json_encoder.default(cast_in_data)
494-
if content_type == self._json_content_type:
515+
if self._content_type_is_json(content_type):
495516
value = self._serialize_json(cast_in_data)
496517
return self._to_dict(self.name, value)
497518
raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
@@ -509,7 +530,7 @@ class QueryParameter(ParameterBase, StyleFormSerializer):
509530
schema: typing.Optional[typing.Type[Schema]] = None,
510531
content: typing.Optional[typing.Dict[str, typing.Type[Schema]]] = None
511532
):
512-
used_style = ParameterStyle.FORM if style is None and content is None and schema else style
533+
used_style = ParameterStyle.FORM if style is None else style
513534
used_explode = self._get_default_explode(used_style) if explode is None else explode
514535

515536
super().__init__(
@@ -572,8 +593,6 @@ class QueryParameter(ParameterBase, StyleFormSerializer):
572593
return self._to_dict(self.name, value)
573594

574595
def get_prefix_separator_iterator(self) -> typing.Optional[PrefixSeparatorIterator]:
575-
if not self.schema:
576-
return None
577596
if self.style is ParameterStyle.FORM:
578597
return PrefixSeparatorIterator('?', '&')
579598
elif self.style is ParameterStyle.SPACE_DELIMITED:
@@ -612,12 +631,17 @@ class QueryParameter(ParameterBase, StyleFormSerializer):
612631
elif self.style is ParameterStyle.PIPE_DELIMITED:
613632
return self.__serialize_pipe_delimited(cast_in_data, prefix_separator_iterator)
614633
# self.content will be length one
634+
if prefix_separator_iterator is None:
635+
prefix_separator_iterator = self.get_prefix_separator_iterator()
615636
for content_type, schema in self.content.items():
616637
cast_in_data = schema(in_data)
617638
cast_in_data = self._json_encoder.default(cast_in_data)
618-
if content_type == self._json_content_type:
619-
value = self._serialize_json(cast_in_data)
620-
return self._to_dict(self.name, value)
639+
if self._content_type_is_json(content_type):
640+
value = self._serialize_json(cast_in_data, eliminate_whitespace=True)
641+
return self._to_dict(
642+
self.name,
643+
next(prefix_separator_iterator) + self.name + '=' + quote(value)
644+
)
621645
raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
622646

623647

@@ -676,7 +700,7 @@ class CookieParameter(ParameterBase, StyleFormSerializer):
676700
for content_type, schema in self.content.items():
677701
cast_in_data = schema(in_data)
678702
cast_in_data = self._json_encoder.default(cast_in_data)
679-
if content_type == self._json_content_type:
703+
if self._content_type_is_json(content_type):
680704
value = self._serialize_json(cast_in_data)
681705
return self._to_dict(self.name, value)
682706
raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
@@ -733,7 +757,7 @@ class HeaderParameter(ParameterBase, StyleSimpleSerializer):
733757
for content_type, schema in self.content.items():
734758
cast_in_data = schema(in_data)
735759
cast_in_data = self._json_encoder.default(cast_in_data)
736-
if content_type == self._json_content_type:
760+
if self._content_type_is_json(content_type):
737761
value = self._serialize_json(cast_in_data)
738762
return self.__to_headers(((self.name, value),))
739763
raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
@@ -796,23 +820,6 @@ class ApiResponseWithoutDeserialization(ApiResponse):
796820
headers: typing.Union[Unset, typing.List[HeaderParameter]] = unset
797821

798822

799-
class JSONDetector:
800-
"""
801-
Works for:
802-
application/json
803-
application/json; charset=UTF-8
804-
application/json-patch+json
805-
application/geo+json
806-
"""
807-
__json_content_type_pattern = re.compile("application/[^+]*[+]?(json);?.*")
808-
809-
@classmethod
810-
def _content_type_is_json(cls, content_type: str) -> bool:
811-
if cls.__json_content_type_pattern.match(content_type):
812-
return True
813-
return False
814-
815-
816823
class OpenApiResponse(JSONDetector):
817824
__filename_content_disposition_pattern = re.compile('filename="(.+?)"')
818825

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.0
1+
unset

samples/openapi3/client/petstore/python/petstore_api/api_client.py

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ def __eq__(self, other):
5353

5454

5555
class JSONEncoder(json.JSONEncoder):
56+
compact_separators = (',', ':')
57+
5658
def default(self, obj):
5759
if isinstance(obj, str):
5860
return str(obj)
@@ -324,8 +326,25 @@ def _serialize_simple(
324326
)
325327

326328

329+
class JSONDetector:
330+
"""
331+
Works for:
332+
application/json
333+
application/json; charset=UTF-8
334+
application/json-patch+json
335+
application/geo+json
336+
"""
337+
__json_content_type_pattern = re.compile("application/[^+]*[+]?(json);?.*")
338+
339+
@classmethod
340+
def _content_type_is_json(cls, content_type: str) -> bool:
341+
if cls.__json_content_type_pattern.match(content_type):
342+
return True
343+
return False
344+
345+
327346
@dataclass
328-
class ParameterBase:
347+
class ParameterBase(JSONDetector):
329348
name: str
330349
in_type: ParameterInType
331350
required: bool
@@ -352,7 +371,6 @@ class ParameterBase:
352371
}
353372
__disallowed_header_names = {'Accept', 'Content-Type', 'Authorization'}
354373
_json_encoder = JSONEncoder()
355-
_json_content_type = 'application/json'
356374

357375
@classmethod
358376
def __verify_style_to_in_type(cls, style: typing.Optional[ParameterStyle], in_type: ParameterInType):
@@ -399,8 +417,11 @@ def __init__(
399417

400418
def _serialize_json(
401419
self,
402-
in_data: typing.Union[None, int, float, str, bool, dict, list]
420+
in_data: typing.Union[None, int, float, str, bool, dict, list],
421+
eliminate_whitespace: bool = False
403422
) -> str:
423+
if eliminate_whitespace:
424+
return json.dumps(in_data, separators=self._json_encoder.compact_separators)
404425
return json.dumps(in_data)
405426

406427

@@ -495,7 +516,7 @@ def serialize(
495516
for content_type, schema in self.content.items():
496517
cast_in_data = schema(in_data)
497518
cast_in_data = self._json_encoder.default(cast_in_data)
498-
if content_type == self._json_content_type:
519+
if self._content_type_is_json(content_type):
499520
value = self._serialize_json(cast_in_data)
500521
return self._to_dict(self.name, value)
501522
raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
@@ -513,7 +534,7 @@ def __init__(
513534
schema: typing.Optional[typing.Type[Schema]] = None,
514535
content: typing.Optional[typing.Dict[str, typing.Type[Schema]]] = None
515536
):
516-
used_style = ParameterStyle.FORM if style is None and content is None and schema else style
537+
used_style = ParameterStyle.FORM if style is None else style
517538
used_explode = self._get_default_explode(used_style) if explode is None else explode
518539

519540
super().__init__(
@@ -576,8 +597,6 @@ def __serialize_form(
576597
return self._to_dict(self.name, value)
577598

578599
def get_prefix_separator_iterator(self) -> typing.Optional[PrefixSeparatorIterator]:
579-
if not self.schema:
580-
return None
581600
if self.style is ParameterStyle.FORM:
582601
return PrefixSeparatorIterator('?', '&')
583602
elif self.style is ParameterStyle.SPACE_DELIMITED:
@@ -616,12 +635,17 @@ def serialize(
616635
elif self.style is ParameterStyle.PIPE_DELIMITED:
617636
return self.__serialize_pipe_delimited(cast_in_data, prefix_separator_iterator)
618637
# self.content will be length one
638+
if prefix_separator_iterator is None:
639+
prefix_separator_iterator = self.get_prefix_separator_iterator()
619640
for content_type, schema in self.content.items():
620641
cast_in_data = schema(in_data)
621642
cast_in_data = self._json_encoder.default(cast_in_data)
622-
if content_type == self._json_content_type:
623-
value = self._serialize_json(cast_in_data)
624-
return self._to_dict(self.name, value)
643+
if self._content_type_is_json(content_type):
644+
value = self._serialize_json(cast_in_data, eliminate_whitespace=True)
645+
return self._to_dict(
646+
self.name,
647+
next(prefix_separator_iterator) + self.name + '=' + quote(value)
648+
)
625649
raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
626650

627651

@@ -680,7 +704,7 @@ def serialize(
680704
for content_type, schema in self.content.items():
681705
cast_in_data = schema(in_data)
682706
cast_in_data = self._json_encoder.default(cast_in_data)
683-
if content_type == self._json_content_type:
707+
if self._content_type_is_json(content_type):
684708
value = self._serialize_json(cast_in_data)
685709
return self._to_dict(self.name, value)
686710
raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
@@ -737,7 +761,7 @@ def serialize(
737761
for content_type, schema in self.content.items():
738762
cast_in_data = schema(in_data)
739763
cast_in_data = self._json_encoder.default(cast_in_data)
740-
if content_type == self._json_content_type:
764+
if self._content_type_is_json(content_type):
741765
value = self._serialize_json(cast_in_data)
742766
return self.__to_headers(((self.name, value),))
743767
raise NotImplementedError('Serialization of {} has not yet been implemented'.format(content_type))
@@ -800,23 +824,6 @@ class ApiResponseWithoutDeserialization(ApiResponse):
800824
headers: typing.Union[Unset, typing.List[HeaderParameter]] = unset
801825

802826

803-
class JSONDetector:
804-
"""
805-
Works for:
806-
application/json
807-
application/json; charset=UTF-8
808-
application/json-patch+json
809-
application/geo+json
810-
"""
811-
__json_content_type_pattern = re.compile("application/[^+]*[+]?(json);?.*")
812-
813-
@classmethod
814-
def _content_type_is_json(cls, content_type: str) -> bool:
815-
if cls.__json_content_type_pattern.match(content_type):
816-
return True
817-
return False
818-
819-
820827
class OpenApiResponse(JSONDetector):
821828
__filename_content_disposition_pattern = re.compile('filename="(.+?)"')
822829

samples/openapi3/client/petstore/python/tests_manual/test_parameters.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,7 @@ def test_checks_content_lengths(self):
930930
content={'application/json': schemas.AnyTypeSchema}
931931
)
932932

933-
def test_content_json_serialization(self):
933+
def test_header_content_json_serialization(self):
934934
name = 'color'
935935
test_cases = (
936936
ParamTestCase(
@@ -990,6 +990,66 @@ def test_content_json_serialization(self):
990990
serialization = parameter.serialize(test_case.payload)
991991
self.assertEqual(serialization, test_case.expected_serialization)
992992

993+
def test_query_content_json_serialization(self):
994+
name = 'color'
995+
test_cases = (
996+
ParamTestCase(
997+
None,
998+
{'color': '?color=null'}
999+
),
1000+
ParamTestCase(
1001+
1,
1002+
{'color': '?color=1'}
1003+
),
1004+
ParamTestCase(
1005+
3.14,
1006+
{'color': '?color=3.14'}
1007+
),
1008+
ParamTestCase(
1009+
'blue',
1010+
{'color': '?color=%22blue%22'}
1011+
),
1012+
ParamTestCase(
1013+
'hello world',
1014+
{'color': '?color=%22hello%20world%22'}
1015+
),
1016+
ParamTestCase(
1017+
'',
1018+
{'color': '?color=%22%22'}
1019+
),
1020+
ParamTestCase(
1021+
True,
1022+
{'color': '?color=true'}
1023+
),
1024+
ParamTestCase(
1025+
False,
1026+
{'color': '?color=false'}
1027+
),
1028+
ParamTestCase(
1029+
[],
1030+
{'color': '?color=%5B%5D'}
1031+
),
1032+
ParamTestCase(
1033+
['blue', 'black', 'brown'],
1034+
{'color': '?color=%5B%22blue%22%2C%22black%22%2C%22brown%22%5D'}
1035+
),
1036+
ParamTestCase(
1037+
{},
1038+
{'color': '?color=%7B%7D'}
1039+
),
1040+
ParamTestCase(
1041+
dict(R=100, G=200, B=150),
1042+
{'color': '?color=%7B%22R%22%3A100%2C%22G%22%3A200%2C%22B%22%3A150%7D'}
1043+
),
1044+
)
1045+
for test_case in test_cases:
1046+
parameter = api_client.QueryParameter(
1047+
name=name,
1048+
content={'application/json': schemas.AnyTypeSchema}
1049+
)
1050+
serialization = parameter.serialize(test_case.payload)
1051+
self.assertEqual(serialization, test_case.expected_serialization)
1052+
9931053
def test_throws_error_for_unimplemented_serialization(self):
9941054
with self.assertRaises(NotImplementedError):
9951055
parameter = api_client.HeaderParameter(

0 commit comments

Comments
 (0)