You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Ability to attach macros to generated types and methods #383
It would be nice to be able to add macros to the generated Schema and API requests in order to extend the functionality, e.g. all of my request return structures have an optional error field, so if I could add a macro that would allow me to extend the support for a protocol for them, I could easily handle the error uniformly
@HonQii could you elaborate a little on what you'd like to see? You can currently write extensions on the types generated and conform them to protocols that you define. Are you asking for the protocol conformance to be automatically added for you by the generator, potentially by specifying a macro you'd like to run in the generator config?
Maybe you could provide a quick snippet of how you'd like the generated code to be different.
Maybe @HonQii means that they could configure some macro name to be automatically attached to all generated types, similar to how we allow adding additional imports? But the user would still need to provide the macro implementation.
(Which is an interesting idea that would indeed provide quite a lot of flexibility for sufficiently motivated adopters.)
Maybe @HonQii means that they could configure some macro name to be automatically attached to all generated types, similar to how we allow adding additional imports? But the user would still need to provide the macro implementation.🔄 ❓
(Which is an interesting idea that would indeed provide quite a lot of flexibility for sufficiently motivated adopters.)🔄 ❓
Right, what I mean is that I can configure macros to the generated Schema as well as the Client so that I can do some wrapping. An example:
// Each of my request response structures will have an error field with a message and code if there is an error
structBaseResponse{varerror:Error? // empty if there is no error.
}
Normally I would need to define a protocol for all structures to follow, and then I could handle these errors consistently. But in the case of autogeneration, I think it's more appropriate to use a macro for this purpose
Similarly, I need a layer of encapsulation for each request in the client to handle exceptions, which can also be accomplished using a macro
Can you be even more specific here, for example use a simple OpenAPI document, generate code with it, and then outline what exact changes you'd like to see in the generated code? I'm still not entirely sure I understand.
// Each of my request response structures will have an error field with a message and code if there is an error
structBaseResponse{varerror:Error? // empty if there is no error.
}
OOI, are you the author of this document? Because, if so, for this example, you could have the generated code make use of shared types by writing this in the OpenAPI document itself. For example, you can make use of of a common type in #/components/schemas, or you could define a shared response in #/components/responses, or you could try and combine these with an allOf1.
Concretely, if you make use of references in the OpenAPI document, then the generator will use a single type. This allows for you to write extensions on this common type once.
Can you be even more specific here, for example use a simple OpenAPI document, generate code with it, and then outline what exact changes you'd like to see in the generated code? I'm still not entirely sure I understand.您能否在这里更具体一点,例如使用一个简单的 OpenAPI 文档,用它生成代码,然后概述您希望在生成的代码中看到哪些确切的更改?我仍然不完全确定我是否理解。
For example, I'm using the Combine framework to encapsulate a lot of UI components, so I'd like to be able to return web requests through the Publisher, and I'm now generating my own batch of code using the Plugin, which is formatted as follows:
If I could support macros when generating swift-openapi-generator, I could generate the methods directly from the macros, and instead of having to convert the results using the filterSuccess method, I could just make each structure in the Schema adhere to a certain protocol, and use Combine's filter function to handle the errors.
// Each of my request response structures will have an error field with a message and code if there is an error
structBaseResponse{varerror:Error? // empty if there is no error.
}
OOI, are you the author of this document? Because, if so, for this example, you could have the generated code make use of shared types by writing this in the OpenAPI document itself. For example, you can make use of of a common type in #/components/schemas, or you could define a shared response in #/components/responses, or you could try and combine these with an allOf1.OOI,你是本文档的作者吗?因为,如果是这样,对于此示例,您可以通过将其编写在 OpenAPI 文档本身中来让生成的代码使用共享类型。例如,您可以在 中使用 中的通用类型,也可以在 中定义共享响应,或者可以尝试将这些响应 #/components/schemas 与 allOf1 . #/components/responses
Concretely, if you make use of references in the OpenAPI document, then the generator will use a single type. This allows for you to write extensions on this common type once.具体来说,如果使用 OpenAPI 文档中的引用,则生成器将使用单个类型。这允许您在此常见类型上编写一次扩展。
If I understand correctly, you'd be interested in being able to use a macro to generate a variant of every client method that uses Combine, like you outlined above?
The question is where exactly the generator would add the @UserSpecifiedMacro macro, probably in the extension, so that the macro could emit an implementation (couldn't be on the func declaration in the protocol). I think that could work, and can be done post-1.0 as a non-breaking change, as this would be fully opt-in through the config file, similar to additionalImports.
changed the title [-]Can I add some parameters to specify that the generated Schema and API support swift macros?[/-][+]Ability to attach macros to generated types and methods[/+]on Nov 29, 2023
Right now, this is the only part of my code-base that I have to manually maintain and introduce the stub MockSomeAPIProtocol for every API and provide each method with mocked macro manually, yet the same part of my code-base, which is using auto generated code that breaks a lot.
I also can't introduce a variable with $ in an extension, as it is only allowed through macros and propertyWrappers.
Being able to ship it with the library itself, would be a great help.
Hello, FWIW I have worked on something in this scope too.
Since generated code gets regenerated on every build, so there's no way to attach attributes or attached macros to generated declarations. The generator rewrites the files on every build, so the only options are post-processing them or forking the generator.
What I propose is pretty straightforward. Adding a new optional config key, called macroAnnotations(name could change) for now, lists attributes per generated declaration:
* matches every schema or operation. The generator emits its attributes first, then the ones for the specific name. Each attribute goes on its own line after the doc comment:
/// - Remark: Generated from `#/components/schemas/Pet`.@MyMacro@PetMacropublicstructPet:Codable,Hashable,Sendable{...}
It should work for both clients and server so everything except client targets Types.swift, which client and server share. Server handlers conform to APIProtocol, which apiProtocol covers. Server.swift only extends APIProtocol and UniversalServer, so there's nothing server-specific to annotate.
Some Examples to illustrate that it could work for all the cases showed in this thread.
Shared handling of responses
This is the use case from this issue. If every response has an optional error field, a macro that adds a protocol conformance can go on every operation output:
This one was not in the thread but it's one the reason that I have worked on it. @DependencyClient from swift-dependencies needs a struct of closures. The generated Client has methods instead, so the macro can't go on it. Today you write a wrapper by hand and update it whenever the document changes:
@DependencyClientstructPetStoreClient{varlistPets: @Sendable(Operations.ListPets.Input)asyncthrows->Operations.ListPets.OutputvarcreatePet: @Sendable(Operations.CreatePet.Input)asyncthrows->Operations.CreatePet.Output
// ... one per operation
}extensionPetStoreClient:DependencyKey{staticvarliveValue:Self{letclient=Client(serverURL:...,transport:URLSessionTransport())returnSelf(listPets:{tryawaitclient.listPets($0)},createPet:{tryawaitclient.createPet($0)})}}
With apiProtocol, a peer macro on APIProtocol can generate this wrapper. When you add an operation to the document, the next build adds its closure to the wrapper.
The adopter or a library provides @DependencyClientWrapper. The generator only attaches it. live(_:) gives the liveValue, and @DependencyClient gives unimplemented closures for tests, so nobody maintains the wrapper by hand. That's what I was after in the forum thread.
Test mocks
Mockable generates a mock from a protocol. On APIProtocol, the mock works for testing both client callers and server handlers:
Thanks for fleshing this out, @mackoj, I really like where this is going.
A few things to dig into:
Is there really no "server" macro we imagine needing, for example to attach to the registerHandlers generated method? Do we see a need there?
Can we flesh out the exact way the schema name matching works? You showed "*" and an exact match, is that all? Or should it be a regex, meaning an exact match would need to be "^Pet$". Is it case sensitive matching?
Can we follow the structure of the doc and make it operations/schemas rather than just schemas at the top level?
Do we support other components, like parameters, requestBodies, etc?
If you'd prefer, I'm open to you opening a draft PR with a proposal, and we can iterate on some of the details there. But the general shape you have here looks good.
I have to admit that I didn't really think about how to push the server part before for this feature. Let's keep a server key for symmetry with client. It would target the generated registerHandlers(on:serverURL:configuration:middlewares:) method. For example, it could be used for a tracing or logging macro that wraps handler registration. It's a small addition, and it means the server side isn't a blind spot if someone needs it later. Did you have more in minds?
True, it's vague and underdefined because in my head I only needed those. I think going full Regex is more coherent than having a pseudo "*" next to exact names like "Pet". I think that the rules could be:
each key is a regex matched against the OpenAPI name (e.g. pet-store), not the Swift-safe name
to match Pet you will need to write ^Pet$ to not match PetOwner too
matching is case-sensitive and will follow the Regex
.* replaces "*" to match everything
when several keys match, their attributes are all emitted but the exact ordering still needs to be pinned down
This covers every component the generator turns into a type (Components.Schemas, Components.Parameters, Components.RequestBodies, Components.Responses, and Components.Headers). The other component kinds (examples, links, callbacks, security schemes) don't produce generated types today, so there would be nothing to annotate
Thanks for offering the proposal and the PR. I'll open a draft PR with a SOAR proposal so we can iterate on the details there.
I was thinking something like this '@DependencyClientWrapper(name: "PetStoreClient")' . I need a bit more time to think about how to express dynamic parameters.(bad idea)
Let's also drop the @ sign. The generator will always add it to macro names. But yeah, how parameters will be expressed in the config is something to figure out. Maybe an array of dictionaries with explicit label and value fields?
Activity
simonjbeaumont commentedon Nov 17, 2023
@HonQii could you elaborate a little on what you'd like to see? You can currently write extensions on the types generated and conform them to protocols that you define. Are you asking for the protocol conformance to be automatically added for you by the generator, potentially by specifying a macro you'd like to run in the generator config?
Maybe you could provide a quick snippet of how you'd like the generated code to be different.
czechboy0 commentedon Nov 17, 2023
Maybe @HonQii means that they could configure some macro name to be automatically attached to all generated types, similar to how we allow adding additional imports? But the user would still need to provide the macro implementation.
(Which is an interesting idea that would indeed provide quite a lot of flexibility for sufficiently motivated adopters.)
HonQii commentedon Nov 20, 2023
Right, what I mean is that I can configure macros to the generated Schema as well as the Client so that I can do some wrapping. An example:
Normally I would need to define a protocol for all structures to follow, and then I could handle these errors consistently. But in the case of autogeneration, I think it's more appropriate to use a macro for this purpose
Similarly, I need a layer of encapsulation for each request in the client to handle exceptions, which can also be accomplished using a macro
czechboy0 commentedon Nov 24, 2023
Can you be even more specific here, for example use a simple OpenAPI document, generate code with it, and then outline what exact changes you'd like to see in the generated code? I'm still not entirely sure I understand.
simonjbeaumont commentedon Nov 27, 2023
OOI, are you the author of this document? Because, if so, for this example, you could have the generated code make use of shared types by writing this in the OpenAPI document itself. For example, you can make use of of a common type in
#/components/schemas, or you could define a shared response in#/components/responses, or you could try and combine these with anallOf1.Concretely, if you make use of references in the OpenAPI document, then the generator will use a single type. This allows for you to write extensions on this common type once.
Footnotes
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#composition-and-inheritance-polymorphism ↩
HonQii commentedon Nov 28, 2023
For example, I'm using the Combine framework to encapsulate a lot of UI components, so I'd like to be able to return web requests through the Publisher, and I'm now generating my own batch of code using the Plugin, which is formatted as follows:
If I could support macros when generating swift-openapi-generator, I could generate the methods directly from the macros, and instead of having to convert the results using the
filterSuccessmethod, I could just make each structure in the Schema adhere to a certain protocol, and use Combine's filter function to handle the errors.HonQii commentedon Nov 28, 2023
Sorry, I didn't write it.
czechboy0 commentedon Nov 28, 2023
If I understand correctly, you'd be interested in being able to use a macro to generate a variant of every client method that uses Combine, like you outlined above?
If so, that seems very similar to the "add async variant" macro here: https://github.com/apple/swift-syntax/blob/main/Examples/Sources/MacroExamples/Implementation/Peer/AddAsyncMacro.swift
The question is where exactly the generator would add the
@UserSpecifiedMacromacro, probably in the extension, so that the macro could emit an implementation (couldn't be on the func declaration in the protocol). I think that could work, and can be done post-1.0 as a non-breaking change, as this would be fully opt-in through the config file, similar toadditionalImports.[-]Can I add some parameters to specify that the generated Schema and API support swift macros?[/-][+]Ability to attach macros to generated types and methods[/+]czechboy0 commentedon Nov 29, 2023
Okay, renaming this issue to track the general desire to attach macros to generated types and functions.
farzadshbfn commentedon Jun 18, 2026
@czechboy0 I just want to provide more use-cases that could benefit a lot from a Macro and are not possible via extensions.
I currently have a
@Mockablemacro which I apply to protocols, to generate a mock container for testing.For example the following code:
Would automatically generate:
Right now, this is the only part of my code-base that I have to manually maintain and introduce the stub
MockSomeAPIProtocolfor every API and provide each method with mocked macro manually, yet the same part of my code-base, which is using auto generated code that breaks a lot.I also can't introduce a variable with
$in an extension, as it is only allowed through macros and propertyWrappers.Being able to ship it with the library itself, would be a great help.
mackoj commentedon Oct 1, 2026
Hello, FWIW I have worked on something in this scope too.
Since generated code gets regenerated on every build, so there's no way to attach attributes or attached macros to generated declarations. The generator rewrites the files on every build, so the only options are post-processing them or forking the generator.
What I propose is pretty straightforward. Adding a new optional config key, called
macroAnnotations(name could change) for now, lists attributes per generated declaration:*matches every schema or operation. The generator emits its attributes first, then the ones for the specific name. Each attribute goes on its own line after the doc comment:It should work for both clients and server so everything except
clienttargets Types.swift, which client and server share. Server handlers conform toAPIProtocol, whichapiProtocolcovers. Server.swift only extendsAPIProtocolandUniversalServer, so there's nothing server-specific to annotate.mackoj commentedon Oct 1, 2026
Some Examples to illustrate that it could work for all the cases showed in this thread.
Shared handling of responses
This is the use case from this issue. If every response has an optional
errorfield, a macro that adds a protocol conformance can go on every operation output:swift-dependencies'
@DependencyClientThis one was not in the thread but it's one the reason that I have worked on it.
@DependencyClientfrom swift-dependencies needs a struct of closures. The generatedClienthas methods instead, so the macro can't go on it. Today you write a wrapper by hand and update it whenever the document changes:With
apiProtocol, a peer macro onAPIProtocolcan generate this wrapper. When you add an operation to the document, the next build adds its closure to the wrapper.The adopter or a library provides
@DependencyClientWrapper. The generator only attaches it.live(_:)gives theliveValue, and@DependencyClientgives unimplemented closures for tests, so nobody maintains the wrapper by hand. That's what I was after in the forum thread.Test mocks
Mockable generates a mock from a protocol. On
APIProtocol, the mock works for testing both client callers and server handlers:czechboy0 commentedon Oct 2, 2026
Thanks for fleshing this out, @mackoj, I really like where this is going.
A few things to dig into:
If you'd prefer, I'm open to you opening a draft PR with a proposal, and we can iterate on some of the details there. But the general shape you have here looks good.
mackoj commentedon Oct 2, 2026
Thanks for the feedback @czechboy0,
I have to admit that I didn't really think about how to push the server part before for this feature. Let's keep a
serverkey for symmetry withclient. It would target the generatedregisterHandlers(on:serverURL:configuration:middlewares:)method. For example, it could be used for a tracing or logging macro that wraps handler registration. It's a small addition, and it means the server side isn't a blind spot if someone needs it later. Did you have more in minds?True, it's vague and underdefined because in my head I only needed those. I think going full Regex is more coherent than having a pseudo
"*"next to exact names like"Pet". I think that the rules could be:pet-store), not the Swift-safe namePetyou will need to write^Pet$to not matchPetOwnertoo.*replaces"*"to match everythingYes, for sure
Would something like this work?
This covers every component the generator turns into a type (
Components.Schemas,Components.Parameters,Components.RequestBodies,Components.Responses, andComponents.Headers). The other component kinds (examples, links, callbacks, security schemes) don't produce generated types today, so there would be nothing to annotateThanks for offering the proposal and the PR. I'll open a draft PR with a SOAR proposal so we can iterate on the details there.
czechboy0 commentedon Oct 2, 2026
Yes that's looking much better. One more consideration - how do we express macros that take parameters?
mackoj commentedon Oct 2, 2026
I was thinking something like this '@DependencyClientWrapper(name: "PetStoreClient")' .
I need a bit more time to think about how to express dynamic parameters.(bad idea)czechboy0 commentedon Oct 3, 2026
Let's also drop the
@sign. The generator will always add it to macro names. But yeah, how parameters will be expressed in the config is something to figure out. Maybe an array of dictionaries with explicitlabelandvaluefields?