Skip to content

Multi-file Swift Output - #1221

Merged
schani merged 40 commits into
glideapps:masterfrom
agilebits:swift/roo/new/multi-file-output
May 18, 2019
Merged

Multi-file Swift Output#1221
schani merged 40 commits into
glideapps:masterfrom
agilebits:swift/roo/new/multi-file-output

Conversation

@MrRooni

@MrRooni MrRooni commented May 7, 2019

Copy link
Copy Markdown
Contributor

Reason to Be

When running quicktype to generate Swift code from a directory of schema files the output currently dumps everything into a single .swift file. This PR breaks each named type out into its own file. Additionally it generates a JSONSchemaSupport.swift that contains many of the helper functions needed by the various outputs.

Resolves #687

CHANGELOG Entry

[IMPROVED] Generating Swift files from a directory of inputs now outputs one Swift file per top level schema object.

Thought Process

After seeing that the Java renderer already does this exact thing and digging into how quicktype writes its files I began refactoring the renderer to divide up the generated source into sensible blocks.

How To Test

Testing manually should be as simple as:

quicktype . -l Swift -s schema -o .

Possible Impacts

I know for a fact that I've broken the rendering of one of the schema inputs. shakes fist at keyword-unions.schema

I'll be working to get that fixed up…somehow.

@MrRooni

MrRooni commented May 7, 2019

Copy link
Copy Markdown
Contributor Author

Throwing an informal wait tag on this one. Need to tidy up a couple things first, but wanted to see how the tests turn out from the server.

MrRooni added 2 commits May 7, 2019 08:35
…o/new/multi-file-output

# Conflicts:
#	src/quicktype-core/language/Swift.ts
@schani

schani commented May 7, 2019

Copy link
Copy Markdown
Member

Looks like the emitted code is incorrect:

main.swift:12:28: error: value of optional type 'TopLevel?' not unwrapped; did you mean to use '!' or '?'?
  | guard let jsonString = try obj.jsonString() else {
  | ^
  | ?
  |  
  | Fixture threw an exception
  | {
  | "error": {
  | "command": "swiftc -o quicktype main.swift quicktype.swift",
  | "code": 1
  | },
  | "sample": {
  | "path": "test/inputs/json/priority/combinations1.json",
  | "additionalRendererOptions": {
  | "support-linux": "false"
  | },
  | "saveOutput": false
  | },
  | "cwd": "/app/test/runs/swift-4f7779"
  | }
  |  
  | main.swift:12:28: error: value of optional type 'TopLevel?' not unwrapped; did you mean to use '!' or '?'?
  | guard let jsonString = try obj.jsonString() else {
  | ^
  | ?
  |  
  | Fixture threw an exception
  | {
  | "error": {
  | "command": "swiftc -o quicktype main.swift quicktype.swift",
  | "code": 1
  | },
  | "sample": {
  | "path": "test/inputs/json/priority/combinations2.json",
  | "additionalRendererOptions": {
  | "support-linux": "false"
  | },
  | "saveOutput": false
  | },
  | "cwd": "/app/test/runs/swift-031586"
  | }
  |  
  | main.swift:12:28: error: value of optional type 'TopLevel?' not unwrapped; did you mean to use '!' or '?'?
  | guard let jsonString = try obj.jsonString() else {
  | ^
  | ?
  |  
  | Fixture threw an exception
  | {
  | "error": {
  | "command": "swiftc -o quicktype main.swift quicktype.swift",
  | "code": 1
  | },
  | "sample": {
  | "path": "test/inputs/json/priority/combinations3.json",
  | "additionalRendererOptions": {
  | "support-linux": "false"
  | },
  | "saveOutput": false
  | },
  | "cwd": "/app/test/runs/swift-2cfdc9"
  | }

@schani

schani commented May 7, 2019

Copy link
Copy Markdown
Member
JSONSchemaSupport.swift:33:41: error: use of undeclared type 'Hasher'
  | public func hash(into hasher: inout Hasher) {
  | ^~~~~~
  |  
  | Fixture threw an exception
  | {
  | "error": {
  | "command": "swiftc -o quicktype *.swift",
  | "code": 1
  | },
  | "sample": {
  | "path": "test/inputs/json/priority/combinations3.json",
  | "additionalRendererOptions": {
  | "support-linux": "false"
  | },
  | "saveOutput": false
  | },
  | "cwd": "/app/test/runs/swift-30719e"
  | }
  |  
  | JSONSchemaSupport.swift:33:41: error: use of undeclared type 'Hasher'
  | public func hash(into hasher: inout Hasher) {
  | ^~~~~~
  |  
  | Fixture threw an exception
  | {
  | "error": {
  | "command": "swiftc -o quicktype *.swift",
  | "code": 1
  | },
  | "sample": {
  | "path": "test/inputs/json/priority/combinations4.json",
  | "additionalRendererOptions": {
  | "support-linux": "false"
  | },
  | "saveOutput": false
  | },
  | "cwd": "/app/test/runs/swift-8ba570"
  | }

@schani

schani commented May 7, 2019

Copy link
Copy Markdown
Member
JSONSchemaSupport.swift:33:41: error: use of undeclared type 'Hasher'
  | public func hash(into hasher: inout Hasher) {
  | ^~~~~~
  |  
  | Fixture threw an exception
  | {
  | "error": {
  | "command": "swiftc -o quicktype *.swift",
  | "code": 1
  | },
  | "sample": {
  | "path": "test/inputs/json/priority/combinations3.json",
  | "additionalRendererOptions": {
  | "support-linux": "false"
  | },
  | "saveOutput": false
  | },
  | "cwd": "/app/test/runs/swift-511bfd"
  | }

@schani schani left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can this still produce one-file output? That should be the default. We have many users of quicktype with Swift who I'm sure depend on the current behavior. Sorry I didn't point this out earlier.

Comment thread build/quicktype-core/package.in.json Outdated
Comment thread src/quicktype-core/Renderer.ts Outdated
Comment thread src/quicktype-core/Renderer.ts Outdated
@schani

schani commented May 9, 2019

Copy link
Copy Markdown
Member
JSONSchemaSupport.swift:33:41: error: use of undeclared type 'Hasher'
  | public func hash(into hasher: inout Hasher) {
  | ^~~~~~
  |  
  | Fixture threw an exception
  | {
  | "error": {
  | "command": "swiftc -o quicktype *.swift",
  | "code": 1
  | },
  | "sample": {
  | "path": "test/inputs/json/priority/combinations4.json",
  | "additionalRendererOptions": {
  | "support-linux": "false"
  | },
  | "saveOutput": false
  | },
  | "cwd": "/app/test/runs/swift-c28655"
  | }
  | [object Object]
  | at /app/test/test.ts:68:17
  | at step (/app/test/test.ts:32:23)
  | at Object.throw (/app/test/test.ts:13:53)
  | at rejected (/app/test/test.ts:5:65)
  | at <anonymous>
  | at process._tickDomainCallback (internal/process/next_tick.js:229:7)
  |  
  | JSONSchemaSupport.swift:33:41: error: use of undeclared type 'Hasher'
  | public func hash(into hasher: inout Hasher) {
  | ^~~~~~
  |  
  | Fixture threw an exception
  | {
  | "error": {
  | "command": "swiftc -o quicktype *.swift",
  | "code": 1
  | },
  | "sample": {
  | "path": "test/inputs/json/priority/combinations3.json",
  | "additionalRendererOptions": {
  | "support-linux": "false"
  | },
  | "saveOutput": false
  | },
  | "cwd": "/app/test/runs/swift-a4533e"
  | }

@MrRooni

MrRooni commented May 9, 2019

Copy link
Copy Markdown
Contributor Author

@schani I should be able to make single file output the default, yes. I'll get that fixed up. Thanks for posting those errors. I'll be interested in seeing how the latest push works, those tests all pass locally for me.

@schani

schani commented May 10, 2019

Copy link
Copy Markdown
Member
quicktype.swift:339:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:505:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:630:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:831:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:1077:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:1271:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:1425:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:1620:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:1866:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:2020:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:2179:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:2509:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:2808:22: error: invalid redeclaration of 'codableTask(with:completionHandler:)'
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  | quicktype.swift:184:22: note: 'codableTask(with:completionHandler:)' previously declared here
  | fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
  | ^
  |  
  | Fixture threw an exception
  | {
  | "error": {
  | "command": "swiftc -o quicktype main.swift quicktype.swift",
  | "code": 1
  | },
  | "sample": {
  | "path": "test/inputs/json/priority/combinations1.json",
  | "additionalRendererOptions": {
  | "url-session": "true"
  | },
  | "saveOutput": false
  | },
  | "cwd": "/app/test/runs/swift-eb5a9b"
  | }

@schani

schani commented May 10, 2019

Copy link
Copy Markdown
Member
Command failed
  | {
  | "command": "diff -Naur quicktype.swift.expected quicktype.swift > /dev/null 2>&1",
  | "code": 1,
  | "cwd": "/app/test/runs/swift-01855a"
  | }
  | Fixture threw an exception
  | {
  | "error": {
  | "command": "diff -Naur quicktype.swift.expected quicktype.swift > /dev/null 2>&1",
  | "code": 1,
  | "cwd": "/app/test/runs/swift-01855a"
  | },
  | "sample": {
  | "path": "test/inputs/json/priority/bug863.json",
  | "additionalRendererOptions": {},
  | "saveOutput": true
  | },
  | "cwd": "/app/test/runs/swift-01855a"
  | }

What failed here is another test we do: It generates JSON Schema from the input JSON, and then it generates Swift from the JSON Schema, and diffs that Swift output with the one coming directly from the input JSON. Ideally those should be identical, but here's a case where they differ. Could you look into that, please? As you can see in languages.ts there's a long list of inputs in skipDiffViaSchema that we already exempt from that test because it's a bit brittle (in the case of Swift it's mostly issues with date/time).

@MrRooni

MrRooni commented May 10, 2019

Copy link
Copy Markdown
Contributor Author

I'm on it, thanks @schani

@schani

schani commented May 10, 2019

Copy link
Copy Markdown
Member

Here's the diff.

One thing that jumps out right away is that the header

+// To parse the JSON, add this file to your project and do:
+//
+//   let dataClass = try DataClass(json)

is added multiple times per file. That should be fixed. I haven't looked at it in more detail apart from that.

@schani
schani merged commit 53fc1c6 into glideapps:master May 18, 2019
@schani

schani commented May 18, 2019

Copy link
Copy Markdown
Member

Awesome, thank you!

@MrRooni
MrRooni deleted the swift/roo/new/multi-file-output branch May 18, 2019 17:05
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.

Output a file per object (Swift)

2 participants