Coder Social home page Coder Social logo

airtasker / spot Goto Github PK

View Code? Open in Web Editor NEW
536.0 49.0 37.0 6.82 MB

Spot is a concise, developer-friendly way to describe your API contract.

License: Other

JavaScript 0.28% TypeScript 99.71% Batchfile 0.01%
openapi swagger typescript api-blueprint dsl json-schema openapi3

spot's Introduction

Spot

Spot ("Single Point Of Truth") is a concise, developer-friendly way to describe your API contract.

Leveraging the TypeScript syntax, it lets you describe your API and generate other API contract formats you need (OpenAPI, Swagger, JSON Schema).

You don't need to use TypeScript in your codebase to benefit from using Spot.

Example of an API definition file api.ts which defines a single POST endpoint to create a user:

import { api, endpoint, request, response, body } from "@airtasker/spot";

@api({
  name: "My API"
})
class Api {}

@endpoint({
  method: "POST",
  path: "/users"
})
class CreateUser {
  @request
  request(@body body: CreateUserRequest) {}

  @response({ status: 201 })
  response(@body body: CreateUserResponse) {}
}

interface CreateUserRequest {
  firstName: string;
  lastName: string;
}

interface CreateUserResponse {
  firstName: string;
  lastName: string;
  role: string;
}

Getting Started

Get started with writing Spot contracts - Spot Guide

For all available syntax, see Spot Syntax

Installation

With yarn installed and initialized add @airtasker/spot to your project:

yarn add @airtasker/spot

You can pass the definition above to a generator by simply running:

npx @airtasker/spot generate --contract api.ts

Why we built Spot

At first glance, you may wonder why we bothered building Spot. Why not use OpenAPI (formely known as Swagger) to describe your API?

At the core, we built Spot because we wanted a better developer experience.

Writing contracts

OpenAPI documents are stored as YAML files, following a very specific schema. You won’t know that you used the wrong field name or forgot to wrap a type definition into a schema object unless you run a good OpenAPI linter. Most developers who aren’t intimately familiar with the OpenAPI specification end up using a visual editor such as Swagger Editor or Stoplight.

Since Spot leverages the TypeScript syntax, all you need is to write valid TypeScript code. Your editor will immediately tell you when your code is invalid. It will tell you what’s missing, and you even get autocomplete for free. We could have picked any other typed language—TypeScript just happened to be one of the most concise and ubiquitous for us.

Reviewing contracts

We believe that API contracts should be checked into Git, or whichever code versioning system you use. In addition, API contracts should be systematically peer reviewed. It’s far too easy for a backend engineer to incorrectly assume what client engineers expect from an endpoint.

Because of their complex nested structure and the richness of the OpenAPI specification, OpenAPI documents can be difficult to review in a pull request. They’re great for machines, but not always for humans.

Spot aims to be as human-readable as possible. We’ve seen developers become a lot more engaged in discussions on pull requests for Spot contracts, compared to our previous OpenAPI documents.

Interoperability with various formats

Depending on what you're trying to achieve (testing, documentation, client code generation…), you'll find tools that only work with OpenAPI 2 (Swagger), and newer tools that only support OpenAPI 3. You may also find tools for a different API ecosystem such as JSON Schema or API Blueprint.

We built Spot with this in mind. Instead of having to juggle various API format converters, Spot can generate every major API document format. This is why we called it "Single Point Of Truth".

oclif Version CircleCI Downloads/week License

Usage

To get started and set up an API declaration in the current directory, run:

npx @airtasker/spot init

You can then run a generator with:

npx @airtasker/spot generate --contract api.ts

In Memory Usage

import { Spot } from "@airtasker/spot";

const contract = Spot.parseContract("./api.ts")
const openApi = Spot.OpenApi3.generateOpenAPI3(contract);

console.log(openApi);

/*
{
  openapi: '3.0.2',
  info: { title: 'my-api', description: undefined, version: '0.0.0' },
  paths: { '/users': { post: [Object] } },
  components: {
    schemas: { CreateUserRequest: [Object], CreateUserResponse: [Object] },
    securitySchemes: undefined
  },
  security: undefined
}
*/

Commands

spot checksum SPOT_CONTRACT

Generate a checksum for a Spot contract

USAGE
  $ spot checksum SPOT_CONTRACT

ARGUMENTS
  SPOT_CONTRACT  path to Spot contract

OPTIONS
  -h, --help  show CLI help

EXAMPLE
  $ spot checksum api.ts

See code: build/cli/src/commands/checksum.js

spot docs SPOT_CONTRACT

Preview Spot contract as OpenAPI3 documentation. The documentation server will start on http://localhost:8080.

USAGE
  $ spot docs SPOT_CONTRACT

ARGUMENTS
  SPOT_CONTRACT  path to Spot contract

OPTIONS
  -h, --help       show CLI help
  -p, --port=port  [default: 8080] Documentation server port

EXAMPLE
  $ spot docs api.ts

See code: build/cli/src/commands/docs.js

spot generate

Runs a generator on an API. Used to produce client libraries, server boilerplates and well-known API contract formats such as OpenAPI.

USAGE
  $ spot generate

OPTIONS
  -c, --contract=contract    (required) Path to a TypeScript Contract definition
  -g, --generator=generator  Generator to run
  -h, --help                 show CLI help
  -l, --language=language    Language to generate
  -o, --out=out              Directory in which to output generated files

EXAMPLE
  $ spot generate --contract api.ts --language yaml --generator openapi3 --out output/

See code: build/cli/src/commands/generate.js

spot help [COMMAND]

display help for spot

USAGE
  $ spot help [COMMAND]

ARGUMENTS
  COMMAND  command to show help for

OPTIONS
  --all  see all commands in CLI

See code: @oclif/plugin-help

spot init

Generates the boilerplate for an API.

USAGE
  $ spot init

OPTIONS
  -h, --help  show CLI help

EXAMPLE
  $ spot init
  Generated the following files:
  - api.ts
  - tsconfig.json
  - package.json

See code: build/cli/src/commands/init.js

spot lint SPOT_CONTRACT

Lint a Spot contract

USAGE
  $ spot lint SPOT_CONTRACT

ARGUMENTS
  SPOT_CONTRACT  path to Spot contract

OPTIONS
  -h, --help                                                     show CLI help
  --has-discriminator=(error|warn|off)                           Setting for has-discriminator
  --has-request-payload=(error|warn|off)                         Setting for has-request-payload
  --has-response=(error|warn|off)                                Setting for has-response
  --has-response-payload=(error|warn|off)                        Setting for has-response-payload
  --no-inline-objects-within-unions=(error|warn|off)             Setting for no-inline-objects-within-unions
  --no-nullable-arrays=(error|warn|off)                          Setting for no-nullable-arrays
  --no-nullable-fields-within-request-bodies=(error|warn|off)    Setting for no-nullable-fields-within-request-bodies
  --no-omittable-fields-within-response-bodies=(error|warn|off)  Setting for no-omittable-fields-within-response-bodies
  --no-trailing-forward-slash=(error|warn|off)                   Setting for no-trailing-forward-slash

EXAMPLES
  $ spot lint api.ts
  $ spot lint --has-descriminator=error
  $ spot lint --no-nullable-arrays=off

See code: build/cli/src/commands/lint.js

spot mock SPOT_CONTRACT

Run a mock server based on a Spot contract

USAGE
  $ spot mock SPOT_CONTRACT

ARGUMENTS
  SPOT_CONTRACT  path to Spot contract

OPTIONS
  -h, --help                                   show CLI help
  -p, --port=port                              (required) [default: 3010] Port on which to run the mock server
  --pathPrefix=pathPrefix                      Prefix to prepend to each endpoint path

  --proxyBaseUrl=proxyBaseUrl                  If set, the server will act as a proxy and fetch data from the given
                                               remote server instead of mocking it

  --proxyFallbackBaseUrl=proxyFallbackBaseUrl  Like proxyBaseUrl, except used when the requested API does not match
                                               defined SPOT contract. If unset, 404 will always be returned.

  --proxyMockBaseUrl=proxyMockBaseUrl          Like proxyBaseUrl, except used to proxy draft endpoints instead of
                                               returning mocked responses.

EXAMPLE
  $ spot mock api.ts

See code: build/cli/src/commands/mock.js

spot validate SPOT_CONTRACT

Validate a Spot contract

USAGE
  $ spot validate SPOT_CONTRACT

ARGUMENTS
  SPOT_CONTRACT  path to Spot contract

OPTIONS
  -h, --help  show CLI help

EXAMPLE
  $ spot validate api.ts

See code: build/cli/src/commands/validate.js

spot validation-server SPOT_CONTRACT

Start the spot contract validation server

USAGE
  $ spot validation-server SPOT_CONTRACT

ARGUMENTS
  SPOT_CONTRACT  path to Spot contract

OPTIONS
  -h, --help       show CLI help
  -p, --port=port  [default: 5907] The port where application will be available

EXAMPLE
  $ spot validation-server api.ts

See code: build/cli/src/commands/validation-server.js

spot's People

Contributors

alephao avatar bhageena avatar clintonfeng-airtasker avatar committedteadrinker avatar danieltheodosius avatar dependabot-preview[bot] avatar dependabot[bot] avatar dhmw avatar flavray avatar fwouts avatar harryparkdotio avatar jankuca avatar kastet avatar lemattma avatar lfportal avatar mahirk avatar maxzirps avatar medric avatar oben01 avatar recardon avatar theringleman avatar thodin3 avatar timdawborn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spot's Issues

Ability to extend class with some methods

It would be great to have ability to inherit some base behavior.
For example, when each HTTP endpoint can return 400 and 500 error with a known schema.

Now those methods are not generated in openapi file.

Example:

class BaseEndpoint {

    @response({ status: 400 })
    badRequest(@body body: BadRequestError) { }

    @response({ status: 500 })
    internalError(@body body: InternalError) { }
}

@endpoint({
    method: "POST",
    path: "/a"
})
class EndpointA extends BaseEndpoint {
    @request
    request(@body body: ReqA) { }
}

@endpoint({
    method: "POST",
    path: "/b"
})
class EndpointB extends BaseEndpoint {
    @request
    request(@body body: ReqB) { }
}

Allowing to generate a components file only

Is your feature request related to a problem? Please describe.
I would like to have a gradual migration to Spot. I would like to start describe components using Spot and then reference them from my existing OpenAPI definition file.

Describe the solution you'd like
I would like to have the required @Api specification removed and also have spot generate components definitions for interfaces exported (or a different way to mark these - as we can't decorate interfaces)

Describe alternatives you've considered
An alternative is to use a dummy @api with a dummy @endpoint and just ignore these sections in the output

Add conversion of examples

Create the conversion of pathParams examples between SPOTs native contract representation to some standard one

I am using JSON.parse now and enforce the quotes.
Are the examples automatically written to the result or did I forget to add this somewhere?

The contract parsing phase will create the Contract object which is passed to one of the generators, which performs the conversion between SPOT's native contract representation to some standard one. For example the openapi3 generator. Feel free to add the example conversion for the openapi generators in this PR. Otherwise we can do that in a separate PR.

@maxzirps let me know if you want to add that in, otherwise we can merge this PR as is.

Originally posted by @lfportal in #916 (comment)

Add examples for request and response body

Is your feature request related to a problem? Please describe.
After #916 I was looking to add examples for the request and response body objects i.e.non parameter objects.

Describe the solution you'd like
The OpenAPI spec defines the examples for this like:

examples:
          Jessica:
            value:
              id: 10
              name: Jessica Smith
          Ron:
            value:
              id: 20
              name: Ron Stewart

This is different than how we define examples for parameters. In the case of a body, the example is generated for the set of properties in a given schema.

Thinking about ways to satisfy that for a given body, there are two ways which this could be done, they both rely on the developer adding an example for every property and match the example names. I've provided only one of the ways below:

interface CreateUserRequest {
  /**
   * @example property-example-one
   * "Angela"
   * @example property-example-two
   * "Meena"
   */
  firstName: string;
  /**
   * @example property-example-one
   * "Kapoor"
   * @example property-example-two
   * "Shah"
   */
  lastName: string;
}

both of these would generate

examples:
          property-example-one:
            value:
              firstName: Angela
              lastName: Kapoor
          property-example-one:
            value:
              firstName: Meena
              lastName: Shah

Output file respecting input file name

The output file currently defaults to api.json or api.yaml regardless of the input ts file's name for

Desired default behaviour would be:
input_file_name.ts outputs input_file_name.{extension}

Support enhanced security headers

Is your feature request related to a problem? Please describe.
An organization may have one more security strategies, that also may be specific to a certain flow. These may also extend to various types such as api key, openID or OAuth. In my case, I would like to define 2 security schemes, apply one globally, but apply the second selectively.

Describe the solution you'd like

Smaller Fixes

  1. Allow multiple security schemes to be defined with custom names
  2. Allow security headers for individual requests

Longer Term

  1. Allow for more authentication flows and permission boundaries

Describe alternatives you've considered
Unfortunately there was no work around to this.

Additional context
Flows: https://swagger.io/docs/specification/authentication/

Support generics

I also noticed the following which capability matrix:
union type -> ✅
inheritance type -> ✅
recursive -> 🔴
generics -> 🔴

Would love to hear any thoughts you have on implementing these.

Originally posted by @mahirk in #1034 (comment)

Generate OpenAPI with various API formats (JSON:API, HAL, JSON-LD)

Is your feature request related to a problem? Please describe.

We're implementing JSON:API across our API's. It would be great to use spot and to define in which format this should be converted, possibly multiple formats.

https://api-platform.com/docs/core/content-negotiation/#content-negotiation

Describe the solution you'd like

Leave spots contract description and model as is. The OpenAPI spec will be generated based on a specified format.

Support recursive types

I also noticed the following which capability matrix:
union type -> ✅
inheritance type -> ✅
recursive -> 🔴
generics -> 🔴

Would love to hear any thoughts you have on implementing these.

Originally posted by @mahirk in #1034 (comment)

Unable to use Map type

Describe the bug
I am trying to declare a field of Map type, like so:
labels: Map<string, string>;

To Reproduce
See above.
Expected behavior
Expected map type to show up in openapi schema.

Screenshots

Error:

Error: Map type is not supported
    at getTargetDeclarationFromTypeReference (~/.npm/_npx/1/lib/node_modules/@airtasker/spot/build/lib/src/parsers/type-parser.js:447:27)
    at parseTypeReference (~/.npm/_npx/1/lib/node_modules/@airtasker/spot/build/lib/src/parsers/type-parser.js:78:31)
    at parseType (~/.npm/_npx/1/lib/node_modules/@airtasker/spot/build/lib/src/parsers/type-parser.js:17:16)
    at parseInterfaceDeclaration (~/.npm/_npx/1/lib/node_modules/@airtasker/spot/build/lib/src/parsers/type-parser.js:289:32)
    at parseTypeReference (~/.npm/_npx/1/lib/node_modules/@airtasker/spot/build/lib/src/parsers/type-parser.js:151:42)
    at parseType (~/.npm/_npx/1/lib/node_modules/@airtasker/spot/build/lib/src/parsers/type-parser.js:17:16)
    at parseInterfaceDeclaration (~/.npm/_npx/1/lib/node_modules/@airtasker/spot/build/lib/src/parsers/type-parser.js:289:32)
    at parseTypeReference (~/.npm/_npx/1/lib/node_modules/@airtasker/spot/build/lib/src/parsers/type-parser.js:151:42)
    at Object.parseType (~/.npm/_npx/1/lib/node_modules/@airtasker/spot/build/lib/src/parsers/type-parser.js:17:16)
    at Object.parseBody (~/.npm/_npx/1/lib/node_modules/@airtasker/spot/build/lib/src/parsers/body-parser.js:15:38)

Desktop (please complete the following information):

  • OS: Ububtu 18.04
  • Yarn version: 1.22.4
  • Node version:13.12.0
  • Spot version: 1.1.2

Additional context
I don't know typescript much and I am not sure if the syntax I am using is correct or not. The error is very clear about Map type not being supported but I am wondering if there are any plans to do it in future. I am also curious as to how other people are defining Maps as it is rather a common type.

I also understand that this issue is more appropriate for a forum post but I didn't find any reference to such forum in the docs.

Allow types and interfaces in defining the request details

Is your feature request related to a problem? Please describe.
In using the json-schema generators I noticed that it does not convert out the request query, params or header into a JSON Schema. Further discovered that I am unable to describe the header and path params as follows:

    request(
        @headers
        headers: SomeTypeHere,
        @pathParams
        pathParams: SomeOtherTypeHere
    ) {}

instead the request would have to be defined as follows:

    request(
        @headers
        headers: { someString: string; },
        @pathParams
        pathParams: { someOtherString: string; }
    ) {}

In investigating the contract, I also noticed the lack of the above request details in the types object of the contract.

Describe the solution you'd like
Remove getParameterTypeAsTypeLiteralOrThrow(parameter); to permit interfaces to be used. After investigating the AST, I am looking into how the TypeReference can be used to retrieve the PropertySignatures.

The method can be replaced as getParameterPropertySignatures and it can handle either getting the signatures from them TypeLiteral or can use the TypeReference.Symbol to retrieve the Declarations and then the PropertySignatures like getTargetDeclarationFromTypeReference

Sample AST: https://ts-ast-viewer.com/#code/FASwtgDg9gTgLgAgN4IIYRAGgQUwHYAm0Iec2MOAjgK44DOZCFd0edO2ARlAQJ7YRUcABYAFVDFRg62dgGNqMEHF4AJHKgI4Y2YRq0w6CAL4IAZjChgEAcgACqEPFR0A1toD0LKHBsBuYFBSbTNUORwEACUqWgYAZRw5CkQkYAR02wAPAFp0EGy6EABzPBIi7PdeGwAuBAYlPCKA40C5ABsXIwBxHDhomno4OKkINojUjIQ7CgGGNIyZ2LgACnnJ9Ls9TW06NfWtgzpa-qWEpN7MPYBKZBa96fpWdmWUBiFqI4QAJgAGH5MrntmE8cMs7Nw+AgIbxanErDhot42DgAEI8Xg3JAtO7tToIHp9GKDYaQMZfZD3RaDIFEhirdYZTb6HZ7SYHHa1CYMyY2HJ5ArFUqNCo4Kq1eplALckyXSaYu6TB5I56vODvT6-f7GQGTYFQZFg6FQ9Gw+GIkFovjy4B3IA

Location

const headerTypeLiteral = getParameterTypeAsTypeLiteralOrThrow(parameter);
and
const pathParamTypeLiteral = getParameterTypeAsTypeLiteralOrThrow(parameter);

Handle Multiple JSDoc Correctly

Describe the bug
SPOT generate fail when multiple JSDoc occurred before any type definition.

To Reproduce
Below is the api.ts file

// api.ts
import { api, body, endpoint, request, response, String } from "@airtasker/spot";

@api({ name: "my-api" })
class Api {}

@endpoint({
  method: "POST",
  path: "/users"
})
class CreateUser {
  @request
  request(
    @body body: CreateUserRequest
  ) {}
}


/**
 * Some Generaal Documentation
 */

/**
 * Some Interface Documentation
 */
interface CreateUserRequest {
  firstName: String;
}

Running command:

> spot generate -c api.ts -g openapi2 -l yaml -o .
> spot generate -c api.ts -g openapi3 -l yaml -o .
> spot generate -c api.ts -g json-schema -l json -o .

Will result in error:

Error: expected at most 1 jsDoc node, got 2

Expected behavior
The generation should success and produce the expected result.

Screenshots
N/A

Desktop (please complete the following information):
@airtasker/spot/1.2.0 darwin-x64 node-v12.13.0

Additional context

Is @body decorator can be removed?

For me, @body body looks a bit strange and it would be great to not use @body decorator because we already have field body and can detect by name that this is a body.

Allow generation of servers and base paths in `api`

Is your feature request related to a problem? Please describe.
OpenAPI3 allows defining servers (https://swagger.io/docs/specification/api-host-and-base-path/) which can help define the scheme, description, and variables which form a server. This request is to enable spot to generate that for the OpenAPI Spec.

Describe the solution you'd like
Allow the definition of server inside the @api module of spot, with the following conversions:

@api({
  name: "My API",
  version: '1',
})
class Api {
  @servers
  server(
    @url
    url: 'https://{environment}.example.com/v2',
    @variables
    variables: {
      /** API description for server variables */
      environment: 'api' | 'api.dev' | 'api.staging'
    }
  ) {}
}
servers:
  - url: https://{environment}.example.com/v2
    variables:
      environment:
        default: api 
        description: API description for environment variables
        enum:
          - api         
          - api.dev  
          - api.staging

Describe alternatives you've considered
Haven't thought of any other designs at this moment, likely will need to think about if it would be useful to add (a) a default value, which could be the first value? and (b) multiple servers.

no ability to add version to doc

Current behavior:

Currently there is no ability to be able to add a version to the doc (aka setting the version in the info section of open api spec).

Desired behavior:

To have the option to be able to set the version.
A potential solution maybe in the @api component have a info object which has a version field that can be set e.g

@api({
  name: "My API",
  info: {
     version: 0.2.0
  }
})

Steps to reproduce:

N/A

Switch Speccy to Spectral

Hey I'm the original author of Speccy. I'm doing the rounds letting people know they should switch to Spectral. Speccy is not getting much love these days, and Spectral is like its considerably more mature and advanced replacement.

You can use the TypeScript API directly instead of running things through the CLI too if you like. Seems like it might be up your alley.

Enhance TypeScript support

Describe the bug
Cannot use template strings or variables inside decorators because the parsers are looking for NumberLiteral and StringLiteral initializers.
Edit: Cannot use TypeScript native types such as Pick, Omit, Exclude.
Edit: Cannot use intersection types.

To Reproduce
Steps to reproduce the behavior:

  • Using http-status-codes for the status in the response decorator throws an error: "Expected to find an initializer of kind 'NumericLiteral'."
  • Using any type of variable for the paths or using template strings inside the endpoints decorator results in a similar error: "Expected to find an initializer of kind 'StringLiteral'."

Expected behavior
I should be able to use variables/references and template strings inside the decorator functions rather than hard coding everything.
Edit: I should be able to use Pick, Omit, Exclude, and other TypeScript native types.
Edit: I should be able to use intersection types in order to reduce the amount of code I would otherwise have to duplicate.

Screenshots
image

Desktop (please complete the following information):

  • OS: macOS Catalina (version 10.15.3)
  • NPM version 6.13.7
  • Node version 13.2.0
  • Spot version 1.0.1

Allow usage for indexed types on body elements

Is your feature request related to a problem? Please describe.
The JSON specification allows an indexed type by defining it as an object which can have any number of strings in it, for example:
the following type definition:

metadata?: {
        [key: string]: string;
    };

Becomes:

        "metadata": {
          "additionalProperties": {
            "type": "string"
          },
          "type": "object"
        },

Describe the solution you'd like
The following spec fails:

class Api {}

@endpoint({
  method: "POST",
  path: "/users/:someOtherString"
})
class CreateUser {
  @request
  request(
    @headers
    headers: HeaderAlias,
    @pathParams
    pathParams: PathParams,
    @body body: CreateUserRequest
  ) {}

  @response({ status: 201 })
  successResponse(@body body: CreateUserResponse) {}
}


interface CreateUserResponse {
  firstName: string;
  lastName: string;
  role: string;
  [key: string]: string
}

with the error as indexed types are not allowed.

The proposal would be to update the type-parser to allow indexed types and define them using the guidelines provided above.

Implement an API Blueprint generator

There's currently no generator for API Blueprint. We may want to implement one, as there are a few useful tools in the API Blueprint ecosystem.

Support specific number types (e.g. int32) instead of just "number"

This could be done by exposing types from lib.ts such as:

export type Int32 = number;

and then importing them into your contract definition:

import { Int32 } from "@zenclabs/api";

type UserId = Int32;

From the parser's perspective, we just need to make sure the type is called Int32. It doesn't really matter that it was imported from @zenclabs/api.

Missing meta information

Is your feature request related to a problem? Please describe.

I am trying to convert an existing OpenApi file to spot. When trying to do that I am missing most of the meta-information parameters. For example:

  • description for parameters and queries
  • summary for the endpoint
  • example for parameters and endpoints
  • other content types than application/json e.g. application/xml for responses
  • description for @interfaces

Describe the solution you'd like

Add these properties to the corresponding annotations (or in the case of annotations for interface types add new annotations.

Describe alternatives you've considered

Another option would be to add something like a global overwrite, that could add these properties (I guess by giving up the type-safety of TS).

As an example:

@api({
    name: "API",
})

@globalUnsafeOverrides({
  "openapi3": {
    "info": {
      "title": "Hello API"
    }
  }
})

would give this openapi3 doc:

{
  "openapi": "3.0.2",
  "info": {
    "title": "API",
    "version": "0.0.0"
  }

via sth. like that:

const finalJson = {
  ...api,
  ...globalUnsaveOverrides
}

Or the other way around (which would preserve the properties set in the annotated version)

const finalJson = {
  ...globalUnsaveOverrides,
  ...api
}

As an alternative for description for interface and its properties, I wonder if it is possible (and a good idea) to use e.g. surrounding comments.

// The user type
interface User {
  // the unique id
  id: string
}

would give

"components": {
    "schemas": {
      "User": {
        "type": "object",
        "description": "The user type",
        "properties": {
          "id": {
            "type": "string"
          },
         "description": "the unique id"
        },
        "required": [
          "id"
        ]
      }
    }
  }

EDIT: I found aa517d3 for the @endpoint annotation and wonder why it got removed?

indexed access types

intro

indexed access types allow for referencing properties which live inside of an interface. This allows a single model to be defined for an entity, but reference the property type without having to redeclare it. This is especially useful for keys like id, where it may be used quite often in places like pathParams.

example

interface Example {
  id: string;
  exampleProperty: boolean;
}

type ExampleId = Example['id'];

edge case

nested indexed access types are allowed

interface NestedExample {
  id: string;
}

interface Example {
  property: {
    key: string;
  };
  nested: NestedExample;
}

// both are valid
type ExamplePropertyKey = Example['property']['key'];
type NestedExampleId = Example['nested']['id'];

PR: #407

`spot generate` fails silently when provided an invalid API

I started defining an API and had mistakenly copy-pasted @api() in two places. When I ran spot generate -a my-api.ts, it failed silently (no error message). I was confused for a few minutes until I thought about running spot validate, which told me there was one too many @api() calls.

This could have been avoided easily by printing an error message when the API provided to generate isn't valid.

Add more documentation

Hi There,

Please, can you add some more documentation about how to setup the development process with your project?
I understand that it's something very obvious for node-js developers but not for someone who came from another dev-language.
Personally, I want to add my custom export-template and I'm struggling at the moment once I change something how to make test execution and check the results. Even two lines... please :)
Or I saw the express server. Can it show the transformation results?

I checked the codebase and I want to say that it really wonders me how elegant your solution is with converting one format to another!! Absolutely simple and amazing!!

Thank you,
Andrey.

OpenAPI object access in memory

Is there a way to generate the document in memory? I'd like to use my OAS document to drive the services/tools I'm building. It would be really cool if I could export an OpenAPI document from a typescript file and import it to use programmatically. For example:

/* api.ts */
import {api, endpoint, request, response, body} from '@airtasker/spot'

@api({
    name: "My API"
})
export default class Api {}

@endpoint({
    method: "POST",
    path: "/users"
})
class CreateUser {
    @request
    request(@body body: CreateUserRequest) {}

    @response({ status: 201 })
    response(@body body: CreateUserResponse) {}
}

interface CreateUserRequest {
    firstName: string;
    lastName: string;
}

interface CreateUserResponse {
    firstName: string;
    lastName: string;
    role: string;
}
/* some-file.ts */
import Api from './api.ts'

/* Access OpenAPI document as JSON here */
console.log(Api.openapi) // expected output: 3.0.0

No docs command

Current behavior:

spot docs api.ts
Error: command docs not found

Desired behavior:

Preview Spot contract as OpenAPI3 documentation at localhost:8080

Steps to reproduce:

  1. Install spot globally npm i -g @airtasker/spot
  2. Copy example api.ts file from README.md to project directory
  3. run spot docs api.ts

Standardised and required naming for SPOT contracts

Hi,

Currently, SPOT does not require a contract to have any standardised name on it. I'm just raising this up to allow any discussion in this. I'm not sure whether SPOT allows empty string in it as well (it should not).

Thanks!

Support aliased imports in TypeScript DSL parser

An example is:

import { User as U } from './models';

interface UserResponse {
  user: U;
}

This is currently not supported. We need to add a few smarts to parser.ts so it understands that User has been renamed to U.

[Proposal] Add support to parameter serialization rules

Introduction

There is no way to specify parameter serialization rules defined by openapi:

https://swagger.io/docs/specification/serialization/#query
http://spec.openapis.org/oas/v3.0.2#parameterExplode

Proposed solution

The proposed solution is to allow the user to specify a global configuration for parameter serialization.

Global config

Currently, spot doesn't provide a way to specify global configurations or overrides for default openapi rules, this proposal includes adding the class decorator @config which would be used in conjunction with the @api decorator. I'll discuss it's usage in the next sections.

Specifying Parameters Serialization

To specify the parameter serialization rules, we would use an object for the key ** paramSerializationStrategy**, this object can have configurations for the serialization of path, query, header, or cookie.

The query configuration, will be composed by the keys array and object, and each of them have a limited set of possible values:

  • array: ampersand, ampersandEscaped, comma, pipe
  • object: ampersand, comma, deep

Example

Here is an example of the configuration, the input, and the output.

Config

@api({ name: "My API" })
@config({
  paramSerializationStrategy: { 
    query: {
      array: "comma";
      object: "deep";
    }
  }
})
class MyApi {}

Input

// ...
request(
  @queryParams
  queryParams: {
    some_object: MyObject;
    some_array?: string[];
  }
  //...
) {}

Output

- in: query
  name: some_object
  required: true
  schema:
    $ref: '#...MyObject'
  style: deepObject # This was added due to the global rule "object: deep"
  explode: true # This was added due to the global rule "object: deep"

- in: query
  name: some_array
  required: false
  schema:
    type: array
    items:
       type: string
  style: form # This was added due to the global rule "array: comma"
  explode: false # This was added due to the global rule "array: comma"

Support for external definitions and better error messages

Describe the bug
I was trying to include and external definition (geojson) but I wasn't able to compile. This is a bit of a feature request + bug reporting.

The lack of support for many TS features as in #704 is a pity, but the inability of using external definitions alredy defined in other typings is a bit of a blocker. There should be a way of including entities/models already defined by some libraries, or at least by a JSON Schema. E.g. if I build an API upon an already existing platform that provides its own types, or using some convention for objects already defined by a standard.

In my case I only added @types/geojson, which are the type definitions for the well known GeoJSON Standard. It's just types, no classes.

To Reproduce

Here is the code

import {
  api,
  queryParams,
  body,
  endpoint,
  request,
  response,
  String,
} from "@airtasker/spot";
import { FeatureCollection, Point } from "geojson";

@api({ name: "my-api" })
class Api {}

@endpoint({
  method: "POST",
  path: "/users",
})
class CreateUser {
  @request
  request(@body body: CreateUserRequest) {}

  @response({ status: 201 })
  successfulResponse(@body body: CreateUserResponse) {}
}

@endpoint({
  method: "GET",
  path: "/users/positions",
})
class GetUserPositions {
  @request
  request(@queryParams query: { search?: String }) {}

  @response({ status: 200 })
  successfulResponse(@body body: UserFeatureCollection) {}
}

interface CreateUserRequest {
  firstName: String;
  lastName: String;
}

interface CreateUserResponse {
  firstName: String;
  lastName: String;
  role: String;
}

type UserFeatureCollection = FeatureCollection<Point, CreateUserResponse>;

Launched with:
npx @airtasker/spot generate --contract=api.ts --generator=openapi3 -o=openapi --language=json
Debugging this is not easy. The error does not report which line failed:

Error: expected a type alias or interface declaration

Expected behavior
Get Openapi 3 json document, or get a more precise explanation of what caused the fault.

Desktop (please complete the following information):

  • OS: Windows 10
  • Yarn version 1.22.4
  • Node 12.13.0
  • Spot version 1.1.2

Additional context
Project started with spot init

Conditional Types for Headers and/or Parameters

We will be creating our API spec, hopefully created by spot. However (and this is an example), we have headers that can either accept a cookie or authentication information. This is legacy code and not ideal but, regardless, would need to be covered in the specification.

If this is not possible, what workarounds exist. Here is an example of what we need:

@request
    request(
        @headers
            headers: {
            "Accept": "*/*"
            "Host": String,
            {
                "Cookie": String
            } ||
            {
                "Authorisation": String
            }
        }
    ) {}

Add online playground for quick Spot evaluation

Is your feature request related to a problem? Please describe.
Before trying out Spot on a project I'd like to quickly evaluate if it could generate the schema I'm expecting, therefore, instead of creating a new project, add npm dependencies and getting familiar with the shell commands, I'd like to have a quick try of Spot from an online playground.

Describe the solution you'd like
I'd like to be able to go to a webpage, the Spot playground, and type in my typescript schema in a text input field. I should be able to hit a 'generate' button and see its generated output in another text field.

Describe alternatives you've considered
These are similar projects using similar playgrounds, which might be used as reference:
https://app.quicktype.io/
https://graphql-code-generator.com/

Additional context
I just finished listening to the great talk from @fwouts at 'apidays Australia' and he suggested an issue could be added for this feature request.

Using as a dependency in an oclif plugin errors out

Using as a dependency in an oclif plugin errors out as:
Cannot find module '@airtasker/spot' or its corresponding type **declarations.**
on command invocation.

Steps to reproduce the behavior:
1.Create an oclif plugin
2. Add @airtasker/spot as a dependency
3. Use createProjectFromExistingSourceFile or validateProject
4. Add the plugin in another oclif project

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.