Skip to content
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

RUMM-1000 Add support for additional properties to the models generation pipeline #399

Merged
merged 19 commits into from
Feb 8, 2021

Conversation

acgh
Copy link
Contributor

@acgh acgh commented Feb 4, 2021

What and why?

We ultimately want to add support for custom timings. This was previously introduced to the schema in rum-events-format #25 relying on JSON Schema's additionalProperty.

Our Swift/Objc pipeline that generates models from a schema doesn't know how to handle additionalProperties at this point. To prevent from mis-generating the model for custom timings we previously had to put a dedicated filter until we add the support for additionalProperties.

In this PR we add the plumbing needed for a basic support of additionalProperty. It will allow us to remove the filter and generate the model for custom timings.

How?

As of 8e6fbbf the approach has been simplified. To sum-up, we model additionalProperties as dictionaries while adding a relatively restrictive support of SwiftDictionary (only primitive types).
Any future gaps between an improved use of JSON Schema in the input files and the current limitations of the pipeline should be caught by exceptions at runtime, and prior to shipping the generated code inside the SDK.

JSON

We add an additionalProperties property to the JSONSchema. It is optional and of type JSONSchema itself as required by the spec.

We map that schema to a JSONObject.AdditionalProperties struct which is a subset of a JSONObject.Property. It holds a type that is a JSONPrimitive, meaning that we only support bool, double, integer, string at this point. The mapping happens in the JSONSchemaToJSONTypeTransformer.

Swift

To support additional properties in SwiftStruct, we introduce a basic SwiftDictionary. Note that keys are String and values must be SwiftPrimitiveType, i.e. Bool, Int, Int64, String, Double.
From that point we can declare a SwiftStruct.Property of type SwiftDictionary to model JSONSchema's additionalProperties. The mapping from JSONObject to SwiftStruct is done in JSONToSwiftTypeTransformer.

JSONToSwiftTypeTransformer enforces a couple of new rules:

  • A provided root object cannot map to a Dictionary, especially a root object cannot have additionalProperties that would require a mapping to a dictionary.
  • A JSON object cannot hold both regular properties and additionalProperties.

Objc Interop

To mirror additions to the Swift type system and transformer, we introduce a ObjcInteropNSDictionary and add the expected mapping methods inside SwiftToObjcInteropTypeTransformer.

RUM

We just add some basic expect methods to allow traversing a SwiftDictionary and transforming any primitive types, e.g. Int -> Int64.

Printers

This has been simplified a lot compared to the initial approach (41a1ac8 and before), they only need to know how to print dictionaries while converting some core Swift <-> Objc types.

Review checklist

  • Feature or bugfix MUST have appropriate tests (unit, integration)
  • Make sure each commit and the PR mention the Issue number or JIRA reference

Next

  • Run tools/rum-models-generator/run.sh generate to add custom timings
    Spoiler this should generate something to resolve this:
... RUMDataModels.swift is out of sync with rum-events-format: 8b955a03d0fe0b2f032a02d6800c61ef3fc9fada
Difference was found when comparing it with template file:
>>>
111,113d110
<         /// User custom timings of the view
<         public let customTimings: [String: Int64]?
< 
172d168
<             case customTimings = "custom_timings"
<<<

@acgh acgh requested a review from a team as a code owner February 4, 2021 10:44
@acgh acgh self-assigned this Feb 4, 2021
Alexandre Costanza added 2 commits February 4, 2021 13:33
)
propertyWrapper.objcInteropType = try objcInteropType(for: swiftArray)
return propertyWrapper
case let swiftDictionary as SwiftDictionary where swiftDictionary.value is SwiftPrimitiveType:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The whole function comes from the block of code previously starting at line 35. But I want to draw attention to this case that handles a SwiftDictionary.

@@ -89,11 +129,59 @@ public class SwiftPrinter: BasePrinter {
writeLine("}")
}

private func printCodableMethods(for properties: [SwiftStruct.Property], additionalProperties: SwiftStruct.Property) throws {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alexandre Costanza added 2 commits February 5, 2021 21:58
Copy link
Member

@ncreated ncreated left a comment

Choose a reason for hiding this comment

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

Looks way simpler and much more straightforward now 🚀. I left some comments that we may consider to simplify it even further - let me know WDYT.

let isRequired: Bool
let isReadOnly: Bool
}

let name: String
let comment: String?
let properties: [Property]
let additionalProperties: Property?
Copy link
Member

Choose a reason for hiding this comment

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

I think that we're using the Property type here with a wrong purpose. Looking at how "additionalProperties" work, only Property.type, Property.isReadOnly and Property.comment apply to it and we force artificial / uncontrolled values for name, defaultValue and isRequired. Maybe it's worth introducing separate type: JSONObject.AdditionalProperties(comment:type:isReadOnly), WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that it's a good point and good idea!

Comment on lines 88 to 91
case let swiftDictionary as SwiftDictionary where swiftDictionary.key is SwiftEnum || swiftDictionary.value is SwiftEnum:
throw Exception.unimplemented(
"Objc Interop for `SwiftDictionary`s with an Enum as key or value is not supported."
)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it's easier to change SwiftDictionary.value to be SwiftPrimitiveType to have compile-time check for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good idea, and similarly SwiftDictionary.key should be a SwiftPrimitiveType for now.

Comment on lines 31 to 34
internal struct SwiftDictionary: SwiftType {
var key: SwiftType
var value: SwiftType
}
Copy link
Member

Choose a reason for hiding this comment

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

IMO we can save some control flows / tests if being more tailored on that, i.e.:

    var key: SwiftPrimitive<String>
    var value: SwiftPrimitiveType

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes you're right!

Copy link
Member

@ncreated ncreated left a comment

Choose a reason for hiding this comment

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

LVG 🎸

@acgh acgh merged commit d61fd7b into master Feb 8, 2021
@acgh acgh deleted the acgh/rumm-1000-support-custom-timings-json-schema branch February 8, 2021 14:20
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