apple / swift-openapi-generator

Generate Swift client and server code from an OpenAPI document.
https://swiftpackageindex.com/apple/swift-openapi-generator/documentation
Apache License 2.0
1.43k stars 116 forks source link

Help needed in specifying openapi spec in one package, client and server then consume this package #620

Closed ptrkstr closed 1 month ago

ptrkstr commented 1 month ago

Question

Thank you for this fantastic package! I have followed the example projects here: https://swiftpackageindex.com/apple/swift-openapi-generator/1.3.0/tutorials/swift-openapi-generator/clientxcode

I wasn't able however to figure out how I could have 1 package that exists on its own that a Server package and Client xcode project can consume. Would there be any guides on this?

simonjbeaumont commented 1 month ago

I wasn't able however to figure out how I could have 1 package that exists on its own that a Server package and Client xcode project can consume. Would there be any guides on this?

Possibly we don't have an example for this exact workflow. Could you describe a bit more what you're trying to achieve? The package that "exists on its own": what does it provide to the others?

Are you looking to share the types generation between a client and server?

We have an example of a single Swift package with tree targets: types, client, and server, where the client and server targets depend on the shared types target. Is this what you want, but instead of a client target, you want to use an Xcode project for the client (because it's an app)?

ptrkstr commented 1 month ago

Thank you for the fast response @simonjbeaumont and apologies for my initial brevity. I think you understood correctly: I would like a central place (ie package) that both

The reason for all this is the app and server should be in their own separate codebases.

simonjbeaumont commented 1 month ago

The reason for all this is the app and server should be in their own separate codebases.

Yep, I agree that's the best because we'd recommend using a Swift package for the server, and you have no choice but to use an Xcode project for the client, if it's an app targeting Apple platforms.

At which point you should ask why you'd want to share the types between these two codebases via a third codebase and whether the complexity is worth it. If not, then I would have only the client and server codebases and share just the OpenAPI document between them. In the client Xcode project you'd specify types and client in the config file; and, in the server package, you'd use types and server. So long as they're using the same source of truth—the OpenAPI document—then that'll be fine. This is a common model too: consider, for example, the distribution of .proto files between projects as another source of truth, when working with gRPC.

The only reason I can think you'd want to do this is if you had written some custom extensions on the Swift types that you'd like to make use of across both the client and server codebases.

If you really do want to do this, then you would structure it as follows, with example names:

I'd also caution against this because, as we outline in the documentation, extra care needs to be taken when vending generated code as an API. In this case, you may be the only consumer of the generated code, but typically we advise folks don't vend it outside their codebase without careful consideration.

ptrkstr commented 1 month ago

Thank you for such a detailed response @simonjbeaumont! Your well presented arguments have convinced me that having only the openapi doc in each codebase is what would be most suitable for my use case. You've helped more than enough, but would you extend your expertise to suggest how one may manage a single document between codebases?

Not to beat a dead horse and bring up a package solution again, but perhaps storing the document only, in its own package and version controlling the package will allow both the app and server to specify the document package version. This would then require some way of making the app project and server package reference the document in the document package. I think this is better than manually copy and pasting the document to the app and server codebases.

simonjbeaumont commented 1 month ago

suggest how one may manage a single document between codebases?

Not to beat a dead horse and bring up a package solution again, but perhaps storing the document only, in its own package and version controlling the package will allow both the app and server to specify the document package version.

Of course. You're trying to get at a couple of things here:

  1. The mechanics of sharing an OpenAPI document between two source repositories; and
  2. Versioning the document as the API evolves.

Providing the OpenAPI document via a Swift package dependency isn't likely to work. While it's possible to bundle up resources into a Swift package, it's not really designed for doing only resource files. You'd need to likely play some tricks in this package manifest, e.g. creating an empty library and an empty product, through which you could smuggle the document.

But it's a non-starter in any event because, even if you could get that to work, Swift package plugins expect a deterministic set of input files and, in the case of Swift OpenAPI Generator, those are openapi-generator-config.yaml and openapi.yaml (or openapi.json). These must be present in your target source directory and there's no way to magically fish these out of an package dependency's bundled resources at build time—at least not without resorting to some out of band build scripts, at which point you're (1) off the supported path and (2) undermining the simplified workflow benefits that this tooling is supposed to offer.

For clients consuming an OpenAPI document from a service they do not maintain that is offering some sort of API stability, they should just commit a copy of the document into their client source repository.

For projects that are maintaining both a client and a server, I'd probably still recommend this, especially if the server API is being evolved in a backwards compatible way, and I'd probably use the .info.version field to communicate the version of the OpenAPI document to the client.

For projects that really want to lock-step development between client and server (sounds like your aim), I'd probably resort to some sort of Git submodule approach or other script to vendor it in automatically. I'd probably just keep it in the server repo since that's what's providing the API.

If you have your heart set on keeping it as its own root in the dependency tree then you could have a repo that contains only the OpenAPI document and use Git submodules to pull it in to the client and server repos.

Hope that helps :)

ptrkstr commented 1 month ago

Thanks so much @simonjbeaumont! I will stick to keeping the document on the server and copying it to the client for now then. I can admit I would have spent countless hours implementing a solution that would have had me fighting the package system had it not been for your concise replies. Thank you for your support and contribution to a fantastic extension to Server Side Swift 🙏

simonjbeaumont commented 1 month ago

You’re very welcome.