This fork is adding some extra features to allow a split of data definitions (done with) data-contract into multiple files.
The main adjustment is based the introduction of a "schemaStack" in the schema.js
That schema Stack will be passed along onParseSchema hook function. That enables us to see the relation of parent <--> child schema definitions.
API Factory NodeJS client will use this to make schema useages containing a x-sap-ddic-name property (schema elements that refer to a SAP DDIC Definition) outsourced into a central ddic.ts file.
The overall goal is to have a central ddic.ts file that contains all SAP DDIC definitions. Those then can be reused across multiple apis.
Additionally this fork contains an adjustment of the generation for enum values in schema.js
This is adjusted in schema.js and is related to the following issue scenario:
When having "number" enum values but those contain leading zeros the typescript compile will throw an error telling that "Octal literals should not be used". Example:
/**
* Activation type
* @xSAPDdicName AUTHCLASS_
* @xSAPInttype N
*/
export type AUTHCLASS_ = 01 | 02 | 10 | 00;
Our adjustment will fix this and create then the following
We solved it by removing leading zeros as well as if the value only contains zeros then not leav a single zero in place.
Our adjustment will fix this and create then the following:
/**
* Activation type
* @xSAPDdicName AUTHCLASS_
* @xSAPInttype N
*/
export type AUTHCLASS_ = 1 | 2 | 10 | 0;
Generate api via swagger scheme.
Supports OA 3.0, 2.0, JSON, yaml
Generated api module use Fetch Api or Axios to make requests.
Any questions you can ask here or in our slack(#swagger-typescript-api channel)
P.S. If you are creating the PR, please check your changes with using command npm run prepare
P.S. If you want to contribute please use the next
branch. All PRs that has target master
will be declined!
Thanks to Jetbrains for providing a free license for their excellent Webstorm IDE.
All examples you can find here
Usage: sta [options]
Usage: swagger-typescript-api [options]
Usage: swagger-typescript-api generate-templates [options]
Options:
-v, --version output the current version
-p, --path <string> path/url to swagger scheme
-o, --output <string> output path of typescript api file (default: "./")
-n, --name <string> name of output typescript api file (default: "Api.ts")
-t, --templates <string> path to folder containing templates
-d, --default-as-success use "default" response status code as success response too.
some swagger schemas use "default" response status code as success response type by default. (default: false)
-r, --responses generate additional information about request responses
also add typings for bad responses (default: false)
--union-enums generate all "enum" types as union types (T1 | T2 | TN) (default: false)
--add-readonly generate readonly properties (default: false)
--route-types generate type definitions for API routes (default: false)
--no-client do not generate an API class
--enum-names-as-values use values in 'x-enumNames' as enum values (not only as keys) (default: false)
--extract-request-params extract request params to data contract (Also combine path params and query params into one object) (default: false)
--extract-request-body extract request body type to data contract (default: false)
--extract-response-body extract response body type to data contract (default: false)
--extract-response-error extract response error type to data contract (default: false)
--modular generate separated files for http client, data contracts, and routes (default: false)
--js generate js api module with declaration file (default: false)
--module-name-index <number> determines which path index should be used for routes separation (example: GET:/fruites/getFruit -> index:0 -> moduleName -> fruites) (default: 0)
--module-name-first-tag splits routes based on the first tag (default: false)
--disableStrictSSL disabled strict SSL (default: false)
--disableProxy disabled proxy (default: false)
--axios generate axios http client (default: false)
--unwrap-response-data unwrap the data item from the response (default: false)
--disable-throw-on-error Do not throw an error when response.ok is not true (default: false)
--single-http-client Ability to send HttpClient instance to Api constructor (default: false)
--silent Output only errors to console (default: false)
--default-response <type> default type for empty response schema (default: "void")
--type-prefix <string> data contract name prefix (default: "")
--type-suffix <string> data contract name suffix (default: "")
--clean-output clean output folder before generate api. WARNING: May cause data loss (default: false)
--api-class-name <string> name of the api class (default: "Api")
--patch fix up small errors in the swagger source definition (default: false)
--debug additional information about processes inside this tool (default: false)
--another-array-type generate array types as Array<Type> (by default Type[]) (default: false)
--sort-types sort fields and types (default: false)
--extract-enums extract all enums from inline interface\type content to typescript enum construction (default: false)
-h, --help display help for command
Commands:
generate-templates Generate ".ejs" templates needed for generate api
-o, --output <string> output path of generated templates
-m, --modular generate templates needed to separate files for http client, data contracts, and routes (default: false)
--http-client <string> http client type (possible values: "fetch", "axios") (default: "fetch")
-c, --clean-output clean output folder before generate template. WARNING: May cause data loss (default: false)
-r, --rewrite rewrite content in existing templates (default: false)
--silent Output only errors to console (default: false)
-h, --help display help for command
Also you can use npx
:
npx swagger-typescript-api -p ./swagger.json -o ./src -n myApi.ts
You can use this package from nodejs:
const { generateApi, generateTemplates } = require('swagger-typescript-api');
const path = require("path");
const fs = require("fs");
/* NOTE: all fields are optional expect one of `output`, `url`, `spec` */
generateApi({
name: "MySuperbApi.ts",
// set to `false` to prevent the tool from writing to disk
output: path.resolve(process.cwd(), "./src/__generated__"),
url: 'http://api.com/swagger.json',
input: path.resolve(process.cwd(), './foo/swagger.json'),
spec: {
swagger: "2.0",
info: {
version: "1.0.0",
title: "Swagger Petstore",
},
// ...
},
templates: path.resolve(process.cwd(), './api-templates'),
httpClientType: "axios", // or "fetch"
defaultResponseAsSuccess: false,
generateClient: true,
generateRouteTypes: false,
generateResponses: true,
toJS: false,
extractRequestParams: false,
extractRequestBody: false,
unwrapResponseData: false,
prettier: { // By default prettier config is load from your project
printWidth: 120,
tabWidth: 2,
trailingComma: "all",
parser: "typescript",
},
defaultResponseType: "void",
singleHttpClient: true,
cleanOutput: false,
enumNamesAsValues: false,
moduleNameFirstTag: false,
generateUnionEnums: false,
typePrefix: '',
typeSuffix: '',
enumKeyPrefix: '',
enumKeySuffix: '',
addReadonly: false,
extractingOptions: {
requestBodySuffix: ["Payload", "Body", "Input"],
requestParamsSuffix: ["Params"],
responseBodySuffix: ["Data", "Result", "Output"],
responseErrorSuffix: ["Error", "Fail", "Fails", "ErrorData", "HttpError", "BadResponse"],
},
/** allow to generate extra files based with this extra templates, see more below */
extraTemplates: [],
anotherArrayType: false,
fixInvalidTypeNamePrefix: "Type",
fixInvalidEnumKeyPrefix: "Value",
codeGenConstructs: (constructs) => ({
...constructs,
RecordType: (key, value) => `MyRecord<key, value>`
}),
primitiveTypeConstructs: (constructs) => ({
...constructs,
string: {
'date-time': 'Date'
}
}),
hooks: {
onCreateComponent: (component) => {},
onCreateRequestParams: (rawType) => {},
onCreateRoute: (routeData) => {},
onCreateRouteName: (routeNameInfo, rawRouteInfo) => {},
onFormatRouteName: (routeInfo, templateRouteName) => {},
onFormatTypeName: (typeName, rawTypeName, schemaType) => {},
onInit: (configuration) => {},
onPreParseSchema: (originalSchema, typeName, schemaType) => {},
onParseSchema: (originalSchema, parsedSchema) => {},
onPrepareConfig: (currentConfiguration) => {},
}
})
.then(({ files, configuration }) => {
files.forEach(({ content, name }) => {
fs.writeFile(path, content);
});
})
.catch(e => console.error(e))
generateTemplates({
cleanOutput: false,
output: PATH_TO_OUTPUT_DIR,
httpClientType: "fetch",
modular: false,
silent: false,
rewrite: false,
})
This option needed for cases when you don't want to use the default swagger-typescript-api
output structure
You can create custom templates with extensions .ejs
or .eta
Templates:
api.ejs
- (generates file) Api class module (locations: /templates/default, /templates/modular)data-contracts.ejs
- (generates file) all types (data contracts) from swagger schema (locations: /templates/base)http-client.ejs
- (generates file) HttpClient class module (locations: /templates/base)procedure-call.ejs
- (subtemplate) route in Api class (locations: /templates/default, /templates/modular)route-docs.ejs
- (generates file) documentation for route in Api class (locations: /templates/base)route-name.ejs
- (subtemplate) route name for route in Api class (locations: /templates/base)route-type.ejs
- (--route-types
option) (subtemplate) (locations: /templates/base)route-types.ejs
- (--route-types
option) (subtemplate) (locations: /templates/base)data-contract-jsdoc.ejs
- (subtemplate) generates JSDOC for data contract (locations: /templates/base)
How to use it:
- copy
swagger-typescript-api
templates into your place in project- from /templates/default for single api file
- from /templates/modular for multiple api files (with
--modular
option) - from /templates/base for base templates (templates using both in default and modular)
- add
--templates PATH_TO_YOUR_TEMPLATES
option - modify ETA templates as you like
NOTE:
Eta has special directive to render template in your Eta templates - includeFile(pathToTemplate, payload)
If you want to use some default templates from this tool you can use path prefixes: @base
, @default
, @modular
.
@base
- path to base templates
@default
- path to single api file templates
@modular
- path to multiple api files templates
Examples:
- includeFile("@base/data-contracts.ejs", { ...yourData, ...it })
- includeFile("@default/api.ejs", { ...yourData, ...it })
- includeFile("@default/procedure-call.ejs", { ...yourData, ...it })
- includeFile("@modular/api.ejs", { ...yourData, ...it })
- includeFile("@modular/procedure-call.ejs", { ...yourData, ...it })
- includeFile("@base/route-docs.ejs", { ...yourData, ...it })
- includeFile("@base/route-name.ejs", { ...yourData, ...it })
- includeFile("@base/route-type.ejs", { ...yourData, ...it })
- includeFile("@base/route-types.ejs", { ...yourData, ...it })
This option should be used in cases when you have api with one global prefix like /api
Example:
GET:/api/fruits/getFruits
POST:/api/fruits/addFruits
GET:/api/vegetables/addVegetable
with --module-name-index 0
Api class will have one property api
When we change it to --module-name-index 1
then Api class have two properties fruits
and vegetables
This option will group your API operations based on their first tag - mirroring how the Swagger UI groups displayed operations
type (Record<string, any> & { name: string, path: string })[]
This thing allow you to generate extra ts\js files based on extra templates (one extra template for one ts\js file)
Example here
This command allows you to generate source templates which using with option --templates
You are able to modify TypeScript internal structs using for generating output with using generateApi
options codeGenConstructs
and primitiveTypeConstructs
.
This option has type (struct: CodeGenConstruct) => Partial<CodeGenConstruct>
.
generateApi({
// ...
codeGenConstructs: (struct) => ({
Keyword: {
Number: "number",
String: "string",
Boolean: "boolean",
Any: "any",
Void: "void",
Unknown: "unknown",
Null: "null",
Undefined: "undefined",
Object: "object",
File: "File",
Date: "Date",
Type: "type",
Enum: "enum",
Interface: "interface",
Array: "Array",
Record: "Record",
Intersection: "&",
Union: "|",
},
CodeGenKeyword: {
UtilRequiredKeys: "UtilRequiredKeys",
},
/**
* $A[] or Array<$A>
*/
ArrayType: (content) => {
if (this.anotherArrayType) {
return `Array<${content}>`;
}
return `(${content})[]`;
},
/**
* "$A"
*/
StringValue: (content) => `"${content}"`,
/**
* $A
*/
BooleanValue: (content) => `${content}`,
/**
* $A
*/
NumberValue: (content) => `${content}`,
/**
* $A
*/
NullValue: (content) => content,
/**
* $A1 | $A2
*/
UnionType: (contents) => _.join(_.uniq(contents), ` | `),
/**
* ($A1)
*/
ExpressionGroup: (content) => (content ? `(${content})` : ""),
/**
* $A1 & $A2
*/
IntersectionType: (contents) => _.join(_.uniq(contents), ` & `),
/**
* Record<$A1, $A2>
*/
RecordType: (key, value) => `Record<${key}, ${value}>`,
/**
* readonly $key?:$value
*/
TypeField: ({ readonly, key, optional, value }) =>
_.compact([readonly && "readonly ", key, optional && "?", ": ", value]).join(""),
/**
* [key: $A1]: $A2
*/
InterfaceDynamicField: (key, value) => `[key: ${key}]: ${value}`,
/**
* $A1 = $A2
*/
EnumField: (key, value) => `${key} = ${value}`,
/**
* $A0.key = $A0.value,
* $A1.key = $A1.value,
* $AN.key = $AN.value,
*/
EnumFieldsWrapper: (contents) =>
_.map(contents, ({ key, value }) => ` ${key} = ${value}`).join(",\n"),
/**
* {\n $A \n}
*/
ObjectWrapper: (content) => `{\n${content}\n}`,
/**
* /** $A *\/
*/
MultilineComment: (contents, formatFn) =>
[
...(contents.length === 1
? [`/** ${contents[0]} */`]
: ["/**", ...contents.map((content) => ` * ${content}`), " */"]),
].map((part) => `${formatFn ? formatFn(part) : part}\n`),
/**
* $A1<...$A2.join(,)>
*/
TypeWithGeneric: (typeName, genericArgs) => {
return `${typeName}${genericArgs.length ? `<${genericArgs.join(",")}>` : ""}`;
},
})
})
For example, if you need to generate output Record<string, any>
instead of object
you can do it with using following code:
generateApi({
// ...
codeGenConstructs: (struct) => ({
Keyword: {
Object: "Record<string, any>",
}
})
})
It is type mapper or translator swagger schema objects. primitiveTypeConstructs
translates type
/format
schema fields to typescript structs.
This option has type
type PrimitiveTypeStructValue =
| string
| ((schema: Record<string, any>, parser: import("./src/schema-parser/schema-parser").SchemaParser) => string);
type PrimitiveTypeStruct = Record<
"integer" | "number" | "boolean" | "object" | "file" | "string" | "array",
string | ({ $default: PrimitiveTypeStructValue } & Record<string, PrimitiveTypeStructValue>)
>
declare const primitiveTypeConstructs: (struct: PrimitiveTypeStruct) => Partial<PrimitiveTypeStruct>
generateApi({
// ...
primitiveTypeConstructs: (struct) => ({
integer: () => "number",
number: () => "number",
boolean: () => "boolean",
object: () => "object",
file: () => "File",
string: {
$default: () => "string",
/** formats */
binary: () => "File",
file: () => "File",
"date-time": () => "string",
time: () => "string",
date: () => "string",
duration: () => "string",
email: () => "string",
"idn-email": () => "string",
"idn-hostname": () => "string",
ipv4: () => "string",
ipv6: () => "string",
uuid: () => "string",
uri: () => "string",
"uri-reference": () => "string",
"uri-template": () => "string",
"json-pointer": () => "string",
"relative-json-pointer": () => "string",
regex: () => "string",
},
array: (schema, parser) => {
const content = parser.getInlineParseContent(schema.items);
return parser.safeAddNullToType(schema, `(${content})[]`);
},
})
})
For example, if you need to change "string"/"date-time"
default output as string
to Date
you can do it with using following code:
generateApi({
primitiveTypeConstructs: (struct) => ({
string: {
"date-time": "Date",
},
})
})
See more about swagger schema type/format data here
- 5 Lessons learned about swagger-typescript-api
- Why Swagger schemes are needed in frontend development ?
- Migration en douceur vers TypeScript (French)
- swagger-typescript-api usage (Japanese)
βββ Please use the next
branch :)
If you need to check your changes at schemas in tests
folder before create a PR just run command npm run test-all
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!
Licensed under the MIT License.