Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/mattpolzin/Sampleable", from: "2.0.0"),
.package(url: "https://github.com/mattpolzin/JSONAPI", from: "4.0.0"),
.package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "1.0.0"),
.package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "1.4.0"),
.package(url: "https://github.com/mattpolzin/OpenAPIReflection", .upToNextMinor(from: "0.3.0")),
.package(url: "https://github.com/typelift/SwiftCheck", .upToNextMinor(from: "0.12.0")),
.package(url: "https://github.com/apple/swift-format", from: "0.50200.1"),
Expand Down
13 changes: 13 additions & 0 deletions Sources/JSONAPISwiftGen/Optional+ZipWith.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Optional+ZipWith.swift
// JSONAPIOpenAPI
//
// Created by Mathew Polzin on 1/19/19.
//

/// Zip two optionals together with the given operation performed on
/// the unwrapped contents. If either optional is nil, the zip
/// yields nil.
func zip<X, Y, Z>(_ left: X?, _ right: Y?, with fn: (X, Y) -> Z) -> Z? {
return left.flatMap { lft in right.map { rght in fn(lft, rght) }}
}
16 changes: 7 additions & 9 deletions Sources/JSONAPISwiftGen/ResourceObjectSwiftGenCollection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import OpenAPIKit
public struct ResourceObjectSwiftGenCollection {
public let resourceObjectGenerators: [ResourceObjectSwiftGen]

public init(_ doc: OpenAPI.Document, testSuiteConfiguration: TestSuiteConfiguration) throws {
public init(_ doc: DereferencedDocument, testSuiteConfiguration: TestSuiteConfiguration) throws {
let pathItems = doc.paths

resourceObjectGenerators = OpenAPI.HttpMethod.allCases
Expand All @@ -30,7 +30,7 @@ public struct ResourceObjectSwiftGenCollection {
for: httpVerb,
at: path,
on: doc.servers.first!,
given: parameters.compactMap { $0.b },
given: parameters,
testSuiteConfiguration: testSuiteConfiguration
).values.flatMap { Array($0.resourceObjectGenerators) }
}
Expand All @@ -41,23 +41,21 @@ public struct ResourceObjectSwiftGenCollection {
}

func documents(
from responses: OpenAPI.Response.Map,
from responses: DereferencedResponse.Map,
for httpVerb: OpenAPI.HttpMethod,
at path: OpenAPI.Path,
on server: OpenAPI.Server,
given params: [OpenAPI.Parameter],
given params: [DereferencedParameter],
testSuiteConfiguration: TestSuiteConfiguration
) -> [OpenAPI.Response.StatusCode: DataDocumentSwiftGen] {
var responseDocuments = [OpenAPI.Response.StatusCode: DataDocumentSwiftGen]()
for (statusCode, response) in responses {

guard let jsonResponse = response.b?.content[.json] else {
guard let jsonResponse = response.content[.json] else {
continue
}

guard let responseSchema = jsonResponse.schema.b else {
continue
}
let responseSchema = jsonResponse.schema

guard case .object = responseSchema else {
print("Found non-object response schema root (expected JSON:API 'data' object). Skipping '\(String(describing: responseSchema.jsonTypeFormat?.jsonType))'.")
Expand All @@ -78,7 +76,7 @@ func documents(
example = nil
}

let testExampleFuncs: [SwiftFunctionGenerator]
let testExampleFuncs: [TestFunctionGenerator]
do {
let responseBodyType = SwiftTypeRep(.init(name: responseBodyTypeName))
if let testPropertiesDict = jsonResponse.vendorExtensions["x-tests"]?.value as? [String: Any] {
Expand Down
186 changes: 124 additions & 62 deletions Sources/JSONAPISwiftGen/Swift Generators/DocumentSwiftGen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import JSONAPI
/// and build a representation of a JSON:API Document that can handle both
/// Data and Error cases.
public struct DataDocumentSwiftGen: JSONSchemaSwiftGenerator {
public let structure: JSONSchema
public let structure: DereferencedJSONSchema
public let decls: [Decl]
public let swiftTypeName: String
public let resourceObjectGenerators: Set<ResourceObjectSwiftGen>
public let exampleGenerator: ExampleSwiftGen?
public let testExampleFuncs: [SwiftFunctionGenerator]
public let testExampleFuncs: [TestFunctionGenerator]

/// Generate Swift code not just for this Document's declaration but
/// also for all declarations required for this Document to compile.
Expand All @@ -30,23 +30,29 @@ public struct DataDocumentSwiftGen: JSONSchemaSwiftGenerator {
.joined(separator: "\n")
}

public init(swiftTypeName: String,
structure: JSONSchema,
allowPlaceholders: Bool = true,
example: ExampleSwiftGen? = nil,
testExampleFuncs: [SwiftFunctionGenerator] = []) throws {
public init(
swiftTypeName: String,
structure: DereferencedJSONSchema,
allowPlaceholders: Bool = true,
example: ExampleSwiftGen? = nil,
testExampleFuncs: [TestFunctionGenerator] = []
) throws {
self.swiftTypeName = swiftTypeName
self.structure = structure
self.exampleGenerator = example
self.testExampleFuncs = testExampleFuncs

(decls, resourceObjectGenerators) = try DataDocumentSwiftGen.swiftDecls(from: structure,
swiftTypeName: swiftTypeName,
allowPlaceholders: allowPlaceholders)
(decls, resourceObjectGenerators) = try DataDocumentSwiftGen.swiftDecls(
from: structure,
swiftTypeName: swiftTypeName,
allowPlaceholders: allowPlaceholders
)
}

static func swiftDeclsForErrorDocument(from resourceObjectContext: JSONSchema.ObjectContext,
swiftTypeName: String) throws -> [Decl] {
static func swiftDeclsForErrorDocument(
from resourceObjectContext: DereferencedJSONSchema.ObjectContext,
swiftTypeName: String
) throws -> [Decl] {
guard let errorsSchema = resourceObjectContext.properties["errors"],
case .array(_, let arrayContext) = errorsSchema,
let errorsItems = arrayContext.items else {
Expand All @@ -58,35 +64,53 @@ public struct DataDocumentSwiftGen: JSONSchemaSwiftGenerator {

let errorsItemsDecls: [Decl]
do { //GenericJSONAPIError<ErrorPayload>
let errorTypealias = Typealias(alias: .def(.init(name: errorTypeName)),
existingType: .def(.init(name: "GenericJSONAPIError",
specializationReps: [.def(.init(name: errorPayloadTypeName))])))
let errorTypealias = Typealias(
alias: .def(
.init(name: errorTypeName)
),
existingType: .def(
.init(
name: "GenericJSONAPIError",
specializationReps: [.def(.init(name: errorPayloadTypeName))]
)
)
)

errorsItemsDecls = try StructureSwiftGen(swiftTypeName: errorPayloadTypeName,
structure: errorsItems,
cascadingConformances: ["Codable", "Equatable"]).decls
errorsItemsDecls = try StructureSwiftGen(
swiftTypeName: errorPayloadTypeName,
structure: errorsItems,
cascadingConformances: ["Codable", "Equatable"]
).decls
+ [errorTypealias]
} catch let error {
throw Error.failedToCreateErrorsStructure(underlyingError: error)
}

let documentTypealiasDecl = Typealias(alias: .def(.init(name: swiftTypeName)),
existingType: .def(.init(name: "JSONAPI.Document",
specializationReps: [
.init(NoResourceBody.self),
.init(NoMetadata.self),
.init(NoLinks.self),
.init(NoIncludes.self),
.init(NoAPIDescription.self),
.def(.init(name: errorTypeName))
])))
let documentTypealiasDecl = Typealias(
alias: .def(.init(name: swiftTypeName)),
existingType: .def(
.init(
name: "JSONAPI.Document",
specializationReps: [
.init(NoResourceBody.self),
.init(NoMetadata.self),
.init(NoLinks.self),
.init(NoIncludes.self),
.init(NoAPIDescription.self),
.def(.init(name: errorTypeName))
]
)
)
)

return errorsItemsDecls + [documentTypealiasDecl]
}

static func swiftDecls(from structure: JSONSchema,
swiftTypeName: String,
allowPlaceholders: Bool) throws -> ([Decl], Set<ResourceObjectSwiftGen>) {
static func swiftDecls(
from structure: DereferencedJSONSchema,
swiftTypeName: String,
allowPlaceholders: Bool
) throws -> ([Decl], Set<ResourceObjectSwiftGen>) {
guard case let .object(_, resourceObjectContextB) = structure else {
throw Error.rootNotJSONObject
}
Expand Down Expand Up @@ -115,19 +139,29 @@ public struct DataDocumentSwiftGen: JSONSchemaSwiftGenerator {
let primaryResourceTypeName: String
switch data {
case .object:
let resourceObject = try ResourceObjectSwiftGen(structure: data,
allowPlaceholders: allowPlaceholders)
let resourceObject = try ResourceObjectSwiftGen(
structure: data,
allowPlaceholders: allowPlaceholders
)
primaryResourceTypeName = resourceObject.resourceTypeName

let isNullablePrimaryResource = data.nullable

// SingleResourceBody<PrimaryResource>
primaryResourceBodyType = .def(.init(name: "SingleResourceBody",
specializationReps: [
.def(.init(name: primaryResourceTypeName,
specializationReps: [],
optional: isNullablePrimaryResource))
]))
primaryResourceBodyType = .def(
.init(
name: "SingleResourceBody",
specializationReps: [
.def(
.init(
name: primaryResourceTypeName,
specializationReps: [],
optional: isNullablePrimaryResource
)
)
]
)
)

allResourceObjectGenerators.insert(resourceObject)

Expand All @@ -137,15 +171,25 @@ public struct DataDocumentSwiftGen: JSONSchemaSwiftGenerator {
throw Error.expectedDataArrayToDefineItems
}

let resourceObject = try ResourceObjectSwiftGen(structure: dataItem,
allowPlaceholders: allowPlaceholders)
let resourceObject = try ResourceObjectSwiftGen(
structure: dataItem,
allowPlaceholders: allowPlaceholders
)
primaryResourceTypeName = resourceObject.resourceTypeName

primaryResourceBodyType = .def(.init(name: "ManyResourceBody",
specializationReps: [
.def(.init(name: primaryResourceTypeName,
specializationReps: []))
]))
primaryResourceBodyType = .def(
.init(
name: "ManyResourceBody",
specializationReps: [
.def(
.init(
name: primaryResourceTypeName,
specializationReps: []
)
)
]
)
)

allResourceObjectGenerators.insert(resourceObject)

Expand All @@ -169,35 +213,53 @@ public struct DataDocumentSwiftGen: JSONSchemaSwiftGenerator {
switch items {
case .one(of: let resourceTypeSchemas, _):
resources = try Array(Set(resourceTypeSchemas.map {
try ResourceObjectSwiftGen(structure: $0,
allowPlaceholders: allowPlaceholders)
try ResourceObjectSwiftGen(
structure: $0,
allowPlaceholders: allowPlaceholders
)
})).sorted { $0.resourceTypeName < $1.resourceTypeName }
default:
resources = [try ResourceObjectSwiftGen(structure: items,
allowPlaceholders: allowPlaceholders)]
resources = [
try ResourceObjectSwiftGen(
structure: items,
allowPlaceholders: allowPlaceholders
)
]
}

let resourceTypes = resources.map { SwiftTypeRep.def(.init(name: $0.resourceTypeName)) }

includeType = .def(.init(name: "Include\(resourceTypes.count)",
specializationReps: resourceTypes))
includeType = .def(
.init(
name: "Include\(resourceTypes.count)",
specializationReps: resourceTypes
)
)


allResourceObjectGenerators = allResourceObjectGenerators.union(resources)
} else {
includeType = .rep(NoIncludes.self)
}

allDecls.append(Typealias(alias: .def(.init(name: swiftTypeName)),
existingType: .def(.init(name: "JSONAPI.Document",
specializationReps: [
primaryResourceBodyType,
.init(NoMetadata.self),
.init(NoLinks.self),
includeType,
.init(NoAPIDescription.self),
"BasicJSONAPIError<AnyCodable>"
]))))
allDecls.append(
Typealias(
alias: .def(.init(name: swiftTypeName)),
existingType: .def(
.init(
name: "JSONAPI.Document",
specializationReps: [
primaryResourceBodyType,
.init(NoMetadata.self),
.init(NoLinks.self),
includeType,
.init(NoAPIDescription.self),
"BasicJSONAPIError<AnyCodable>"
]
)
)
)
)

return (allDecls, allResourceObjectGenerators)
}
Expand Down
Loading