Some protocol that create Schemas that can be loaded into the docs
I notice that the models does not get generated as a schema in the docs and has to be manually imported. There probably is a way now to do it but if not this will be a good enhancement to this. Like a struct to inherit a some kind of schema that gets loaded when creating the docs.
Yeah, this would be nice. Are you referring to Fluent models in particular or models that are represented as plain Swift structs?
yes fluent models that automatically adds it to the doc.
actually not really fluent models exactly because i use dtos and doesn't directly use the fluent models as front facing
Do your DTOs encode to JSON pretty directly or do they use Swift features like property wrappers? If you'd be able to share an example of a DTO you use, it's possible there's a workable solution for you that just isn't documented very well.
It looks like you've got ActivityLog.Public set up to render in OpenAPI documentation; am I reading your above comments correctly that you do have some glue code that currently adds that ActivityLog.Public structure to your OpenAPI documentation, but you'd like it to be more automatic? If so, what is your current approach?
What I am wondering is if you could possibly only need to add OpenAPIEncodedSchema conformance to your ActivityLog type (by just calling to ActivityLog.Public's existing conformance).
It looks like you've got
ActivityLog.Publicset up to render in OpenAPI documentation; am I reading your above comments correctly that you do have some glue code that currently adds thatActivityLog.Publicstructure to your OpenAPI documentation, but you'd like it to be more automatic? If so, what is your current approach?
Yes correct. I actually did a little modification to current implementation that conforms to a new protocol that I created.
For objects:
import Foundation
import OpenAPIKit
import Sampleable
import Vapor
import VaporTypedRoutes
import VaporOpenAPI
public protocol OpenAPISchema: Encodable, Sampleable, OpenAPIExampleProvider {
static var componentKey: OpenAPI.ComponentKey { get }
}
extension OpenAPISchema {
public static var componentKey: OpenAPI.ComponentKey {
.init(rawValue: String(describing: Self.self).replacingOccurrences(of: ".", with: ""))!
}
public static func openAPISchema(using encoder: JSONEncoder) throws -> JSONSchema {
try genericOpenAPISchemaGuess(using: encoder)
}
}
For enums:
import OpenAPIKit
import OpenAPIReflection
import Vapor
import VaporOpenAPI
public protocol OpenAPIEnum: Content, CaseIterable, Hashable, AnyJSONCaseIterable {
static var componentKey: OpenAPI.ComponentKey { get }
}
extension OpenAPIEnum {
public static var componentKey: OpenAPI.ComponentKey {
.init(rawValue: String(describing: Self.self).replacingOccurrences(of: ".", with: ""))!
}
}
and AcitivityLog.Public now got changed to this:
extension ActivityLog.Public: OpenAPISchema {
static var sample: Self {
.init(id: .init(), created: nil, requestType: nil, url: nil, ip: nil, statusCode: nil, errorLog: nil)
}
}
Currently still optimizing it more to just use one. But still learning how to use the OpenAPIKit and VaporOpenAPI. I also made a fork to both VaporOpenAPI and OpenAPIReflection to make some minor changes to it and start supporting your 3.0 release.
What I am wondering is if you could possibly only need to add
OpenAPIEncodedSchemaconformance to yourActivityLogtype (by just calling toActivityLog.Public's existing conformance).
I haven't really look at that yet. I'll try to play around with it.
To give my thought some context, if your Vapor.Route has a response type of ActivityLog, then your response body schema will get picked up by https://github.com/mattpolzin/VaporOpenAPI/blob/main/Sources/VaporOpenAPI/VaporRoute%2BOpenAPI.swift#L263 if your ActivityLog type conforms to OpenAPIEncodedSchemaType; and, as I mentioned above, it should be a trivial conformance by simply calling out to the same methods you've defined for the ActivityLog.Public type already. Anyway, if you get around to giving that a try I'd be curious to hear if that makes anything a bit easier!
If, by some chance, the DTO protocol dictates that there exists a nested Public type, you could probably even write an extension DTO: OpenAPIEncodedSchemaType where Public: OpenAPIEncodedSchemaType to save some repetition.
To give my thought some context, if your
Vapor.Routehas a response type ofActivityLog, then your response body schema will get picked up by https://github.com/mattpolzin/VaporOpenAPI/blob/main/Sources/VaporOpenAPI/VaporRoute%2BOpenAPI.swift#L263 if yourActivityLogtype conforms toOpenAPIEncodedSchemaType; and, as I mentioned above, it should be a trivial conformance by simply calling out to the same methods you've defined for theActivityLog.Publictype already. Anyway, if you get around to giving that a try I'd be curious to hear if that makes anything a bit easier!
Yes this part works and that's what I'm actually doing with the OpenAPISchema. It does generate the encoded schema as expected. However, the issue I believe is that those schemas that conforms to OpenAPIEncodedSchemaType does not get added as a schema model and used as a ref. I was hoping it would create the models as schemas automatically and the endpoints use a ref to those objects.
For example, I generated a route that returns the NotificationCount. It does not say that it is returning that model or schema.

The schema that I manually added. But still somehow not referenced.

=====
What to be expected should be something like

and the models looks like

This returns a ref to the object which looks like this
"responses": {
"200": {
"description": "successful operation",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Pet"
}
}
},
"400": {
"description": "Invalid tag value"
}
}
Basically it would use this "$ref": "#/definitions/Pet" and the actual object Pet gets added as a schema if currently does not exist.
This how I currently generate the schemas.
let document = try OpenAPI.Document(
info: info,
servers: servers,
paths: paths,
components: .init(
schemas: [
User.Public.componentKey: User.Public.genericOpenAPISchemaGuess(using: jsonEncoder),
User.Count.componentKey: User.Count.genericOpenAPISchemaGuess(using: jsonEncoder),
User.NotificationCount.componentKey: User.NotificationCount.genericOpenAPISchemaGuess(using: jsonEncoder),
DeviceType.componentKey: JSONSchema.string(required: true, nullable: false, allowedValues: DeviceType.allCases(using: jsonEncoder))
],
securitySchemes: [
.init(rawValue: "oauth")!: oauth
]
),
security: [
[
.component(named: "oauth"): ["OAuth 2.0"]
]
]
)
Oh, I see now. Yeah, the libraries as currently written don’t do any sort of equivalence checking (to determine that the schema for one endpoint is the same as the schema of another) so I don’t use references on purpose. I can see how this is less than ideal. Given an assumption that a particular swift type is always serialized under the same rules (so it could be turned into a single referenced schema in the OpenAPI documentation), this could definitely be improved upon. It might be nice to have some sort of flag that someone indicates a model should be stored in the components instead of inlined into the response documentation.
Yeah that was the goal I have in mind. That's why i have some kind of DTO models that conforms to OpenAPISchema. Anything Codable, Sampleable, and OpenAPIEncodedSchemaType will tell VaporOpenAPI that the model can be reference as long as it's used in routes.