Skip to content
This repository has been archived by the owner on Oct 30, 2022. It is now read-only.

Propose and implement a good documentation convention for OpenAPI refinements #346

Open
mikesol opened this issue Nov 22, 2019 · 0 comments
Assignees
Labels
documentation Improvements or additions to documentation refactoring Housekeeping and pruning

Comments

@mikesol
Copy link

mikesol commented Nov 22, 2019

OpenAPI refinements is pretty hard to understand. The basic idea is that it transforms OpenAPI schemas into subsets of those schemas. For example, it allows you to remove certain methods (ie remove a POST method from an endpoint), allows you to constrain an array in a response with a minimum or maximum length, etc.

The idea of "subset" is a bit confusing here, at least to me. Subset refers to the outcome space. For example, imagine that an Open API schema says that endpoint GET kittens can return an array of Kitten objects. A valid subset of the outcome space may be all arrays of kittens with at least three values. That is, array with min length three are valid possible outcomes for GET kittens, but are not exhaustive of all possible outcomes, as there may be 0, 1 or 2 kittens in real life.

Narrowing the outcome space is how unmock makes transformations in tests like "serve me an array of at least three kittens when I run this test" or "always 404 when I hit this endpoint in a test".

OpenAPI refinements does this by using monocle-ts. Monocle TS allows you to drill down into arbitrarily complex data structures and get/set little bits of them. In the case of a setter, it returns a copy of the data structure with the "setter" part changed and everything else staying the same.

Code with lenses tends to be super functional-looking, which is a pleasure to write and (IMO) hard to read. For example, take the following code:

const lensToOperations = (
  path: RegExp | boolean,
  operations: MethodNames[] | boolean,
) =>
  lensToPath(path)
    .composeIso(objectToArray<any>())
    .composeTraversal(
      fromTraversable(array)<[string, any]>().filter(i =>
        typeof operations === "boolean"
          ? operations
          : operations.map(z => `${z}`).indexOf(i[0]) !== -1,
      ),
    )
    .composeLens(valueLens())
    .composePrism(
      new Prism<any, Operation>(s => (isOperation(s) ? some(s) : none), a => a),
    );

It is a composition of optic primitives in the functional style, but it is too opaque to be maintainable by anyone other than the author (me). Essentially what's happening is that we are progressively zooming in on and object based on certain rules and conditions. Because the pattern is so regular, I have a hard time knowing the best way to document what's going on.

It would be great if someone with fresh eyes had a chance to read over this and help out. The best way to start IMO is with the initial "zooms". For example, the function lensToOperations above calls lensToPath, so lensToPath should be understood first before moving on to how it is used as a building bloc.

Once this is done, it'd be great to find a way to better document this code, which probably requires refactoring and perhaps even splitting into multiple files, so that it was more maintainable and ideally pedagogical.

@mikesol mikesol added documentation Improvements or additions to documentation refactoring Housekeeping and pruning labels Nov 22, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation Improvements or additions to documentation refactoring Housekeeping and pruning
Projects
None yet
Development

No branches or pull requests

2 participants