mattpolzin / OpenAPIKit

Codable Swift OpenAPI implementation.
MIT License
281 stars 35 forks source link

Load external references #287

Closed mattpolzin closed 4 months ago

mattpolzin commented 1 year ago

Work in progress interface for external reference loading can be summarized by the following example:

/// An example of implementing a loader context for loading external references
/// into an OpenAPI document.
struct ExampleLoaderContext: ExternalLoaderContext {
    static func load<T>(_ url: URL) throws -> T where T : Decodable {
        // load data from file, perhaps. we will just mock that up for the example:
        let data = mockParameterData(url)

        let decoded = try JSONDecoder().decode(T.self, from: data)
        let finished: T
        if var extendable = decoded as? VendorExtendable {
            extendable.vendorExtensions["x-source-url"] = AnyCodable(url)
            finished = extendable as! T
        } else {
            finished = decoded 
        }
        return finished
    }

    mutating func nextComponentKey<T>(type: T.Type, at url: URL, given components: OpenAPIKit.OpenAPI.Components) throws -> OpenAPIKit.OpenAPI.ComponentKey {
        // do anything you want here to determine what key the new component should be stored at.
        // for the example, we will just transform the URL into a valid components key:
        let urlString = url.pathComponents.dropFirst().joined(separator: "_").replacingOccurrences(of: ".", with: "_")
        return try .forceInit(rawValue: urlString)
    }

    /// Mock up some data, just for the example. 
    static func mockParameterData(_ url: URL) -> Data {
        return """
        {
            "name": "name",
            "in": "path",
            "schema": { "type": "string" },
            "required": true
        }
        """.data(using: .utf8)!
    }
}

var document = OpenAPI.Document(
   info: .init(title: "test document", version: "1.0.0"),
   servers: [],
   paths: [
       "/hello/{name}": .init(
           parameters: [
               .reference(.external(URL(string: "file://./params/name.json")!))
           ]
       )
    ],
   components: .init(
       // just to show, no parameters defined within document components :
       parameters: [:]
   )
)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

// - MARK: Before 
print(
   String(data: try encoder.encode(document), encoding: .utf8)!
)
/*
{
  "openapi": "3.1.0",
  "info": {
    "title": "test document",
    "version": "1.0.0"
  },
  "paths": {
    "\/hello\/{name}": {
      "parameters": [
        {
          "$ref": "file:\/\/.\/params\/name.json"
        }
      ]
    }
  }
}
*/

let context = ExampleLoaderContext()
try document.externallyDereference(in: context)

// - MARK: After
print(
   String(data: try encoder.encode(document), encoding: .utf8)!
)
/*
{
  "paths": {
    "\/hello\/{name}": {
      "parameters": [
        {
          "$ref": "#\/components\/parameters\/params_name_json"
        }
      ]
    }
  },
  "components": {
    "parameters": {
      "params_name_json": {
        "x-source-url": "file:\/\/.\/params\/name.json",
        "in": "path",
        "name": "name",
        "required": true,
        "schema": {
          "type": "string"
        }
      }
    }
  },
  "openapi": "3.1.0",
  "info": {
    "title": "test document",
    "version": "1.0.0"
  }
}
*/
mattpolzin commented 12 months ago

Tracked as ticket at https://github.com/mattpolzin/OpenAPIKit/issues/279

mattpolzin commented 4 months ago

I'm going to close this PR. The following work is more likely to be the ultimately design: https://github.com/mattpolzin/OpenAPIKit/pull/369