Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Message body serialization adapters #372

Open
kylef opened this issue Nov 27, 2019 · 0 comments
Open

Message body serialization adapters #372

kylef opened this issue Nov 27, 2019 · 0 comments

Comments

@kylef
Copy link
Member

kylef commented Nov 27, 2019

In order to serialize message bodies when generating examples for request/response body from a data structure, we can provide numerous adapters for serializing content. For example serializing a data structure element into JSON, multipart form, JSON Schema or MSON etc.

DA0AC136-471D-45F8-ACC3-63B095835873

Components:

  • Serialize should return a parse result. They should be able to emit warnings and errors. This is a problem in the current design, for example serialising API Blueprint or OpenAPI can result in loss of information which isn't clear to the end user during conversion. Loss of information should create warnings, and we should have source map information where possible.

    "This line from my OAS 2 document cannot be serialized into API Blueprint because X".

  • Other parsers such as OAS 2 and OAS 3 should make use of the registered serializers when generating message bodies (where generateMessageBody option is enabled). The parser consumer may load JSON if they want to generate JSON bodies, or custom adapters such as yaml, messagepack, protobuf etc.

  • Extra note: "Console" functionality in documentation can provide a UI for entering the data structures, headers, parameters which can update value in a copy of the data structure elements. It can use these adapters to generate the message body so it does not have to duplicate the efforts for each content-type.

Here's a rough idea of how this might look and how you can use these
adapters:

const { Namespace } = require('@apielements/core');
const jsonAdapter = require('@apielements/json');
const jsonSchemaAdapter = require('@apielements/json-schema');
const formURLEncodedAdapter = require('@apielements/form-urlencoded');
const multipartFormAdapter = require('@apielements/multipart-form');
const msonAdapter = require('@apielements/mson');

const namespace = new Namespace();
namespace.use(jsonAdapter);
namespace.use(jsonSchemaAdapter);
namespace.use(formURLEncodedAdapter);
namespace.use(multipartFormAdapter);
namespace.use(msonAdapter);

describe('JSON Adapter', () => {
  it('can convert data structure to JSON', async () => {
    const structure = new namespace.elements.Object({ name: 'Doe' })'
    const parseResult = await namespace.serialize({
      input: structure,
      mediaType: 'application/json',
    });

    assert.equal(parseResult.value.toValue()), '{"name": "Doe"}');
  });
});

describe('JSON Schema Adapter', () => {
  it('can convert data structure to JSON', async () => {
    const structure = new namespace.elements.Object({ name: 'Doe' })'
    const parseResult = await namespace.serialize({
      input: structure,
      mediaType: 'application/schema+json',
    });

    assert.equal(parseResult.value.toValue()), '{"type": "object", "properties": { "name": { "type": "string" }}}');
  });
});

describe('URL Form Adapter', () => {
  it('can convert data structure to multipart form', async () => {
    const structure = new namespace.elements.Object({ name: 'Doe' })'
    const parseResult = await namespace.serialize({
      input: structure,
      mediaType: 'application/x-www-form-urlencoded',
    });

    assert.equal(parseResult.value.toValue()), 'name=doe');
  });
});

describe('Multipart Form Adapter', () => {
  it('can convert data structure to multipart form', async () => {
    const structure = new namespace.elements.Object({ name: 'Doe' })'
    const parseResult = await namespace.serialize({
      input: structure,
      mediaType: 'multipart/formdata; BOUNDARY=foo',
    });

    assert.equal(parseResult.value.toValue()), `--foo\r\nContent-Disposition: form-data; name="name"\r\n\r\nDoe\r\n\r\n--foo--\r\n');
  });
});

describe('MSON Adapter', () => {
  it('can convert data structure to MSON', async () => {
    const structure = new namespace.elements.Object({ name: 'Doe' })'
    const parseResult = await namespace.serialize({
      input: structure,
      mediaType: 'text/mson+markdown',
    });

    assert.equal(parseResult.value.toValue()), '+ name: Doe\n');
  });
});

The above interface expects that the input element is frozen so it can traverse up to the parents to resolve any references. We likely want to be able to provide a cache so that there is no duplicate efforts when resolving numerous
structures which contain shared references in the same document. Possible you can register a cache with the namespace, perhaps something like:

namespace.use(new Cache());
// or
namespace.serialize({ input, cache: new Cache() });

Adapters will be able to detect cache and utilise them to prevent redundant expensive work during serializing recursive structures:

// namespace's serialize function
async function serialize({ input, mediaType }) {
  if (id(for: input) && cache && cache has cache for id(for: input) + mediaType) {
    return cached result;
  }

  // dispatch to an adapter to handle serialize
  return await find('serialize', mediaType)(input);
}

async function serialize({ input, namespace }) {
  // exit early if found in cache
  const result = namespace.cache(forSerializer: 'application/json', input.id);
  if (result) {
    return result;
  }

  // mayor fixed pseudo/imperitive code for demo purposes: recursively go over result
  // assuming input is object element
  const object = {};

  input.forEach((value, key) => {
    // recurse serialize, it will handle cache of the value if it has ID /
    // resolving references
    const v = await namespace.serialize({ input: value, mediaType });
    // note in this example v is actually stringified JSON, we should design a
    // way around that else we'll double encode
    object[key.toValue] = v;
  });

  return JSON.stringify(object);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant