diff --git a/stone/backends/obj_c_helpers.py b/stone/backends/obj_c_helpers.py index 50440304..217a55b5 100644 --- a/stone/backends/obj_c_helpers.py +++ b/stone/backends/obj_c_helpers.py @@ -151,6 +151,9 @@ 'id', 'delete', 'hash', + 'boolvalue', + 'floatvalue', + 'intvalue', } _reserved_prefixes = { diff --git a/stone/backends/swift.py b/stone/backends/swift.py index d97a621d..0f4c5175 100644 --- a/stone/backends/swift.py +++ b/stone/backends/swift.py @@ -11,6 +11,14 @@ mapped_list_info, ) +from stone.backends.obj_c_helpers import ( + fmt_class_prefix as legacy_fmt_class_prefix, + fmt_class_caps, + fmt_var as fmt_legacy_objc_var, + fmt_type as fmt_legacy_objc_type, + fmt_camel_upper as fmt_legacy_camel_upper, +) + from stone.ir import ( Boolean, Bytes, @@ -30,6 +38,8 @@ is_timestamp_type, is_union_type, is_user_defined_type, + is_numeric_type, + is_boolean_type, unwrap_nullable, is_nullable_type, ) @@ -172,6 +182,9 @@ def _objc_no_defualts_func_args(self, data_type, args_data=None): return self._func_args(args) + # swift initializer args for objc types + # 'id: id, path: path, isDeleted: isDeleted.boolValue, + # 'propertyGroups: propertyGroups.compactMap { $0.swift }' def _objc_init_args_to_swift(self, data_type, args_data=None, include_defaults=True): args = [] for field in data_type.all_fields: @@ -206,9 +219,16 @@ def _objc_init_args_to_swift(self, data_type, args_data=None, include_defaults=T value = '{}{}'.format(value, suffix) elif is_map_type(field_data_type): + list_nsnumber_type = _nsnumber_type_table.get( + field_data_type.value_data_type.__class__) + if is_user_defined_type(field_data_type.value_data_type): value = '{}{}.mapValues {{ $0.swift }}'.format(name, '?' if nullable else '') + elif list_nsnumber_type: + value = '{}{}.mapValues {{ $0{} }}'.format(name, + '?' if nullable else '', + list_nsnumber_type) elif is_user_defined_type(field_data_type): value = '{}{}.{}'.format(name, '?' if nullable else '', @@ -222,9 +242,195 @@ def _objc_init_args_to_swift(self, data_type, args_data=None, include_defaults=T extra_args = [tuple(type_data[:-1]) for type_data in type_data_list] for name, _, _ in extra_args: args.append((name, name)) + return self._func_args(args) + # Init args for mapping a legacy objc object to an objc object + # 'id: object.id_, path: object.path, isDeleted: object.isDeleted, propertyGroups: + # object.propertyGroups.compactMap { mapDBFILEPROPERTIESPropertyGroupToDBX(object: $0) }' + def _shim_legacy_objc_init_args_to_objc(self, data_type, source_var_name, args_data=None): + args = [] + for field in data_type.all_fields: + arg = self._shim_legacy_objc_init_arg_to_objc(field, data_type, source_var_name) + args.append(arg) + + if args_data is not None: + _, type_data_list = tuple(args_data) + extra_args = [tuple(type_data[:-1]) for type_data in type_data_list] + for name, _, _ in extra_args: + args.append((name, name)) return self._func_args(args) + # Union type check when mapping a legacy objc object to an objc object + # 'isRouteAccessDenied' or 'isPaperDocument' + def _fmt_legacy_objc_union_case_check(self, var, data_type): + legacy_objc_name = fmt_bridged_legacy_objc_var(var, fmt_legacy_objc_type(data_type, + no_ptr=True), False) + return 'is{}'.format(fmt_legacy_camel_upper(legacy_objc_name)) + + # Init args for mapping an objc object to a legacy objc object + # 'id_: object.id, path: object.path, isDeleted: object.isDeleted, propertyGroups: + # object.propertyGroups.compactMap { mapDBXFilePropertiesPropertyGroupToDB(object: $0) }' + def _shim_objc_init_args_legacy_objc(self, data_type, args_data=None): + args = [] + for field in data_type.all_fields: + arg = self._shim_objc_init_arg_legacy_objc(field, len(args) == 0) + args.append(arg) + + if len(args) == 0: + args.append(('default', '()')) + + if args_data is not None: + _, type_data_list = tuple(args_data) + extra_args = [tuple(type_data[:-1]) for type_data in type_data_list] + for name, _, _ in extra_args: + args.append((name, name)) + + return self._func_args(args) + + # When mapping from an objc object to a legacy objc object, type mapper for the + # union associated type + # 'object.complete.compactMap { mapDBXTeamMemberAddResultToDB(object: $0) }' + # 'object.templateNotFound' + # 'mapDBXAuthInvalidAccountTypeErrorToDB(object: object.invalidAccountType)' + def _shim_objc_union_associated_type(self, field, is_first_pass=True): + # return just the value, no parameter name needed + return self._shim_objc_init_arg_legacy_objc(field, is_first_pass)[1] + + # When mapping from an objc object to a legacy objc object, type mapper for the + # a single constituent field type, with the parameter name + # ('complete', 'object.complete.compactMap { mapDBXTeamMemberAddResultToDB(object: $0) }') + def _shim_objc_init_arg_legacy_objc(self, field, is_first_pass=True): + name = fmt_var(field.name) + field_data_type, nullable = unwrap_nullable(field.data_type) + type_name_for_trimming = fmt_legacy_objc_type(field_data_type, no_ptr=True) + + legacy_objc_name = fmt_bridged_legacy_objc_var(field.name, type_name_for_trimming, + is_first_pass) + + value = 'object.{}'.format(name) + if nullable and isinstance(field_data_type, Bytes): + value = 'object.{}.flatMap {{ String(data: $0, encoding: .utf8) }}'.format(name) + elif not nullable and isinstance(field_data_type, Bytes): + value = 'String(data: object.{}, encoding: .utf8) ?? .init()'.format(name) + elif is_list_type(field_data_type): + _, prefix, suffix, list_data_type, _ = mapped_list_info(field_data_type) + + value = 'object.{}{}'.format(name, + '?' if nullable else '') + list_nsnumber_type = _nsnumber_type_table.get(list_data_type.__class__) + + if not is_user_defined_type(list_data_type) and not list_nsnumber_type: + value = 'object.{}'.format(name) + else: + value = '{}.compactMap {}'.format(value, prefix) + + if is_user_defined_type(list_data_type): + value = '{}{{ map{}ToDB(object: $0) }}'.format(value, + fmt_objc_type(list_data_type)) + else: + value = '{}{{ $0 }}'.format(value) + + value = '{}{}'.format(value, + suffix) + elif is_map_type(field_data_type): + if is_user_defined_type(field_data_type.value_data_type): + value = 'object.{}{}.mapValues {{ map{}ToDB(object: $0) }}'.format(name, + '?' if nullable else '', + fmt_objc_type(field_data_type.value_data_type)) + + elif is_user_defined_type(field_data_type): + value = 'map{}ToDB{}(object: object.{})'.format( + fmt_objc_type(field_data_type), + 'Optional' if nullable else '', + name) + return (legacy_objc_name, value) + + # When mapping from a legacy objc object to an objc object, type mapper for the + # union associated type + # 'mapDBAUTHInvalidAccountTypeErrorToDBX(object: object.invalidAccountType)' + # 'object.base64Data' + # 'object.complete.compactMap { mapDBTEAMTeamMemberAddResultToDBX(object: $0) }' + def _shim_legacy_objc_union_associated_type(self, field, parent_data_type): + # return just the value, no parameter name needed + return self._shim_legacy_objc_init_arg_to_objc(field, parent_data_type, 'object')[1] + + # When mapping from a legacy objc object to an objc object, type mapper for the + # union associated type + # 'mapDBAUTHInvalidAccountTypeErrorToDBX(object: object.invalidAccountType)' + # 'object.base64Data' + # 'object.complete.compactMap { mapDBTEAMTeamMemberAddResultToDBX(object: $0) }' + def _shim_legacy_objc_init_arg_to_objc(self, field, parent_data_type, source_var_name): + name = fmt_var(field.name) + legacy_objc_name = fmt_legacy_objc_var(field.name) + field_data_type, nullable = unwrap_nullable(field.data_type) + value = '{}.{}'.format(source_var_name, legacy_objc_name) + if isinstance(field_data_type, Bytes): + value = '{}{}.{}{}'.format(value, '?' if nullable else '', 'data(using: .utf8)', + '' if nullable else ' ?? Data()') + elif is_list_type(field_data_type): + _, prefix, suffix, list_data_type, _ = mapped_list_info(field_data_type) + + value = '{}.{}{}'.format(source_var_name, + legacy_objc_name, + '?' if nullable else '') + + if not is_user_defined_type(list_data_type): + value = '{}.{}'.format(source_var_name, legacy_objc_name) + if is_user_defined_type(list_data_type): + value = '{}.compactMap {}'.format(value, prefix) + value = '{}{{ map{}ToDBX(object: $0) }}'.format(value, + legacy_fmt_class_prefix(list_data_type)) + value = '{}{}'.format(value, suffix) + elif is_map_type(field_data_type): + if is_user_defined_type(field_data_type.value_data_type): + value = '{}.{}{}.mapValues {{ map{}ToDBX(object: $0) }}'.format( + source_var_name, + legacy_objc_name, + '?' if nullable else '', + self._fmt_class_prefix_namespace(field_data_type.value_data_type, + parent_data_type.namespace)) + + elif is_user_defined_type(field_data_type): + value = 'map{}ToDBX{}(object: {}.{})'.format( + legacy_fmt_class_prefix(field_data_type), + 'Optional' if nullable else '', + source_var_name, + legacy_objc_name) + return (name, value) + + # Initialize an objc union from a legacy objc union + # '.paperAccessDenied(paperAccessDenied.swift)' + # '.complete(complete.map { $0.swift })' + def _shim_legacy_objc_union_associated_type_init(self, field): + name = fmt_var(field.name) + field_data_type, nullable = unwrap_nullable(field.data_type) + nsnumber_type = _nsnumber_type_table.get(field_data_type.__class__) + + value = '.{name}({name})'.format(name=name) + if is_numeric_type(field_data_type) or is_boolean_type(field_data_type): + value = '.{name}({name}{})'.format(nsnumber_type, name=name) + elif is_list_type(field_data_type): + _, _, _, list_data_type, _ = mapped_list_info(field_data_type) + if is_user_defined_type(list_data_type): + value = '.{name}({name}.map {{ {}.swift }})'.format('$0', name=name) + elif is_map_type(field_data_type): + if is_user_defined_type(field_data_type.value_data_type): + value = '.{}{}.mapValues {{ map{}ToDBX(object: $0) }}'.format(name, + '?' if nullable else '', + self._fmt_class_prefix_namespace(field_data_type.value_data_type, + field_data_type.namespace)) + elif is_user_defined_type(field_data_type): + if field_data_type.parent_type and not is_union_type(field_data_type): + value = '.{name}({name}{}.subSwift)'.format('?' if nullable else '', name=name) + else: + value = '.{name}({name}{}.swift)'.format('?' if nullable else '', name=name) + return value + + # 'DBFILEPROPERTIESPropertyGroup' + def _fmt_class_prefix_namespace(self, data_type, namespace): + return 'DB{}{}'.format( + fmt_class_caps(namespace.name), fmt_class(data_type.name)) + def _objc_swift_var_name(self, data_type): parent_type = data_type.parent_type uw_parent_type, _ = unwrap_nullable(parent_type) @@ -312,3 +518,74 @@ def fmt_serial_obj(data_type): result = 'Serialization._{}'.format(result) return result if not nullable else 'NullableSerializer({})'.format(result) + +# Manual reproduction of the effects of Apple's objc to swift name translation +# see https://github.com/swiftlang/swift/blob/main/docs/CToSwiftNameTranslation-OmitNeedlessWords.md +# Adding routes to the sdk migration shim may necessitate adding more cases here +def fmt_bridged_legacy_objc_var(var, type_name, first_pass): + legacy_objc_name = fmt_legacy_objc_var(var) + + if legacy_objc_name == 'valueAnswerScores' and type_name == 'DBCONTEXTENGINEValueUnion': + legacy_objc_name = 'valueAnswer' + elif legacy_objc_name == 'valueAnswerScores' and type_name == 'DBCONTEXTENGINEScores': + legacy_objc_name = 'valueAnswer' + elif (legacy_objc_name == "replayPlan" and type_name + == "DBCASHCATALOGCONSTANTSSTONEProductPlanType"): + legacy_objc_name = "replay" + elif (legacy_objc_name == "replayPlanFreemium" and type_name + == "DBCASHCATALOGCONSTANTSSTONEProductPlanType"): + legacy_objc_name = "replayFreemium" + elif (legacy_objc_name == "replayPlanTrial" and type_name + == "DBCASHCATALOGCONSTANTSSTONEProductPlanType"): + legacy_objc_name = "replayTrial" + elif legacy_objc_name == "removeExpiry" and type_name == "DBSHARINGLinkExpiry": + legacy_objc_name = "remove" + elif legacy_objc_name == "removePassword" and type_name == "DBSHARINGLinkPassword": + legacy_objc_name = "remove" + elif legacy_objc_name == "requireRole" and type_name == "DBACCOUNTRole": + legacy_objc_name = "require" + elif legacy_objc_name == "startTime" and type_name == "DBASSISTANTTime": + legacy_objc_name = "start" + elif legacy_objc_name == "endTime" and type_name == "DBASSISTANTTime": + legacy_objc_name = "end" + elif legacy_objc_name == "resolvedWithDetails" and type_name == "DBCOMMENTS2ResolvedDetails": + legacy_objc_name = "resolvedWith" + elif legacy_objc_name == "answerScores" and type_name == "DBCONTEXTENGINEScores": + legacy_objc_name = "answer" + elif (legacy_objc_name == "ignoreReason" and type_name + == 'DBCONTEXTENGINEAnswersWorkflowAnswerRejectionReason'): + legacy_objc_name = 'ignore' + elif legacy_objc_name == "boltToken" and type_name == 'DBEKMSBoltChannelToken': + legacy_objc_name = 'bolt' + elif legacy_objc_name == "captureDate" and type_name == 'NSDate *': + legacy_objc_name = 'capture' + elif legacy_objc_name == "rangeStartToken" and type_name == 'DBFQLToken': + legacy_objc_name = 'rangeStart' + elif legacy_objc_name == "rangeEndToken" and type_name == 'DBFQLToken': + legacy_objc_name = 'rangeEnd' + elif legacy_objc_name == "operator" and type_name == 'DBFQLOperator': + legacy_objc_name = 'with' + elif legacy_objc_name == "operator" and type_name == 'DBFQLOperator': + legacy_objc_name = 'with' + elif legacy_objc_name == "startTime" and type_name == 'DBGENIETime': + legacy_objc_name = 'start' + elif legacy_objc_name == "endTime" and type_name == 'DBGENIETime': + legacy_objc_name = 'end' + elif (legacy_objc_name == "missingPermissions" and type_name + == 'NSArray'): + legacy_objc_name = 'missing' + elif (legacy_objc_name == "googlePlayDetails" and type_name + == 'DBSTOREGooglePlaySubscriptionDetails'): + legacy_objc_name = 'googlePlay' + + if first_pass: + if legacy_objc_name == 'internal': + legacy_objc_name = 'withInternal' + elif legacy_objc_name == 'public': + legacy_objc_name = 'withPublic' + elif legacy_objc_name == 'operator': + legacy_objc_name = 'with' + elif legacy_objc_name == 'extension': + legacy_objc_name = 'withExtension' + + return legacy_objc_name diff --git a/stone/backends/swift_client.py b/stone/backends/swift_client.py index 00362d69..69a30eb5 100644 --- a/stone/backends/swift_client.py +++ b/stone/backends/swift_client.py @@ -31,6 +31,10 @@ datatype_has_subtypes, ) +from stone.backends.obj_c_helpers import ( + fmt_class_prefix +) + _MYPY = False if _MYPY: import typing # noqa: F401 # pylint: disable=import-error,unused-import,useless-suppression @@ -93,6 +97,11 @@ action='store_true', help='Generate the Objective-C compatibile files.', ) +_cmdline_parser.add_argument( + '--objc-shim', + action='store_true', + help='Generate the Objective-C to Swift migration files.', +) class SwiftBackend(SwiftBaseBackend): @@ -137,14 +146,23 @@ class SwiftBackend(SwiftBaseBackend): def generate(self, api): for namespace in api.namespaces.values(): - if namespace.routes: + if self._namespace_contains_valid_routes_for_auth_type(namespace): self._generate_routes(namespace) self._generate_client(api) self._generate_request_boxes(api) - if not self.args.objc: + if not self.args.objc and not self.args.objc_shim: self._generate_reconnection_helpers(api) + # Argument cast when mapping a legacy objc route to an objc route + # ', let args = args as? DBUSERSGetAccountBatchArg' + def shim_rpc_function_argument_if_necessary(self, data_type): + if is_user_defined_type(data_type) and len(data_type.fields) > 0: + class_name = fmt_class_prefix(data_type) + return ', let args = args as? {}'.format(class_name) + else: + return '' + def _generate_client(self, api): template_globals = {} template_globals['class_name'] = self.args.class_name @@ -158,6 +176,9 @@ def _generate_client(self, api): self._write_output_in_target_folder(template.render(), 'DBX{}.swift'.format(self.args.module_name)) + + elif self.args.objc_shim: + self._generate_sdk_migration_shim(api) else: template = self._jinja_template("SwiftClient.jinja") template.globals = template_globals @@ -220,6 +241,8 @@ def _generate_routes(self, namespace): self._write_output_in_target_folder(output_from_parsed_template, 'DBX{}Routes.swift'.format(ns_class)) + elif self.args.objc_shim: + return else: template = self._jinja_template("SwiftRoutes.jinja") template.globals = template_globals @@ -258,6 +281,8 @@ def _generate_request_boxes(self, api): file_name = 'DBX{}RequestBox.swift'.format(self.args.class_name) self._write_output_in_target_folder(output, file_name) + elif self.args.objc_shim: + return else: template = self._jinja_template("SwiftRequestBox.jinja") template.globals = template_globals @@ -296,6 +321,32 @@ def _generate_reconnection_helpers(self, api): output_from_parsed_template, '{}.swift'.format(class_name) ) + def _generate_sdk_migration_shim(self, api): + template = self._jinja_template("SwiftObjcShimHelpers.jinja") + template_globals = {} + template_globals['namespaces'] = api.namespaces.values() + template_globals['route_client_args'] = self._route_client_args + template_globals['fmt_route_objc_class'] = self._fmt_route_objc_class + template_globals['fmt_func'] = fmt_func + template_globals['fmt_objc_type'] = fmt_objc_type + template_globals['objc_to_legacy_objc_mapper'] = self._shim_objc_to_legacy_objc_type_mapper + template_globals['fmt_route_name_namespace'] = fmt_route_name_namespace + template_globals['shim_rpc_function_arg'] = self.shim_rpc_function_argument_if_necessary + template_globals['route_args'] = self._route_args + template_globals['is_struct_type'] = is_struct_type + template_globals['is_union_type'] = is_union_type + template_globals['fmt_var'] = fmt_var + objc_init_key = 'shim_legacy_objc_init_args_to_objc' + template_globals[objc_init_key] = self._shim_legacy_objc_init_args_to_objc + template_globals['fmt_class_prefix'] = fmt_class_prefix + + template.globals = template_globals + + output_from_parsed_template = template.render() + + self._write_output_in_target_folder(output_from_parsed_template, + 'ShimSwiftObjcHelpers.swift') + def _background_compatible_namespace_route_pairs(self, api): namespaces = api.namespaces.values() background_compatible_routes = [] @@ -557,6 +608,23 @@ def _route_objc_result_type(self, route, args_data): result_type = '{}, {}'.format(result_type, error_type) return result_type + # Used in objc to legacy objc type RPC completion mapping, optionals only. + # 'mapDBXCameraUploadsMobileCommitCameraUploadResultToDBOptional(object: result)' + # 'mapDBXCameraUploadsMobileCommitCameraUploadErrorToDBOptional(object: routeError)' + def _shim_objc_to_legacy_objc_type_mapper(self, data_type, mapped_object_name): + if data_type.name == 'Void': + return 'nil' + elif is_list_type(data_type): + if is_user_defined_type(data_type.data_type): + list_data_type = fmt_objc_type(data_type.data_type) + return '{}?.map {{ map{}ToDBOptional(object: $0) }}'.format(mapped_object_name, + list_data_type) + else: + return '{}'.format(mapped_object_name) + else: + return 'map{}ToDBOptional(object: {})'.format(fmt_objc_type(data_type), + mapped_object_name) + def _background_compatible_routes_for_objc_requests(self, api): namespaces = api.namespaces.values() objc_class_to_route = {} diff --git a/stone/backends/swift_helpers.py b/stone/backends/swift_helpers.py index 69cd462b..ed6f7d1f 100644 --- a/stone/backends/swift_helpers.py +++ b/stone/backends/swift_helpers.py @@ -138,7 +138,6 @@ def fmt_func(name, version): name = _format_camelcase(name) return name - def fmt_type(data_type): data_type, nullable = unwrap_nullable(data_type) @@ -175,6 +174,11 @@ def fmt_objc_type(data_type, allow_nullable=True): def fmt_var(name): return _format_camelcase(name) +# Type check component when mapping an objc union to a legacy objc union +# e.g.: `asPricePlanTypePremium` +def fmt_shim_union_psuedo_cast(name): + arg = _format_camelcase(name, lower_first=False) + return 'as{}'.format(arg) def fmt_default_value(field): if is_tag_ref(field.default): @@ -267,6 +271,17 @@ def field_is_user_defined_list(field): # List[typing.Tuple[let_name: str, swift_type: str, objc_type: str]] def objc_datatype_value_type_tuples(data_type): ret = [] + for d_type in datatype_subtype_value_types(data_type): + case_let_name = fmt_var(d_type.name) + swift_type = fmt_type(d_type) + objc_type = fmt_objc_type(d_type) + ret.append((case_let_name, swift_type, objc_type)) + + return ret + +# List[typing.Tuple[let_name: str, type: DataType]] +def datatype_subtype_value_types(data_type): + ret = [] # if list type get the data type of the item if is_list_type(data_type): @@ -282,15 +297,8 @@ def objc_datatype_value_type_tuples(data_type): for subtype in all_subtypes: # subtype[0] is the tag name and subtype[1] is the subtype struct itself - struct = subtype[1] - case_let_name = fmt_var(struct.name) - swift_type = fmt_type(struct) - objc_type = fmt_objc_type(struct) - ret.append((case_let_name, swift_type, objc_type)) + ret.append((subtype[1])) return ret -def field_datatype_has_subtypes(field) -> bool: - return datatype_has_subtypes(field.data_type) - def datatype_has_subtypes(data_type) -> bool: return len(objc_datatype_value_type_tuples(data_type)) > 0 diff --git a/stone/backends/swift_rsrc/ObjCRoutes.jinja b/stone/backends/swift_rsrc/ObjCRoutes.jinja index df741744..0fd8de10 100644 --- a/stone/backends/swift_rsrc/ObjCRoutes.jinja +++ b/stone/backends/swift_rsrc/ObjCRoutes.jinja @@ -104,12 +104,29 @@ public class {{ fmt_route_objc_class(namespace, route, args_data) }}: NSObject, self.response(queue: nil, completionHandler: completionHandler) } + {% if route.attrs.get('style') == 'rpc' and ('user' in route.attrs.get('auth') or 'noauth' in route.attrs.get('auth')) and not route.deprecated %} @objc @discardableResult public func response( queue: DispatchQueue?, completionHandler: @escaping ({{ result_type }}) -> Void ) -> Self { + self.response(queue: nil, analyticsBlock: nil, completionHandler: completionHandler) + } + {% endif %} + + @objc + @discardableResult public func response( + queue: DispatchQueue? = nil, + {% if route.attrs.get('style') == 'rpc' and ('user' in route.attrs.get('auth') or 'noauth' in route.attrs.get('auth')) and not route.deprecated %} + analyticsBlock: AnalyticsBlock? = nil, + {% endif %} + completionHandler: @escaping ({{ result_type }}) -> Void + ) -> Self { + {% if route.attrs.get('style') == 'rpc' and ('user' in route.attrs.get('auth') or 'noauth' in route.attrs.get('auth')) and not route.deprecated %} + swift.response(queue: queue, analyticsBlock: analyticsBlock) { result, error in + {% else %} swift.response(queue: queue) { result, error in + {% endif %} {% if route.error_data_type.name != 'Void' %} {% set error_type = 'DBX' + fmt_class(route.error_data_type.namespace.name) + fmt_class(route.error_data_type.name) %} {% set error_call = 'routeError, callError' %} @@ -117,7 +134,11 @@ public class {{ fmt_route_objc_class(namespace, route, args_data) }}: NSObject, var callError: DBXCallError? switch error { case .routeError(let box, _, _, _): + {% if is_union_type(route.error_data_type) %} + routeError = {{ error_type }}.factory(swift: box.unboxed) + {% else %} routeError = {{ error_type }}(swift: box.unboxed) + {% endif %} callError = nil default: routeError = nil diff --git a/stone/backends/swift_rsrc/ObjcTypes.jinja b/stone/backends/swift_rsrc/ObjcTypes.jinja index 10bd3491..6a729d92 100644 --- a/stone/backends/swift_rsrc/ObjcTypes.jinja +++ b/stone/backends/swift_rsrc/ObjcTypes.jinja @@ -22,23 +22,7 @@ public class DBX{{ namespace_class_name }}{{ data_type_class_name }}: {{ 'NSObje {% for field in data_type.fields %} {{ struct_field_doc(field, ' ') }} @objc - {% if field_datatype_has_subtypes(field) %} - public var {{ fmt_var(field.name) }}: {{ fmt_objc_type(field.data_type) }} { - {% if (field_is_user_defined(field)) or (field_is_user_defined_optional(field)) %} - {% if field_is_user_defined_optional(field) %} - return {{ swift_var_name }}.{{ fmt_var(field.name) }}.flatMap { {{ fmt_objc_type(field.data_type, False) }}.wrapPreservingSubtypes(swift: $0) } - {% else %} - return {{ fmt_objc_type(field.data_type) }}.wrapPreservingSubtypes(swift: {{ swift_var_name }}.{{ fmt_var(field.name) }}) - {% endif %} - {% elif (field_is_user_defined_map(field)) or (field_is_user_defined_list(field)) %} - {{ swift_var_name }}.{{ fmt_var(field.name) }}.{{ 'mapValues' if field_is_user_defined_map(field) else 'map' }} { - return {{ fmt_objc_type(field.data_type.data_type) }}.wrapPreservingSubtypes(swift: $0) - } - {% endif %} - } - {% else %} public var {{ fmt_var(field.name) }}: {{ fmt_objc_type(field.data_type) }} { {{ objc_return_field_value_oneliner(data_type, field) }} } - {% endif %} {% endfor %} {% if data_type.fields %} @@ -52,11 +36,24 @@ public class DBX{{ namespace_class_name }}{{ data_type_class_name }}: {{ 'NSObje self.{{ swift_var_name }} = {{ swift_type }}({{ objc_init_args_to_swift(data_type) }}) {% endif %} } + {% elif data_type.parent_type.fields %} + + @objc + public override init({{ func_args(objc_init_args(data_type.parent_type)) }}) { + let swift = {{ swift_type }}({{ objc_init_args_to_swift(data_type.parent_type) }}) + self.{{ swift_var_name }} = swift + super.init(swift: swift) + } + {% elif not data_type.parent_type %} + public override init() { + self.{{ swift_var_name }} = {{ swift_type }}() + super.init() + } {% endif %} - let {{ swift_var_name }}: {{ swift_type }} + public let {{ swift_var_name }}: {{ swift_type }} - public init(swift: {{ swift_type }}) { + {{ "fileprivate" if (objc_datatype_value_type_tuples(data_type)|length > 0) else "public" }} init(swift: {{ swift_type }}) { self.{{ swift_var_name }} = swift {% if data_type.parent_type %} super.init(swift: swift) @@ -74,6 +71,7 @@ public class DBX{{ namespace_class_name }}{{ data_type_class_name }}: {{ 'NSObje return DBX{{ namespace_class_name }}{{ data_type_class_name }}(swift: swift) } } + {% else %} {% endif %} @objc @@ -85,9 +83,9 @@ public class DBX{{ namespace_class_name }}{{ data_type_class_name }}: {{ 'NSObje {% set swift_enum = namespace_class_name + '.' + fmt_class(data_type.name) %} @objc public class {{ union_class_name }}: NSObject { - let swift: {{ swift_enum }} + public let swift: {{ swift_enum }} - public init(swift: {{ swift_enum }}) { + fileprivate init(swift: {{ swift_enum }}) { self.swift = swift } diff --git a/stone/backends/swift_rsrc/SwiftObjcArgTypeMappings.jinja b/stone/backends/swift_rsrc/SwiftObjcArgTypeMappings.jinja new file mode 100644 index 00000000..dea83a7e --- /dev/null +++ b/stone/backends/swift_rsrc/SwiftObjcArgTypeMappings.jinja @@ -0,0 +1,51 @@ +/// +/// Copyright (c) 2024 Dropbox, Inc. All rights reserved. +/// +/// Auto-generated by Stone, do not modify. +/// + +import Foundation +import stone_sdk_objc +import stone_sdk_swift +import stone_sdk_swift_objc + +{% for data_type in namespace.linearize_data_types() %} +{% set dbx_data_type_class_name = fmt_objc_type(data_type) %} +{% set db_data_type_class_name = fmt_class_prefix(data_type) %} +func map{{ db_data_type_class_name }}ToDBXOptional(object: {{ db_data_type_class_name}}?) -> {{ dbx_data_type_class_name }}? { + guard let object = object else { return nil } + return map{{ db_data_type_class_name }}ToDBX(object: object) +} + +func map{{ db_data_type_class_name }}ToDBX(object: {{ db_data_type_class_name}}) -> {{ dbx_data_type_class_name }} { + {% if is_struct_type(data_type) %} + {% if datatype_subtype_value_types(data_type)|length > 0 %} + switch object { + {% for type in datatype_subtype_value_types(data_type) %} + case let object as {{ fmt_class_prefix(type) }}: + return {{ fmt_objc_type(type) }}({{ shim_legacy_objc_init_args_to_objc(type, 'object') }}) + {% endfor %} + default: + return {{ dbx_data_type_class_name }}({{ shim_legacy_objc_init_args_to_objc(data_type, 'object') }}) + } + {% else %} + return {{ dbx_data_type_class_name }}({{ shim_legacy_objc_init_args_to_objc(data_type, 'object') }}) + {% endif %} + {% elif is_union_type(data_type) %} + {% for field in data_type.all_fields %} + {% set case_class = dbx_data_type_class_name + fmt_class(field.name) %} + {% set case_var_name = fmt_var(field.name) %} + if object.{{ fmt_legacy_objc_union_case_check(field.name, data_type) }}() { + {% if not is_void_type(field.data_type) %} + let {{ case_var_name }} = {{ shim_legacy_objc_union_associated_type(field, data_type) }} + return {{ dbx_data_type_class_name }}.factory(swift: {{ shim_legacy_objc_union_associated_type_init(field) }}) + {% else %} + return {{ case_class }}() + {% endif %} + } + {% endfor %} + fatalError("codegen error") + {% endif %} +} + +{% endfor %} diff --git a/stone/backends/swift_rsrc/SwiftObjcShimHelpers.jinja b/stone/backends/swift_rsrc/SwiftObjcShimHelpers.jinja new file mode 100644 index 00000000..093fe8d1 --- /dev/null +++ b/stone/backends/swift_rsrc/SwiftObjcShimHelpers.jinja @@ -0,0 +1,212 @@ +/// +/// Copyright (c) 2024 Dropbox, Inc. All rights reserved. +/// +/// Auto-generated by Stone, do not modify. +/// + +import Foundation +import stone_sdk_objc +import stone_sdk_swift +import stone_sdk_swift_objc + +@objc +public class SDKShimHelpers: NSObject { + // MARK: DBAPIRpcTask + + @objc + @discardableResult public static func setResponseBlockRPC(block: @escaping DBRpcResponseBlockImpl, on task: DBXRequest, with queue: OperationQueue?, analyticsBlock: AnalyticsBlock?) -> Bool { + {% for namespace in namespaces | selectattr('name', '!=', 'team') %} + {% for route in namespace.routes %} + {% if route.attrs.get('style') == 'rpc' and ('user' in route.attrs.get('auth') or 'noauth' in route.attrs.get('auth')) and not route.deprecated %} + {% set objc_request_class = fmt_route_objc_class(namespace, route, None) %} + {% set objc_result_mapper = objc_to_legacy_objc_mapper(route.result_data_type, 'result') %} + {% set objc_error_mapper = objc_to_legacy_objc_mapper(route.error_data_type, 'routeError') %} + if let task = task as? {{ objc_request_class }} { + {% if route.result_data_type.name == 'Void' and route.error_data_type.name == 'Void' %} + task.response(analyticsBlock: analyticsBlock) { networkError in + let mappedCallError = callErrorToDB(error: networkError) + let wrappedBlock = { block(networkError == nil ? DBNilObject() : nil, nil, mappedCallError) } + {% elif route.result_data_type.name == 'Void' %} + task.response(analyticsBlock: analyticsBlock) { routeError, networkError in + let mappedError = {{ objc_error_mapper }} + let mappedCallError = callErrorToDB(error: networkError) + let wrappedBlock = { block((networkError == nil && routeError == nil) ? DBNilObject() : nil, mappedError, mappedCallError) } + {% elif route.error_data_type.name == 'Void' %} + task.response(analyticsBlock: analyticsBlock) { result, networkError in + let mappedResult = {{ objc_result_mapper }} + let mappedCallError = callErrorToDB(error: networkError) + let wrappedBlock = { block(mappedResult, nil, mappedCallError) } + {% else %} + task.response(analyticsBlock: analyticsBlock) { result, routeError, networkError in + let mappedResult = {{ objc_result_mapper }} + let mappedError = {{ objc_error_mapper }} + let mappedCallError = callErrorToDB(error: networkError) + let wrappedBlock = { block(mappedResult, mappedError, mappedCallError) } + {% endif %} + Self.processCompletion(wrappedBlock, queue: queue) + } + return true + } + {% endif %} + {% endfor %} + {% endfor %} + return false + } + + private static func processCompletion(_ wrappedBlock: @escaping () -> Void, queue: OperationQueue?) { + if let queue = queue { + queue.addOperation(wrappedBlock) + } else { + wrappedBlock() + } + } + + // MARK: DBAPITransportClient + + @objc public static func rpcRequest(for route: DBRoute, args: DBSerializable, userId: String, client: DBXDropboxClient) -> DBXRequest? { + // Make sure this handles versioned routes properly + let qualifiedRouteName = route.namespace_ + "/" + route.name + {% for namespace in namespaces | selectattr('name', '!=', 'team') %} + {% for route in namespace.routes %} + {% if route.attrs.get('style') == 'rpc' and ('user' in route.attrs.get('auth') or 'noauth' in route.attrs.get('auth')) and not route.deprecated %} + {% set func_name = fmt_func(route.name, route.version) %} + {% set objc_data_type = fmt_objc_type(route.result_data_type) %} + {% set route_args_no_defaults = route_args(namespace, route, None, True, False) %} + {% set route_args = route_args(namespace, route, None, True) %} + if qualifiedRouteName == "{{ fmt_route_name_namespace(route, namespace.name) }}"{{ shim_rpc_function_arg(route.arg_data_type) }} { + {% if is_struct_type(route.arg_data_type) %} + return client.{{ fmt_var(namespace.name) }}.{{ func_name }}({{ shim_legacy_objc_init_args_to_objc(route.arg_data_type, 'args', None) }}) + {% elif is_union_type(route.arg_data_type) %} + return client.{{ fmt_var(namespace.name) }}.{{ func_name }}({{ fmt_var(route.arg_data_type.name) }}: map{{ fmt_class_prefix(route.arg_data_type) }}toDBX(object: {{ fmt_var(route.arg_data_type.name) }})) + {% else %} + return client.{{ fmt_var(namespace.name) }}.{{ func_name }}() + {% endif %} + } + {% endif %} + {% endfor %} + {% endfor %} + return nil + } +} + +private func callErrorToDB(error: DBXCallError?) -> DBRequestError? { + if let error = error?.asInternalServerError { + return DBRequestError(asInternalServerError: error.requestId, statusCode: NSNumber(value: error.code), errorContent: error.message, userMessage: nil) + } + if let error = error?.asBadInputError { + return DBRequestError(asBadInputError: error.requestId, statusCode: NSNumber(value: 400), errorContent: nil, userMessage: nil) + } + if let error = error?.asAuthError { + var localizedUserMessage: DBLocalizedUserMessage? = nil + if let lum = error.localizedUserMessage { + localizedUserMessage = DBLocalizedUserMessage(text: lum.text, locale: lum.locale) + } + let structuredAuthError: DBAUTHAuthError = { + if error.error.asInvalidAccessToken != nil { + return DBAUTHAuthError(invalidAccessToken: ()) + } else if error.error.asInvalidSelectUser != nil { + return DBAUTHAuthError(invalidSelectUser: ()) + } else if error.error.asInvalidSelectAdmin != nil { + return DBAUTHAuthError(invalidSelectAdmin: ()) + } else if error.error.asUserSuspended != nil { + return DBAUTHAuthError(userSuspended: ()) + } else if error.error.asExpiredAccessToken != nil { + return DBAUTHAuthError(expiredAccessToken: ()) + } else if let error = error.error.asMissingScope { + let scopeError = DBAUTHTokenScopeError(requiredScope: error.missingScope.requiredScope) + return DBAUTHAuthError(missingScope: scopeError) + } else if error.error.asRouteAccessDenied != nil { + return DBAUTHAuthError(routeAccessDenied: ()) + } else if error.error.asNoTeamApiAccess != nil { + return DBAUTHAuthError(noTeamApiAccess: ()) + } else if error.error.asInvalidTeamAuthHeader != nil { + return DBAUTHAuthError(invalidTeamAuthHeader: ()) + } else if error.error.asFederationAccessDenied != nil { + return DBAUTHAuthError(federationAccessDenied: ()) + } else { + return DBAUTHAuthError(other: ()) + } + }() + return DBRequestError(asAuthError: error.requestId, statusCode: NSNumber(value: 401), errorContent: error.message, userMessage: localizedUserMessage, structuredAuthError: structuredAuthError) + } + if let error = error?.asAccessError { + var localizedUserMessage: DBLocalizedUserMessage? = nil + if let lum = error.localizedUserMessage { + localizedUserMessage = DBLocalizedUserMessage(text: lum.text, locale: lum.locale) + } + let structuredAccessError: DBAUTHAccessError = { + if let error = error.error.asInvalidAccountType { + let accountTypeError: DBAUTHInvalidAccountTypeError = { + if error.invalidAccountType.asFeature != nil { + return DBAUTHInvalidAccountTypeError(feature: ()) + } else if error.invalidAccountType.asEndpoint != nil { + return DBAUTHInvalidAccountTypeError(endpoint: ()) + } else { + return DBAUTHInvalidAccountTypeError(other: ()) + } + }() + return DBAUTHAccessError(invalidAccountType: accountTypeError) + } else if let error = error.error.asPaperAccessDenied { + let paperAccessError: DBAUTHPaperAccessError = { + if error.paperAccessDenied.asNotPaperUser != nil { + return DBAUTHPaperAccessError(notPaperUser: ()) + } else if error.paperAccessDenied.asPaperDisabled != nil { + return DBAUTHPaperAccessError(paperDisabled: ()) + } else { + return DBAUTHPaperAccessError(other: ()) + } + }() + return DBAUTHAccessError(paperAccessDenied: paperAccessError) + } else if error.error.asTeamAccessDenied != nil { + return DBAUTHAccessError(teamAccessDenied: ()) + } else if let error = error.error.asNoPermission { + let noPermissionError: DBAUTHNoPermissionError = { + if let error = error.noPermission.asUnauthorizedAccountIdUsage { + let error: DBAUTHUnauthorizedAccountIdUsageError = .init(unauthorizedAccountIds: error.unauthorizedAccountIdUsage.unauthorizedAccountIds) + return DBAUTHNoPermissionError(unauthorizedAccountIdUsage: error) + } else { + return DBAUTHNoPermissionError(other: ()) + } + }() + return DBAUTHAccessError(noPermission: noPermissionError) + } else if error.error.asPpAccessDenied != nil { + return DBAUTHAccessError(ppAccessDenied: ()) + } else { + return DBAUTHAccessError(other: ()) + } + }() + return DBRequestError(asAccessError: error.requestId, statusCode: NSNumber(value: 403), errorContent: error.message, userMessage: localizedUserMessage, structuredAccessError: structuredAccessError) + } + if let error = error?.asHttpError { + return DBRequestError(asHttpError: error.requestId, statusCode: NSNumber(value: error.code), errorContent: error.message, userMessage: nil) + } + if let error = error?.asRateLimitError { + var localizedUserMessage: DBLocalizedUserMessage? = nil + if let lum = error.localizedUserMessage { + localizedUserMessage = DBLocalizedUserMessage(text: lum.text, locale: lum.locale) + } + let reason: DBAUTHRateLimitReason = { + if error.error.reason.asTooManyRequests != nil { + DBAUTHRateLimitReason(tooManyRequests: ()) + } else if error.error.reason.asTooManyWriteOperations != nil { + DBAUTHRateLimitReason(tooManyWriteOperations: ()) + } else { + DBAUTHRateLimitReason(other: ()) + } + }() + + let structuredRateLimitError: DBAUTHRateLimitError = .init(reason: reason, retryAfter: error.error.retryAfter) + + return DBRequestError(asRateLimitError: error.requestId, statusCode: NSNumber(value: 429), errorContent: error.message, userMessage: localizedUserMessage, structuredRateLimitError: structuredRateLimitError, backoff: structuredRateLimitError.retryAfter) + } + if let error = error?.asSerializationError { + return DBRequestError(asClientError: error.error) + } + if let error = error?.asReconnectionError { + return DBRequestError(asClientError: error.error) + } + if let error = error?.asClientError { + return DBRequestError(asClientError: error.error) + } + return nil +} diff --git a/stone/backends/swift_rsrc/SwiftObjcTypeMappings.jinja b/stone/backends/swift_rsrc/SwiftObjcTypeMappings.jinja new file mode 100644 index 00000000..a6aa23ac --- /dev/null +++ b/stone/backends/swift_rsrc/SwiftObjcTypeMappings.jinja @@ -0,0 +1,54 @@ +/// +/// Copyright (c) 2024 Dropbox, Inc. All rights reserved. +/// +/// Auto-generated by Stone, do not modify. +/// + +import Foundation +import stone_sdk_objc +import stone_sdk_swift +import stone_sdk_swift_objc + +{% for data_type in namespace.linearize_data_types() %} +{% set dbx_data_type_class_name = fmt_objc_type(data_type) %} +{% set db_data_type_class_name = fmt_class_prefix(data_type) %} +func map{{ dbx_data_type_class_name }}ToDBOptional(object: {{ dbx_data_type_class_name}}?) -> {{ db_data_type_class_name }}? { + guard let object = object else { return nil } + return map{{ dbx_data_type_class_name }}ToDB(object: object) +} + +func map{{ dbx_data_type_class_name }}ToDB(object: {{ dbx_data_type_class_name}}) -> {{ db_data_type_class_name }} { + {% if is_struct_type(data_type) %} + {% if datatype_subtype_value_types(data_type)|length > 0 %} + switch object { + {% for type in datatype_subtype_value_types(data_type) %} + case let object as {{ fmt_objc_type(type) }}: + return {{ fmt_class_prefix(type) }}({{ shim_objc_init_args_to_legacy_objc(type) }}) + {% endfor %} + default: + return {{ db_data_type_class_name }}({{ shim_objc_init_args_to_legacy_objc(data_type) }}) + } + {% else %} + return {{ db_data_type_class_name }}({{ shim_objc_init_args_to_legacy_objc(data_type) }}) + {% endif %} + {% elif is_union_type(data_type) %} + {% for field in data_type.all_fields %} + {% set field_var_name = fmt_var(field.name) %} + {% set field_data_type, nullable = unwrap_nullable(field.data_type) %} + {% set legacy_objc_field_name = fmt_bridged_legacy_objc_var(field.name, fmt_legacy_objc_type(field_data_type, no_ptr=True), True) %} + {% set objc_type = fmt_objc_type(field_data_type) %} + {% if not is_void_type(field.data_type) %} + if let object = object.{{ fmt_shim_union_psuedo_cast(field.name) }} { + let {{ field_var_name }} = {{ shim_objc_union_associated_type(field) }} + return {{ db_data_type_class_name }}({{ legacy_objc_field_name }}: {{ field_var_name }}) + } + {% else %} + if object.{{ fmt_shim_union_psuedo_cast(field.name) }} != nil { + return {{ db_data_type_class_name }}({{ legacy_objc_field_name }}: ()) + } + {% endif %} + {% endfor %} + fatalError("codegen error") + {% endif %} +} +{% endfor %} diff --git a/stone/backends/swift_rsrc/SwiftTypes.jinja b/stone/backends/swift_rsrc/SwiftTypes.jinja index 8e4c87e0..db270691 100644 --- a/stone/backends/swift_rsrc/SwiftTypes.jinja +++ b/stone/backends/swift_rsrc/SwiftTypes.jinja @@ -28,6 +28,8 @@ public class {{ fmt_class(namespace.name) }} { super.init({{ field_name_args(data_type.parent_type) }}) {% endif %} } + {% elif not data_type.parent_type %} + public init() { } {% endif %} {% if not data_type.parent_type %} diff --git a/stone/backends/swift_types.py b/stone/backends/swift_types.py index f07851ca..3864c892 100644 --- a/stone/backends/swift_types.py +++ b/stone/backends/swift_types.py @@ -11,6 +11,7 @@ SwiftBaseBackend, undocumented, _nsnumber_type_table, + fmt_bridged_legacy_objc_var, ) from stone.backends.swift_helpers import ( @@ -22,18 +23,21 @@ fmt_type, fmt_route_name, fmt_objc_type, + fmt_shim_union_psuedo_cast, mapped_list_info, field_is_user_defined, field_is_user_defined_optional, field_is_user_defined_map, field_is_user_defined_list, objc_datatype_value_type_tuples, - field_datatype_has_subtypes + datatype_subtype_value_types, + datatype_has_subtypes, ) from stone.ir import ( is_list_type, is_numeric_type, + is_float_type, is_string_type, is_struct_type, is_union_type, @@ -44,6 +48,11 @@ is_map_type ) +from stone.backends.obj_c_helpers import ( + fmt_type as fmt_legacy_objc_type, + fmt_class_prefix, +) + _MYPY = False if _MYPY: import typing # noqa: F401 # pylint: disable=import-error,unused-import,useless-suppression @@ -61,7 +70,7 @@ _cmdline_parser.add_argument( '--objc', action='store_true', - help='Generate the Objective-C compatibile files', + help='Generate the Objective-C compatibile files.', ) _cmdline_parser.add_argument( '-d', @@ -69,6 +78,11 @@ action='store_true', help=('Sets whether documentation is generated.'), ) +_cmdline_parser.add_argument( + '--objc-shim', + action='store_true', + help='Generate the Objective-C to Swift migration files.', +) class SwiftTypesBackend(SwiftBaseBackend): """ @@ -138,7 +152,7 @@ class SwiftTypesBackend(SwiftBaseBackend): cmdline_parser = _cmdline_parser def generate(self, api): rsrc_folder = os.path.join(os.path.dirname(__file__), 'swift_rsrc') - if not self.args.objc: + if not self.args.objc and not self.args.objc_shim: self.logger.info('Copying StoneValidators.swift to output folder') shutil.copy(os.path.join(rsrc_folder, 'StoneValidators.swift'), self.target_folder_path) @@ -185,8 +199,6 @@ def generate(self, api): template_globals['field_is_user_defined_optional'] = field_is_user_defined_optional template_globals['field_is_user_defined_list'] = field_is_user_defined_list template_globals['field_is_user_defined_map'] = field_is_user_defined_map - in_jinja_key = 'field_datatype_has_subtypes' - template_globals[in_jinja_key] = field_datatype_has_subtypes template_globals['objc_datatype_value_type_tuples'] = objc_datatype_value_type_tuples template_globals['objc_init_args_to_swift'] = self._objc_init_args_to_swift template_globals['objc_union_arg'] = self._objc_union_arg @@ -194,6 +206,9 @@ def generate(self, api): template_globals['swift_union_arg_to_objc'] = self._swift_union_arg_to_objc template_globals['union_swift_arg_guard'] = self._union_swift_arg_guard + if self.args.objc_shim: + self._add_shim_template_globals(template_globals) + swift_template_file = "SwiftTypes.jinja" swift_template = template_env.get_template(swift_template_file) swift_template.globals = template_globals @@ -202,6 +217,14 @@ def generate(self, api): objc_template = template_env.get_template(objc_template_file) objc_template.globals = template_globals + shim_template_file = "SwiftObjcTypeMappings.jinja" + shim_template = template_env.get_template(shim_template_file) + shim_template.globals = template_globals + + arg_shim_template_file = "SwiftObjcArgTypeMappings.jinja" + arg_shim_template = template_env.get_template(arg_shim_template_file) + arg_shim_template.globals = template_globals + for namespace in api.namespaces.values(): ns_class = fmt_class(namespace.name) @@ -210,6 +233,15 @@ def generate(self, api): route_schema=api.route_schema) self._write_output_in_target_folder(objc_output, 'DBX{}.swift'.format(ns_class)) + elif self.args.objc_shim: + shim_output = shim_template.render(namespace=namespace, + route_schema=api.route_schema) + self._write_output_in_target_folder(shim_output, + 'ShimTypeMappings{}.swift'.format(ns_class)) + arg_shim_output = arg_shim_template.render(namespace=namespace, + route_schema=api.route_schema) + self._write_output_in_target_folder(arg_shim_output, + 'ShimArgTypeMappings{}.swift'.format(ns_class)) else: swift_output = swift_template.render(namespace=namespace, route_schema=api.route_schema) @@ -218,6 +250,27 @@ def generate(self, api): if self.args.documentation: self._generate_jazzy_docs(api) + def _add_shim_template_globals(self, template_globals): + template_globals['fmt_class_prefix'] = fmt_class_prefix + legacy_objc_init_key = 'shim_objc_init_args_to_legacy_objc' + template_globals[legacy_objc_init_key] = self._shim_objc_init_args_legacy_objc + template_globals['shim_objc_union_associated_type'] = self._shim_objc_union_associated_type + template_globals['fmt_shim_union_psuedo_cast'] = fmt_shim_union_psuedo_cast + template_globals['unwrap_nullable'] = unwrap_nullable + template_globals['fmt_bridged_legacy_objc_var'] = fmt_bridged_legacy_objc_var + template_globals['fmt_legacy_objc_type'] = fmt_legacy_objc_type + template_globals['datatype_subtype_value_types'] = datatype_subtype_value_types + objc_init_key = 'shim_legacy_objc_init_args_to_objc' + template_globals[objc_init_key] = self._shim_legacy_objc_init_args_to_objc + legacy_associated_type_key = 'shim_legacy_objc_union_associated_type' + template_globals[legacy_associated_type_key] = self._shim_legacy_objc_union_associated_type + union_init_key = 'shim_legacy_objc_union_associated_type_init' + template_globals[union_init_key] = self._shim_legacy_objc_union_associated_type_init + union_check_case_key = 'fmt_legacy_objc_union_case_check' + template_globals[union_check_case_key] = self._fmt_legacy_objc_union_case_check + template_globals['fmt_legacy_objc_type'] = fmt_legacy_objc_type + template_globals['is_void_type'] = is_void_type + def _generate_jazzy_docs(self, api): jazzy_cfg_path = os.path.join('../Format', 'jazzy.json') with open(jazzy_cfg_path, encoding='utf-8') as jazzy_file: @@ -367,10 +420,19 @@ def _objc_return_field_value_oneliner(self, parent_type, field): if is_user_defined_type(list_data_type): objc_type = fmt_objc_type(list_data_type, False) - value = '{}{}.map {}{{ {}(swift: $0) }}'.format(value, + if is_union_type(list_data_type): + value = '{}{}.map {}{{ {}.factory(swift: $0) }}'.format(value, '?' if nullable else '', prefix, objc_type) + else: + has_subtypes = datatype_has_subtypes(list_data_type) + value = '{}{}.map {}{{ {}{}(swift: $0) }}'.format(value, + '?' if nullable else '', + prefix, + objc_type, + '.wrapPreservingSubtypes' if + has_subtypes else '') elif is_numeric_type(list_data_type): map_func = 'compactMap' if list_nullable else 'map' value = '{}{}.{} {}{{ $0 as NSNumber{} }}'.format(value, @@ -378,17 +440,26 @@ def _objc_return_field_value_oneliner(self, parent_type, field): map_func, prefix, '?' if list_nullable else '') - value = '{}{}'.format(value, suffix) return value - elif is_map_type(data_type) and is_user_defined_type(data_type.value_data_type): + elif is_map_type(data_type): objc_type = fmt_objc_type(data_type.value_data_type) value = '{}.{}'.format(swift_var_name, - fmt_var(field.name)) - value = '{}{}.mapValues {{ {}(swift: $0) }}'.format(value, - '?' if nullable else '', - objc_type) - return value + fmt_var(field.name)) + has_subtypes = datatype_has_subtypes(data_type.value_data_type) + if is_user_defined_type(data_type.value_data_type): + value = '{}{}.mapValues {{ {}{}(swift: $0) }}'.format(value, + '?' if nullable else '', + objc_type, + '.wrapPreservingSubtypes' if + has_subtypes else '') + return value + elif is_float_type(data_type.value_data_type): + value = '{}.{}{}.mapValues({{ $0 as NSNumber }})'.format(swift_var_name, + fmt_var(field.name), '?' if nullable else '') + return value + else: + return value elif is_user_defined_type(data_type): value = '' swift_arg_name = '{}.{}'.format(swift_var_name, @@ -398,9 +469,16 @@ def _objc_return_field_value_oneliner(self, parent_type, field): swift_var_name, fmt_var(field.name)) swift_arg_name = 'swift' - return '{}{}(swift: {})'.format(value, - fmt_objc_type(field.data_type, False), - swift_arg_name) + if is_union_type(data_type): + return '{}{}.factory(swift: {})'.format(value, + fmt_objc_type(data_type, False), + swift_arg_name) + else: + has_subtypes = datatype_has_subtypes(data_type) + return '{}{}{}(swift: {})'.format(value, + fmt_objc_type(data_type, False), + '.wrapPreservingSubtypes' if has_subtypes else '', + swift_arg_name) elif is_numeric_type(data_type) or is_boolean_type(data_type): return '{}.{} as NSNumber{}'.format(swift_var_name, fmt_var(field.name), @@ -474,7 +552,10 @@ def _swift_union_arg_to_objc(self, field): suffix) return value elif is_user_defined_type(field_data_type): - return '{}(swift: swiftArg)'.format(fmt_objc_type(field_data_type)) + if is_union_type(field_data_type): + return '{}.factory(swift: swiftArg)'.format(fmt_objc_type(field_data_type)) + else: + return '{}(swift: swiftArg)'.format(fmt_objc_type(field_data_type)) elif is_void_type(field_data_type): return '' elif nsnumber_type: