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

Offer synchronous initializer for case where spec is already in hand #1292

Closed
matthewadams opened this issue Apr 18, 2018 · 2 comments
Closed

Comments

@matthewadams
Copy link

Currently, the top-level Swagger() function accepts a spec option, which can be used if the Open API Specification document has already been retrieved. In that case, there is no need for asynchronous behavior, as there is no I/O taking place while parsing the spec.

Due to circumstances beyond our control, we are required to download the spec separately from the location at which we invoke the service provider's endpoints, and we'd like to construct our own classes (that delegate to the Swagger client) by using the spec in the classes' constructor. Without synchronous Swagger client initialization, we are required to instead implement an asynchronous initialization pattern where a user instantiates a class synchronously (because constructors are synchronous), then calls an async init() function. This pattern looks like the following.

Note: this uses Node.js on the server side.

class AbstractSwaggerEndpoint {
  constructor(spec) { // subclass provides loaded spec by calling super(spec)
    this._spec = spec
    this._initialized = false
  }

  // instance method inside class AbstractSwaggerEndpoint
  async init () {
    if (this._initialized) return this

    // return from static cache if client has already been built
    this._client = AbstractSwaggerEndpoint.CLIENTS[this.constructor.name]
    if (this._client) {
      this._initialized = true
      return this
    }

    // asynchronously build then cache swagger client
    this._client = AbstractSwaggerEndpoint.CLIENTS[this.constructor.name] = await Swagger({
      spec: this._spec
    })

    this._initialized = true
    return this
  }

  // ...
}

// static cache; key is subclass name, value is swagger client
AbstractSwaggerEndpoint.CLIENTS = {}

Then, to encapsulate users from this pattern, we offer a static factory method pattern like the following.

Subclass.new = async opts => new Subclass(opts).init()

This allows users to call await Subclass.new(opts) instead of const sub = new Subclass(opts) then await sub.init().

This is a lot of work for the scenario where the spec is already in hand. If swagger-client offered a synchronous, top-level function like Swagger.fromSpec(opts) or similar, where opts was required, at mininum, to have a spec property containing the spec as a JavaScript object, we could avoid the asynchronous initialization pattern described above.

@char0n
Copy link
Member

char0n commented Jul 2, 2020

Hi @matthewadams,

Currently, the top-level Swagger() function accepts a spec option, which can be used if the Open API Specification document has already been retrieved.

Due to some legacy reasons, instantiating SwaggerClient returns an instance locked inside a Promise, instead of an instance itself. We'll rectify this issue in next major release of SwaggerClient where we're gonna be able to do breaking changes.

In that case, there is no need for asynchronous behavior, as there is no I/O taking place while parsing the spec.

Actually there is. If you already have a spec then it can contain remote JSON References which do need to be resolved. And because of that resolution is an asynchronous operation.

This is a lot of work for the scenario where the spec is already in hand. If swagger-client offered a synchronous, top-level function like Swagger.fromSpec(opts) or similar, where opts was required, at mininum, to have a spec property containing the spec as a JavaScript object, we could avoid the asynchronous initialization pattern described above.

Actually there is a static method SwaggerClient.resolve that returns resolved specification instead of SwaggerClient instance. More about this method can be found in OpenApi Definition Resolver. But again, as I mentioned before resolution is an asynchronous operation so the method returns a Promise.

Regarding building instances of swagger-client and executing them later...maybe following code fragment will help:

const assign = require('lodash/assign');
const SwaggerClient = require('swagger-client');

function SwaggerClientSync(url, opts = {}) {
  this.url = url;
  this.opts = opts;
  return this;
}

SwaggerClientSync.prototype.init = async function init() {
  const client = await new SwaggerClient(this.url, this.opts);
  delete this.url;
  delete this.opts;
  return assign(this, client);
};

const client = new SwaggerClientSync({ url: 'http://petstore.swagger.io/v2/swagger.json' });

client.init().then(() => console.dir('initialized and resolved'));

@char0n
Copy link
Member

char0n commented Jul 23, 2020

Closing due to inactivity.

@char0n char0n closed this as completed Jul 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants