Adrià Vilanova MartÃnez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame^] | 1 | # Copyright 2016 Google Inc. All Rights Reserved. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | """Endpoints-specific implementation of ProtoRPC's ProtoJson class.""" |
| 16 | from __future__ import absolute_import |
| 17 | |
| 18 | import base64 |
| 19 | |
| 20 | from protorpc import protojson |
| 21 | |
| 22 | from . import messages |
| 23 | |
| 24 | # pylint: disable=g-bad-name |
| 25 | |
| 26 | |
| 27 | __all__ = ['EndpointsProtoJson'] |
| 28 | |
| 29 | |
| 30 | class EndpointsProtoJson(protojson.ProtoJson): |
| 31 | """Endpoints-specific implementation of ProtoRPC's ProtoJson class. |
| 32 | |
| 33 | We need to adjust the way some types of data are encoded to ensure they're |
| 34 | consistent with the existing API pipeline. This class adjusts the JSON |
| 35 | encoding as needed. |
| 36 | |
| 37 | This may be used in a multithreaded environment, so take care to ensure |
| 38 | that this class (and its parent, protojson.ProtoJson) remain thread-safe. |
| 39 | """ |
| 40 | |
| 41 | def encode_field(self, field, value): |
| 42 | """Encode a python field value to a JSON value. |
| 43 | |
| 44 | Args: |
| 45 | field: A ProtoRPC field instance. |
| 46 | value: A python value supported by field. |
| 47 | |
| 48 | Returns: |
| 49 | A JSON serializable value appropriate for field. |
| 50 | """ |
| 51 | # Override the handling of 64-bit integers, so they're always encoded |
| 52 | # as strings. |
| 53 | if (isinstance(field, messages.IntegerField) and |
| 54 | field.variant in (messages.Variant.INT64, |
| 55 | messages.Variant.UINT64, |
| 56 | messages.Variant.SINT64)): |
| 57 | if value not in (None, [], ()): |
| 58 | # Convert and replace the value. |
| 59 | if isinstance(value, list): |
| 60 | value = [str(subvalue) for subvalue in value] |
| 61 | else: |
| 62 | value = str(value) |
| 63 | return value |
| 64 | |
| 65 | return super(EndpointsProtoJson, self).encode_field(field, value) |
| 66 | |
| 67 | @staticmethod |
| 68 | def __pad_value(value, pad_len_multiple, pad_char): |
| 69 | """Add padding characters to the value if needed. |
| 70 | |
| 71 | Args: |
| 72 | value: The string value to be padded. |
| 73 | pad_len_multiple: Pad the result so its length is a multiple |
| 74 | of pad_len_multiple. |
| 75 | pad_char: The character to use for padding. |
| 76 | |
| 77 | Returns: |
| 78 | The string value with padding characters added. |
| 79 | """ |
| 80 | assert pad_len_multiple > 0 |
| 81 | assert len(pad_char) == 1 |
| 82 | padding_length = (pad_len_multiple - |
| 83 | (len(value) % pad_len_multiple)) % pad_len_multiple |
| 84 | return value + pad_char * padding_length |
| 85 | |
| 86 | def decode_field(self, field, value): |
| 87 | """Decode a JSON value to a python value. |
| 88 | |
| 89 | Args: |
| 90 | field: A ProtoRPC field instance. |
| 91 | value: A serialized JSON value. |
| 92 | |
| 93 | Returns: |
| 94 | A Python value compatible with field. |
| 95 | """ |
| 96 | # Override BytesField handling. Client libraries typically use a url-safe |
| 97 | # encoding. b64decode doesn't handle these gracefully. urlsafe_b64decode |
| 98 | # handles both cases safely. Also add padding if the padding is incorrect. |
| 99 | if isinstance(field, messages.BytesField): |
| 100 | try: |
| 101 | # Need to call str(value) because ProtoRPC likes to pass values |
| 102 | # as unicode, and urlsafe_b64decode can only handle bytes. |
| 103 | padded_value = self.__pad_value(str(value), 4, '=') |
| 104 | return base64.urlsafe_b64decode(padded_value) |
| 105 | except (TypeError, UnicodeEncodeError) as err: |
| 106 | raise messages.DecodeError('Base64 decoding error: %s' % err) |
| 107 | |
| 108 | return super(EndpointsProtoJson, self).decode_field(field, value) |