Skip to content

Commit

Permalink
Add Objective-C to Swift SDK migration shim.
Browse files Browse the repository at this point in the history
  • Loading branch information
julianlocke committed Sep 30, 2024
1 parent 40bd164 commit 436fdd7
Show file tree
Hide file tree
Showing 11 changed files with 780 additions and 22 deletions.
3 changes: 3 additions & 0 deletions stone/backends/obj_c_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@
'id',
'delete',
'hash',
'boolvalue',
'floatvalue',
'intvalue',
}

_reserved_prefixes = {
Expand Down
277 changes: 277 additions & 0 deletions stone/backends/swift.py

Large diffs are not rendered by default.

60 changes: 59 additions & 1 deletion stone/backends/swift_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -137,14 +141,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:
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
Expand All @@ -158,6 +171,8 @@ def _generate_client(self, api):

self._write_output_in_target_folder(template.render(),
'DBX{}.swift'.format(self.args.module_name))

self._generate_sdk_migration_shim(api)
else:
template = self._jinja_template("SwiftClient.jinja")
template.globals = template_globals
Expand Down Expand Up @@ -296,6 +311,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 = []
Expand Down Expand Up @@ -557,6 +598,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 = {}
Expand Down
23 changes: 17 additions & 6 deletions stone/backends/swift_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -282,11 +297,7 @@ 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:
Expand Down
4 changes: 4 additions & 0 deletions stone/backends/swift_rsrc/ObjCRoutes.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,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
Expand Down
19 changes: 16 additions & 3 deletions stone/backends/swift_rsrc/ObjcTypes.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,22 @@ 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 }}) {
self.{{ swift_var_name }} = swift
Expand Down Expand Up @@ -85,9 +98,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
}

Expand Down
51 changes: 51 additions & 0 deletions stone/backends/swift_rsrc/SwiftObjcArgTypeMappings.jinja
Original file line number Diff line number Diff line change
@@ -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 %}
Loading

0 comments on commit 436fdd7

Please sign in to comment.