Use cases for making things more dynamic

We have a lot of dynamic things we want to do in our API definitions. I want to look at use cases and examples that show how one might use Geneva to solve them.

A few notes about the examples below:

  1. The variables are values defined before the definition is executed.
  2. The definition is the code, in a sense
  3. The result is what the code produced when it runs
  4. The examples I show are truncated—they aren't valid or complete OpenAPI or AsyncAPI definitions

Everything but the results are editable. Have fun!

We want reusability

As noted, we've added keywords like $ref to specifications to help people make things reusable. We want to define once and reuse all over. Geneva does this by way of ref:.

variables (edit)
info:
contact:
name: API Support
url: http://www.example.com/support
email: support@example.com
definition (edit)
openapi: 3.0.0
info: ref:info
result
openapi: 3.0.0
info:
contact:
name: API Support
url: http://www.example.com/support
email: support@example.com

This differs from $ref that we find in OpenAPI and JSON Schema. The ref here is referencing a variable in the current scope. Geneva lets you define variables and reuse them through the entire definition. This means Geneva's ref and $ref could be used together.

variables (edit)
schemas:
customer:
$ref: https://example.com/customer.yml
definition (edit)
openapi: 3.0.0
components:
schemas:
Customer: ref:schemas.customer
result
openapi: 3.0.0
components:
schemas:
Customer:
$ref: https://example.com/customer.yml

This could be a way to pass in different schemas for different builds.

We want to compose files together

Left untended, YAML documents grow in complexity and size. It's not easy to read or edit a 2,000 line YAML document. The next step is to break files like this into multiple files.

Here I create a mock file system that I reference in the definition. Geneva allows for passing in the normal file system modules, too, though this might not be safe.

files (edit)
infoFile: |
contact:
name: API Support
url: http://www.example.com/support
email: support@example.com
definition (edit)
openapi: 3.0.0
info:
fn:include: infoFile
result
openapi: 3.0.0
info:
contact:
name: API Support
url: http://www.example.com/support
email: support@example.com

Imagine including Markdown files that technical writers put together. Or adding in security definitions managed by the security team. Or pulling in operations defined in a separate file. You could include many different kinds of files without bloating a specification with that functionality.

We want localization

Similarly, people may want to add localization to their OpenAPI or AsyncAPI definition. Instead of adding localization to the specification, people could pass in different languages to build separate final OpenAPI definitions.

variables (edit)
lang: en
locale:
userSignedupDescription:
en: An event describing that a user just signed up.
getLocale:
fn:lambda:
- term
- fn:path:
- [ref:term, ref:lang]
- ref:locale
definition (edit)
asyncapi: 2.0.0
info:
title: Example
version: 0.1.0
channels:
user/signedup:
subscribe:
message:
description:
fn:getLocale: userSignedupDescription
result
asyncapi: 2.0.0
info:
title: Example
version: 0.1.0
channels:
user/signedup:
subscribe:
message:
description: An event describing that a user just signed up.

If you look, you'll see that I use fn:lambda, which is a function definition. I'm making my own function to use throughout my definition. I'll let you decide if this is a great or horrible idea.

We want base definitions

Sometimes we want to add certain status codes to every response in an OpenAPI definition. This happens for error codes that all return the same error format.

variables (edit)
postResponses:
fn:lambda:
- responses
- fn:mergeDeepLeft:
- responses:
"405":
description: Method Not Allowed
content:
application/json: {}
- ref:responses
definition (edit)
openapi: 3.0.0
paths:
/customers:
post:
fn:postResponses:
responses:
"200":
description: Customer added
content:
application/json: {}
result
openapi: 3.0.0
paths:
/customers:
post:
responses:
"200":
description: Customer added
content:
application/json: {}
"405":
description: Method Not Allowed
content:
application/json: {}

We want a way to use templates

We saw where AWS requires people to use a !Join function to create strings. Maybe a template would be better.

Geneva provides a fn:template function that allows for mustache templates. This is a simple example to show how templates work. You can use mustache features in the template or use other functions of Geneva to create values.

variables (edit)
env: dev
definition (edit)
openapi: 3.0.0
info:
description:
fn:template: |
This is in the {{env}} environment.
result
openapi: 3.0.0
info:
description: |
This is in the dev environment.