Skip to content

Commit

Permalink
Merge pull request #940 from DataDog/ncreated/RUMM-2266-generate-sess…
Browse files Browse the repository at this point in the history
…ion-replay-models

RUMM-2266 Reorganise `rum-models-generator` package for Session Replay code generation
  • Loading branch information
ncreated authored Jul 26, 2022
2 parents edee34f + ca7f957 commit 945adc4
Show file tree
Hide file tree
Showing 42 changed files with 600 additions and 356 deletions.
35 changes: 30 additions & 5 deletions tools/rum-models-generator/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,45 @@ let package = Package(
.package(name: "Difference", url: "https://github.com/krzysztofzablocki/Difference.git", from: "0.5.0"),
],
targets: [
// CLI wrapper
.target(
name: "rum-models-generator",
dependencies: [
"RUMModelsGeneratorCore",
"CodeGeneration",
"CodeDecoration",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]),
]
),
.testTarget(
name: "rum-models-generatorTests",
dependencies: ["rum-models-generator",]
),

// Product-agnostic code generator (JSON Schema -> Swift | Objc-interop)
.target(
name: "RUMModelsGeneratorCore",
name: "CodeGeneration",
dependencies: []
),
.testTarget(
name: "rum-models-generator-coreTests",
dependencies: ["RUMModelsGeneratorCore", "Difference"],
name: "CodeGenerationTests",
dependencies: [
"CodeGeneration",
"Difference"
],
resources: [.copy("Fixtures")]
),

// Product-specific code decorators
.target(
name: "CodeDecoration",
dependencies: ["CodeGeneration"]
),
.testTarget(
name: "CodeDecorationTests",
dependencies: [
"CodeDecoration",
"Difference"
]
),
]
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,22 @@
*/

import Foundation
import CodeGeneration

/// Adjusts naming and structure of generated code for RUM.
public class RUMCodeDecorator: CodeDecorator {
public init() {}

// MARK: - CodeDecorator

public func decorate(code: GeneratedCode) throws -> GeneratedCode {
return GeneratedCode(
swiftTypes: try transform(types: code.swiftTypes)
)
}

// MARK: - Internal

/// Transforms `SwiftTypes` for RUM code generation.
internal class RUMSwiftTypeTransformer {
/// Types which will shared between all input `types`. Sharing means detaching those types from nested declaration
/// and putting them at the root level of the resultant `types` array, so the type can be printed without being nested.
private let sharedTypeNames = [
Expand Down Expand Up @@ -269,3 +282,28 @@ internal class RUMSwiftTypeTransformer {
return SwiftTypeReference(referencedTypeName: name)
}
}

// MARK: - Utilities

extension String {
private var camelCased: String {
guard !isEmpty else {
return ""
}

let words = components(separatedBy: CharacterSet.alphanumerics.inverted)
let first = words.first! // swiftlint:disable:this force_unwrapping
let rest = words.dropFirst().map { $0.uppercasingFirst }
return ([first] + rest).joined(separator: "")
}

/// Uppercases the first character.
var uppercasingFirst: String { prefix(1).uppercased() + dropFirst() }
/// Lowercases the first character.
var lowercasingFirst: String { prefix(1).lowercased() + dropFirst() }

/// "lowerCamelCased" notation.
var lowerCamelCased: String { camelCased.lowercasingFirst }
/// "UpperCamelCased" notation.
var upperCamelCased: String { camelCased.uppercasingFirst }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-2020 Datadog, Inc.
*/

import Foundation
import CodeGeneration

/// Adjusts naming and structure of generated code for Session Replay.
public class SRCodeDecorator: CodeDecorator {
public init() {}

// MARK: - CodeDecorator

public func decorate(code: GeneratedCode) throws -> GeneratedCode {
return code // RUMM-2266 Implement actual decoration
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-2020 Datadog, Inc.
*/

import Foundation

/// Type-safe Swift schema.
public protocol SwiftType {}

/// Swift primitive type.
public protocol SwiftPrimitiveType: SwiftType {}
/// An allowed value of Swift primitive type.
public protocol SwiftPrimitiveValue {}
/// An allowed default value of Swift property.
public protocol SwiftPropertyDefaultValue {}
/// An allowed value of Swift with no obj-c interoperability.
public protocol SwiftPrimitiveNoObjcInteropType: SwiftPrimitiveType {}

extension Bool: SwiftPrimitiveValue, SwiftPropertyDefaultValue {}
extension Int: SwiftPrimitiveValue, SwiftPropertyDefaultValue {}
extension Int64: SwiftPrimitiveValue, SwiftPropertyDefaultValue {}
extension String: SwiftPrimitiveValue, SwiftPropertyDefaultValue {}
extension Double: SwiftPrimitiveValue, SwiftPropertyDefaultValue {}

/// Represents `Swift.Codable` - we need to define utility type because it cannot be declared as `extension` to `Codable`.
public struct SwiftCodable: SwiftPrimitiveNoObjcInteropType {}

/// Represents `Swift.Encodable` - we need to define utility type because it cannot be declared as `extension` to `Encodable`.
public struct SwiftEncodable: SwiftPrimitiveNoObjcInteropType {}

public struct SwiftPrimitive<T: SwiftPrimitiveValue>: SwiftPrimitiveType {
public init() {}
}

public struct SwiftArray: SwiftType {
public var element: SwiftType
}

public struct SwiftDictionary: SwiftType {
public let key = SwiftPrimitive<String>()
public var value: SwiftPrimitiveType
}

/// An `enum` with raw-type cases.
public struct SwiftEnum: SwiftType {
public struct Case: SwiftType, SwiftPropertyDefaultValue {
public enum RawValue {
case string(value: String)
case integer(value: Int)
}
public var label: String
public var rawValue: RawValue
}

public var name: String
public var comment: String?
public var cases: [Case]
public var conformance: [SwiftProtocol]
}

/// An `enum` with associated-type cases.
public struct SwiftAssociatedTypeEnum: SwiftType {
public struct Case: SwiftType, SwiftPropertyDefaultValue {
public var label: String
public var associatedType: SwiftType
}

public var name: String
public var comment: String?
public var cases: [Case]
public var conformance: [SwiftProtocol]
}

public struct SwiftStruct: SwiftType {
public struct Property: SwiftType {
/// Mutability levels of a property.
/// From the lowest `immutable` to the highest `mutable`.
public enum Mutability: Int {
case immutable
case mutableInternally
case mutable
}

public enum CodingKey {
/// Static coding key with fixed value.
case `static`(value: String)
/// Dynamic coding key with value determined at runtime.
case `dynamic`

public var isStatic: Bool {
switch self {
case .static: return true
case .dynamic: return false
}
}
}

public var name: String
public var comment: String?
public var type: SwiftType
public var isOptional: Bool
public var mutability: Mutability
public var defaultValue: SwiftPropertyDefaultValue?
public var codingKey: CodingKey
}

public var name: String
public var comment: String?
public var properties: [Property]
public var conformance: [SwiftProtocol]
}

public struct SwiftProtocol: SwiftType {
public var name: String
public var conformance: [SwiftProtocol]

public init(name: String, conformance: [SwiftProtocol]) {
self.name = name
self.conformance = conformance
}
}

/// Reference to any other Swift type.
public struct SwiftTypeReference: SwiftType {
public var referencedTypeName: String

public init(referencedTypeName: String) {
self.referencedTypeName = referencedTypeName
}
}

public let codableProtocol = SwiftProtocol(name: "Codable", conformance: [])

// MARK: - Helpers

public extension SwiftType {
/// The name of this type (or `nil` if this type is unnamed).
var typeName: String? {
let `struct` = self as? SwiftStruct
let `enum` = self as? SwiftEnum
let associatedTypeEnum = self as? SwiftAssociatedTypeEnum
return `struct`?.name ?? `enum`?.name ?? associatedTypeEnum?.name
}
}

// MARK: - Equatable

extension SwiftStruct: Equatable {
public static func == (lhs: SwiftStruct, rhs: SwiftStruct) -> Bool {
return String(describing: lhs) == String(describing: rhs)
}
}

extension SwiftEnum: Equatable {
public static func == (lhs: SwiftEnum, rhs: SwiftEnum) -> Bool {
return String(describing: lhs) == String(describing: rhs)
}
}

public func == (lhs: SwiftType, rhs: SwiftType) -> Bool {
return String(describing: lhs) == String(describing: rhs)
}

public func != (lhs: SwiftType, rhs: SwiftType) -> Bool {
return !(lhs == rhs)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-2020 Datadog, Inc.
*/

import Foundation

/// Generates code from provided JSON schema file.
public struct ModelsGenerator {
public init() {}

/// Generates code from JSON schema file.
/// - Parameter schemaFileURL: the URL of schema file
/// - Returns: schema for generated code.
public func generateCode(from schemaFileURL: URL) throws -> GeneratedCode {
let jsonSchema = try JSONSchemaReader().read(schemaFileURL)
let jsonType = try JSONSchemaToJSONTypeTransformer().transform(jsonSchema: jsonSchema)
let swiftTypes = try JSONToSwiftTypeTransformer().transform(jsonType: jsonType)
return GeneratedCode(swiftTypes: swiftTypes)
}
}

/// Schema describing generated code.
public struct GeneratedCode {
/// An array of Swift schemas describing root constructs in generated code.
public let swiftTypes: [SwiftType]

/// Changes this schema by applying provided decoration.
/// It can be used to adjust naming and structure of generated code.
public func decorate(using decorator: CodeDecorator) throws -> GeneratedCode {
return try decorator.decorate(code: self)
}

/// Renders generated code with provided template.
public func print(using template: OutputTemplate, and printer: CodePrinter) throws -> String {
let codeText = try printer.print(code: self)
return template.render(code: codeText)
}

public init(swiftTypes: [SwiftType]) {
self.swiftTypes = swiftTypes
}
}

/// A type decorating generated code.
/// Decoration can be used to adjust naming and structure in generated code before it is printed.
public protocol CodeDecorator {
func decorate(code: GeneratedCode) throws -> GeneratedCode
}

/// The template for generated code file.
public struct OutputTemplate {
let header: String
let footer: String

/// Initializer.
/// - Parameters:
/// - header: a text block to put before generated code
/// - footer: a text block to put after generated code
public init(header: String, footer: String) {
self.header = header
self.footer = footer
}

func render(code: String) -> String {
return [header, code, footer].joined()
}
}

/// Prints generated code.
public protocol CodePrinter {
func print(code: GeneratedCode) throws -> String
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,24 @@ import Foundation
/// public var integer: NSNumber { foo.integer as NSNumber }
/// }
///
internal class ObjcInteropPrinter: BasePrinter {
/// The prefix used for types exposed to Obj-c.
private let objcTypeNamesPrefix: String

init(objcTypeNamesPrefix: String) {
public class ObjcInteropPrinter: BasePrinter, CodePrinter {
public init(objcTypeNamesPrefix: String) {
self.objcTypeNamesPrefix = objcTypeNamesPrefix
}

// MARK: - CodePrinter

public func print(code: GeneratedCode) throws -> String {
let objcInteropTransformer = SwiftToObjcInteropTypeTransformer()
let objcInteropTypes = try objcInteropTransformer.transform(swiftTypes: code.swiftTypes)
return try print(objcInteropTypes: objcInteropTypes)
}

// MARK: - Internal

/// The prefix used for types exposed to Obj-c.
private let objcTypeNamesPrefix: String

func print(objcInteropTypes: [ObjcInteropType]) throws -> String {
reset()
try objcInteropTypes.forEach { try print(objcInteropType: $0) }
Expand Down
Loading

0 comments on commit 945adc4

Please sign in to comment.