VaporOpenAPI icon indicating copy to clipboard operation
VaporOpenAPI copied to clipboard

Some protocol that create Schemas that can be loaded into the docs

Open jcbriones opened this issue 2 years ago • 14 comments

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.

jcbriones avatar Feb 07 '23 22:02 jcbriones

Yeah, this would be nice. Are you referring to Fluent models in particular or models that are represented as plain Swift structs?

mattpolzin avatar Feb 08 '23 00:02 mattpolzin

yes fluent models that automatically adds it to the doc.

jcbriones avatar Feb 08 '23 02:02 jcbriones

actually not really fluent models exactly because i use dtos and doesn't directly use the fluent models as front facing

jcbriones avatar Feb 08 '23 02:02 jcbriones

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.

mattpolzin avatar Feb 08 '23 02:02 mattpolzin

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?

mattpolzin avatar Feb 09 '23 14:02 mattpolzin

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).

mattpolzin avatar Feb 09 '23 14:02 mattpolzin

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?

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 OpenAPIEncodedSchema conformance to your ActivityLog type (by just calling to ActivityLog.Public's existing conformance).

I haven't really look at that yet. I'll try to play around with it.

jcbriones avatar Feb 09 '23 14:02 jcbriones

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!

mattpolzin avatar Feb 09 '23 17:02 mattpolzin

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.

mattpolzin avatar Feb 09 '23 17:02 mattpolzin

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!

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. image

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

=====

What to be expected should be something like image

and the models looks like image

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"
    }
}

jcbriones avatar Feb 09 '23 21:02 jcbriones

Basically it would use this "$ref": "#/definitions/Pet" and the actual object Pet gets added as a schema if currently does not exist.

jcbriones avatar Feb 09 '23 21:02 jcbriones

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"]
      ]
  ]
)

jcbriones avatar Feb 09 '23 21:02 jcbriones

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.

mattpolzin avatar Feb 09 '23 22:02 mattpolzin

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.

jcbriones avatar Feb 10 '23 00:02 jcbriones