Skip to content

fix: enhance model property handling for array types in Swift template #1087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 4, 2025

Conversation

ChiragAgg5k
Copy link
Member

@ChiragAgg5k ChiragAgg5k commented Jun 3, 2025

What does this PR do?

Fixes handling for array types.

Test Plan

before:

attributes: map["attributes"] as! [AnyCodable],   // CRASHES ON THIS LINE

after:

attributes: (map["attributes"] as! [Any]).map { AnyCodable($0) },

Related PRs and Issues

Have you read the Contributing Guidelines on issues?

yes.

@ChiragAgg5k ChiragAgg5k requested a review from Copilot June 3, 2025 08:23
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates the Swift model template to correctly map raw [Any] arrays into [AnyCodable] when generating model from(map:) initializers.

  • Adds a special case for properties typed as [AnyCodable] to map each element through AnyCodable(_:)
  • Provides a default empty array (?? []) for optional [AnyCodable] properties
  • Leaves existing behavior for other array‐of‐model mappings unchanged
Comments suppressed due to low confidence (2)

templates/swift/Sources/Models/Model.swift.twig:84

  • Using as! [Any] will crash if the cast fails. Consider using as? [Any] with a fallback (e.g., map["..."] as? [Any] ?? []) to avoid runtime errors.
(map["{{property.name }}"] as! [Any]...

templates/swift/Sources/Models/Model.swift.twig:84

  • There should be dedicated unit tests covering model decoding for [AnyCodable] properties, including empty, non-array, and malformed inputs.
(map["{{property.name }}"] as? [Any] ?? []).map

@@ -81,7 +81,7 @@ open class {{ definition | modelType(spec) | raw }}: Codable {
public static func from(map: [String: Any] ) -> {{ definition.name | caseUcfirst }} {
return {{ definition.name | caseUcfirst }}(
{%~ for property in definition.properties %}
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}{% if property.type == 'array' and (property | propertyType(spec) | raw) == '[AnyCodable]' %}(map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} [Any]{% if not property.required %} ?? []{% endif %}).map { AnyCodable($0) }{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}
Copy link
Preview

Copilot AI Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Detecting [AnyCodable] via a raw string comparison is brittle. Consider exposing a metadata flag (e.g., property.isAnyCodableArray) or using the schema to detect this case more reliably.

Copilot uses AI. Check for mistakes.

@@ -81,7 +81,7 @@ open class {{ definition | modelType(spec) | raw }}: Codable {
public static func from(map: [String: Any] ) -> {{ definition.name | caseUcfirst }} {
return {{ definition.name | caseUcfirst }}(
{%~ for property in definition.properties %}
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}{% if property.type == 'array' and (property | propertyType(spec) | raw) == '[AnyCodable]' %}(map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} [Any]{% if not property.required %} ?? []{% endif %}).map { AnyCodable($0) }{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}
Copy link
Preview

Copilot AI Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Defaulting an optional [AnyCodable] property to an empty array (?? []) changes its semantic from nil to []. If consumer code relies on distinguishing nil vs. empty, consider preserving nil for optional arrays.

Copilot uses AI. Check for mistakes.

@@ -81,7 +81,7 @@ open class {{ definition | modelType(spec) | raw }}: Codable {
public static func from(map: [String: Any] ) -> {{ definition.name | caseUcfirst }} {
return {{ definition.name | caseUcfirst }}(
{%~ for property in definition.properties %}
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}{% if property.type == 'array' and (property | propertyType(spec) | raw) == '[AnyCodable]' %}(map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} [Any]{% if not property.required %} ?? []{% endif %}).map { AnyCodable($0) }{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, as suggested by copilot, you can instead add a function in https://github.com/appwrite/sdk-generator/blob/master/src/SDK/Language/Swift.php That returns a bool. use the existing propertyType function and add function like isCodable or something that makes sense that checks this case

@@ -81,7 +81,8 @@ open class {{ definition | modelType(spec) | raw }}: Codable {
public static func from(map: [String: Any] ) -> {{ definition.name | caseUcfirst }} {
return {{ definition.name | caseUcfirst }}(
{%~ for property in definition.properties %}
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %}
{%~ set isDocument = definition.name | lower == 'document' %}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a comment here, temporary until we fix the BE

@lohanidamodar lohanidamodar merged commit 70284c1 into master Jun 4, 2025
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants