OpenAPIKit

I think it's a great idea to have a separate library that handles parsing, representing and exporting OpenAPI files. Looks like you already covered a large part of the spec!

I'm not sure though if it's a good idea to use Codable for parsing and generating files. My main concerns are (1) error messages when parsing yaml and json files, and (2) losing dictionary order of the source files.

(1) Error handling

OpenAPI files can get quite large and it's easy to make mistakes when editing them. In my opinion it's important to have meaningful error messages that contain the line number of the file where the error occurred. I'm not sure if that's possible when using JSONDecoder or YAMLDecoder + Codable.

Consider this minimal example:

openapi: 3.0.0
info:
  title: API
  version: 1.0.0
paths:
  /all-items:
    summary: Get all items
    get:
      responses:
        "200":
          description: All items
  /one-item:
    get:
      summary: Get one item

The error is that the second path (/one-item) must contain at least one response. The error at the moment looks like this (formatted for readability):

Swift.DecodingError.dataCorrupted(
  Swift.DecodingError.Context(
    codingPath: [],
    debugDescription: "The given data was not valid YAML.",
    underlyingError: Optional(Poly failed to decode any of its types at: "paths//one-item"

      JSONReference<Components, PathItem> could not be decoded because:
      keyNotFound(
        CodingKeys(
          stringValue: "$ref",
          intValue: nil
        ),
        Swift.DecodingError.Context(
          codingPath: [
            CodingKeys(stringValue: "paths", intValue: nil),
            _DictionaryCodingKey(stringValue: "/one-item", intValue: nil)
          ],
          debugDescription: "No value associated with key CodingKeys(stringValue: \"$ref\", intValue: nil) (\"$ref\").",
          underlyingError: nil
        )
      )

      PathItem could not be decoded because:
      keyNotFound(
        CodingKeys(stringValue: "responses", intValue: nil),
        Swift.DecodingError.Context(codingPath: [
            CodingKeys(stringValue: "paths", intValue: nil),
            _DictionaryCodingKey(stringValue: "/one-item", intValue: nil),
            CodingKeys(stringValue: "get", intValue: nil)
          ],
          debugDescription: "No value associated with key CodingKeys(stringValue: \"responses\", intValue: nil) (\"responses\").",
          underlyingError: nil
        )
      )
    )
  )
)

I'm sure the error message can be improved by evaluating the underlying errors, but I don't know if it's possible to add line numbers with this approach.

(2) Dictionary order

Personally I think it's important to preserve the order of the dictionaries in the source files. Consider the paths object. It often contains dozens of paths and operations and people tend to create a "semantic" order in the source file (e.g. first register, then login, then list all items, then create an item, the get an item, ...). Especially when generating documentation it's very helpful if this order is preserved. SwaggerUI will do this, and the Ruby library that I've used in the past preserves the order, too.

Another case is modifying OpenAPI files programmatically. If the order and formatting isn't preserved, a git diff will show large blocks of removed and added lines, even if only one item was changed by the program.

To be able to do this we would need event-driven JSON and YAML parsers I think, and an intermediate representation of the OpenAPI document that stores lines numbers and formatting and uses ordered dictionaries.

What do you think?

1 Like